Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New concept: Optional #2913

Draft
wants to merge 19 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions concepts/optional-type/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"blurb": "Learn to use the Optional class by helping Tim print details of his company employees.",
"authors": [
"josealonso"
],
"contributors": [
"kahgoh"
]
}
59 changes: 59 additions & 0 deletions concepts/optional-type/about.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# About the Optional Type

The **Optional<T>** type was introduced in Java 8 as a way to indicate that a method _may_ return a value.

Before Java 8, developers had to implement null checks:

```java
public Employee getEmployee(String name) {
// Assume that getEmployeeByName retrieves an Employee from a database
Employee employee = getEmployeeByName(name);
if (employee != null) {
return employee;
} else {
throw new IllegalArgumentException("Employee not found");
}
}
```

With the Optional API, the code above can be simplified to:

```java
public Optional<Employee> getEmployee(String name) {
// Assume that getEmployeeByName returns an Optional<Employee>
return getEmployeeByName(name)
.orElseThrow(() -> new IllegalArgumentException("Employee not found"));
}
```

## Usage with the Stream API

The Optional API is more useful when many methods are chained and each method returns an Optional<T> object.

```java
List<Optional<Employee>> employees = new ArrayList<>();
employees.add(getEmployee("Tim"));
employees.add(getEmployee("Bob"));
employees.add(getEmployee("Alice"));
```

```java
public List<Optional<Employee>> getEmployeesInTheirTwenties(){
return employees.stream()
.filter(Optional::isPresent)
.map(Optional::get)
.filter(employee -> employee.getAge() >= 20 && employee.getAge() < 30)
.collect(Collectors.toList());
}
```

It is important to understand that the Optional API does not eliminate the null checking,
but it defers it until the end of a series of methods, as long as all those methods return an optional object.

## Recommended usage

The Optional type is mainly used as returned type. Using it as a parameter type or field type is less common and
not recommended, as explained by one well-known Java language architect in [this SO answer](https://stackoverflow.com/questions/26327957/should-java-8-getters-return-optional-type)

The official documentation says:
> Optional is primarily intended for use as a method return type where there is a clear need to represent "no result," and where using null is likely to cause errors. A variable whose type is Optional should never itself be null; it should always point to an Optional instance.
90 changes: 90 additions & 0 deletions concepts/optional-type/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Introduction

## Optional

The **Optional<T>** type was introduced in Java 8 as a way to indicate that a method _may_ return a value.

In other words, there is a chance the method returns "no value" at all.

## Creating an Optional<T> object

Given an object of type Employee, an Optional<Employee> object can be created using the static [of][optional-of-javadoc] method:

```java
Employee employee = new Employee();
Optional<Employee> optionalEmployee = Optional.of(employee);
```

If the employee _may_ be not present, the static [ofNullable][optional-ofNullable-javadoc] method must be used:

```java
Employee nullableEmployee = new Employee();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a better example would be calling a method that might return null (in this example, it is never null). I'd also suggest calling the variable optionalEmployee because nullable tends mean you can assign a null value to it.

Optional<Employee> nullableEmployee = Optional.ofNullable(employee);
```

`optionalEmployee` and `nullableEmployee` both are wrappers of an `Employee` object.

## Basic methods

If a value is present, the [isPresent][optional-isPresent-javadoc] method returns true and the [get][optional-get-javadoc] method returns the value.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've switch from "may return a value" or "no value" from the introduction to "present" here. I think it would help to define what "present" and "empty" mean either here or back in the introduction.


```java
Employee employee = new Employee("Tim", 45);
Optional<Employee> optionalEmployee = Optional.ofNullable(employee);
boolean isThereAnEmployee = optionalEmployee.isPresent(); // true
Employee employee = optionalEmployee.get();
```

## Usage

In order to throw an exception when the value is not present, the [orElseThrow][optional-orElseThrow-javadoc] method must be used.

```java
public Optional<Employee> getEmployee(String name) {
// Assume that getEmployeeByName returns an Optional<Employee>
return getEmployeeByName(name)
.orElseThrow(() -> new IllegalArgumentException("Employee not found"));
}
```

If a default value must be returned, the [orElse][optional-orElse-javadoc] method can be used.

```java
public Optional<Employee> getEmployee(String name) {
// Assume that getEmployeeByName returns an Optional<Employee>
return getEmployeeByName(name)
.orElse(new Employee("Daniel"));
}
```

Other commonly used method is [ifPresentOrElse][optional-ifPresentOrElse-javadoc], which is used to handle both cases with the same method: the case where the value is present and the case where the value is empty.

```java
public Optional<Employee> getEmployee(String name) {
// Assume that getEmployeeByName returns an Optional<Employee>
return getEmployeeByName(name)
.ifPresentOrElse(
employee -> System.out.println(employee.getName()),
() -> System.out.println("Employee not found")
);
}
```

Provided all the invoked methods return Optional objects, many methods can be chained without having to worry about null checking:

```java
public Optional<Integer> getEmployeeAge(String name) {
Optional<Employee> optionalEmployee = getEmployeeByName(name);
return getEmployeeByName(name)
.map(employee -> employee.getAge())
.orElse("No employee found");
}
```

[optional-of-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html#of(java.lang.Object)
[optional-ofNullable-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html#ofNullable(java.lang.Object)
[optional-get-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html#get()
[optional-isPresent-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html#isPresent()
[optional-orElse-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html#orElse(T)
[optional-orElseThrow-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html#orElseThrow()
[optional-ifPresentOrElse-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html#ifPresentOrElse(java.util.function.Consumer,java.lang.Runnable)
2 changes: 2 additions & 0 deletions concepts/optional-type/links.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest a link to the Javadocs for Optional. See the links.json for the Lists concept for an example.


19 changes: 18 additions & 1 deletion config.json
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,18 @@
"status": "beta"
},
{
"slug": "tim-from-marketing-2",
"name": "tim-from-marketing-2",
"uuid": "a6cfc286-8c62-4f5b-8e59-f6bfc4374092",
"concepts": [
"optional-type"
],
"prerequisites": [
"lists",
"generic-types"
]
},
{
"slug": "international-calling-connoisseur",
"name": "International Calling Connoisseur",
"uuid": "03506c5a-601a-42cd-b037-c310208de84d",
Expand Down Expand Up @@ -947,7 +959,7 @@
"practices": [],
"prerequisites": [
"arrays",
"if-statements"
"if-else-statements"
],
"difficulty": 5
},
Expand Down Expand Up @@ -1971,6 +1983,11 @@
"uuid": "78f3c7b2-cb9c-4d21-8cb4-7106a188f713",
"slug": "ternary-operators",
"name": "Ternary Operators"
},
{
"uuid": "",
"slug": "optional-type",
"name": "Optional Type"
}
],
"key_features": [
Expand Down
32 changes: 32 additions & 0 deletions exercises/concept/tim-from-marketing-2/.classpath
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="bin/starterTest" path="src/test/java">
<attributes>
<attribute name="gradle_scope" value="starterTest"/>
<attribute name="gradle_used_by_scope" value="starterTest"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="bin/starterSource" path="src/main/java">
<attributes>
<attribute name="gradle_scope" value="starterSource"/>
<attribute name="gradle_used_by_scope" value="starterSource"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="bin/test" path="build/gen/test/java">
<attributes>
<attribute name="gradle_scope" value="test"/>
<attribute name="gradle_used_by_scope" value="test"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="bin/main" path=".meta/src/reference/java">
<attributes>
<attribute name="gradle_scope" value="main"/>
<attribute name="gradle_used_by_scope" value="main,test"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17/"/>
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
<classpathentry kind="output" path="bin/default"/>
</classpath>
10 changes: 10 additions & 0 deletions exercises/concept/tim-from-marketing-2/.docs/hints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Hints

## 1. Get an employee by ID

- This method returns an `Optional<Employee>` object, not an `Employee` one.

## 2. Return the name and department of a given employee in a certain format

- You can call the method `getEmployeeById(int)` to get the employee.
- Remember the syntax of the `ifPresentOrElse()` method.
24 changes: 24 additions & 0 deletions exercises/concept/tim-from-marketing-2/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Instructions

In this exercise you will be writing code to retrieve the factory employees.
Employees have an ID, a name and a department name, like in the [tim-from-marketing](/exercises/concept/tim-from-marketing) exercise.
The first field of an employee is always an integer number, but the name and the department name may be empty or null.
The class constructor receives a parameter of type List<Employee>, which is populated in the tests.
You will be writing two methods: `getEmployeeById(int)` and `getEmployeeDetailsById(int)`.

## 1. Get an employee by ID

Implement the `getEmployeeById(int)` method so that it returns an Optional<Employee> object.

If the employee does not exist, returns an empty Optional instance.

## 2. Return the name and department of a given employee in a certain format

Implement the `getEmployeeDetailsById(int)` method to return a string containing the id, the name and the department of a given employee:

```java
getEmployeeDetailsById(1) => "1 - Tim - Marketing"
getEmployeeDetailsById(3) => "3 - Steve - Engineering"
```

If the employee does not exist or is null, it returns `No employee found for id: [id]`.
90 changes: 90 additions & 0 deletions exercises/concept/tim-from-marketing-2/.docs/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Introduction

## Optional

The **Optional<T>** type was introduced in Java 8 as a way to indicate that a method _may_ return a value.

In other words, there is a chance the method returns "no value" at all.

## Creating an Optional<T> object

Given an object of type Employee, an Optional<Employee> object can be created using the static [of][optional-of-javadoc] method:

```java
Employee employee = new Employee();
Optional<Employee> optionalEmployee = Optional.of(employee);
```

If the employee _may_ be not present, the static [ofNullable][optional-ofNullable-javadoc] method must be used:

```java
Employee nullableEmployee = new Employee();
Optional<Employee> nullableEmployee = Optional.ofNullable(employee);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nullableEmployee is never null here. I think a better example would be to call a method that might return null.

I'd also suggest Optional.empty.

```

`optionalEmployee` and `nullableEmployee` both are wrappers of an `Employee` object.

## Basic methods

If a value is present, the [isPresent][optional-isPresent-javadoc] method returns true and the [get][optional-get-javadoc] method returns the value.

```java
Employee employee = new Employee("Tim", 45);
Optional<Employee> optionalEmployee = Optional.ofNullable(employee);
boolean isThereAnEmployee = optionalEmployee.isPresent(); // true
Employee employee = optionalEmployee.get();
```

## Usage

In order to throw an exception when the value is not present, the [orElseThrow][optional-orElseThrow-javadoc] method must be used.

```java
public Optional<Employee> getEmployee(String name) {
// Assume that getEmployeeByName returns an Optional<Employee>
return getEmployeeByName(name)
.orElseThrow(() -> new IllegalArgumentException("Employee not found"));
}
```

If a default value must be returned, the [orElse][optional-orElse-javadoc] method can be used.

```java
public Optional<Employee> getEmployee(String name) {
// Assume that getEmployeeByName returns an Optional<Employee>
return getEmployeeByName(name)
.orElse(new Employee("Daniel"));
}
```

Other commonly used method is [ifPresentOrElse][optional-ifPresentOrElse-javadoc], which is used to handle both cases with the same method: the case where the value is present and the case where the value is empty.

```java
public Optional<Employee> getEmployee(String name) {
// Assume that getEmployeeByName returns an Optional<Employee>
return getEmployeeByName(name)
.ifPresentOrElse(
employee -> System.out.println(employee.getName()),
() -> System.out.println("Employee not found")
);
}
```

Provided all the invoked methods return Optional objects, many methods can be chained without having to worry about null checking:

```java
public Optional<Integer> getEmployeeAge(String name) {
Optional<Employee> optionalEmployee = getEmployeeByName(name);
return getEmployeeByName(name)
.map(employee -> employee.getAge())
.orElse("No employee found");
}
```

[optional-of-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html#of(java.lang.Object)
[optional-ofNullable-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html#ofNullable(java.lang.Object)
[optional-get-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html#get()
[optional-isPresent-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html#isPresent()
[optional-orElse-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html#orElse(T)
[optional-orElseThrow-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html#orElseThrow()
[optional-ifPresentOrElse-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html#ifPresentOrElse(java.util.function.Consumer,java.lang.Runnable)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Introduction

%{concept:optional-types}
26 changes: 26 additions & 0 deletions exercises/concept/tim-from-marketing-2/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "tim-from-marketing-2",
"uuid": "a6cfc286-8c62-4f5b-8e59-f6bfc4374092",
"concepts": [
"optional-type"
],
"authors": [
"josealonso"
],
"contributors": [
"kahgoh"
],
"files": {
"solution": [
"src/main/java/EmployeeDatabase.java"
],
"test": [
"src/test/java/EmployeeDatabaseTest.java"
],
"exemplar": [
".meta/src/reference/java/EmployeeDatabase.java"
]
},
"icon": "nullability",
"blurb": "Learn to use the Optional class by helping Tim print details of his company employees."
}
Loading
Loading