-
-
Notifications
You must be signed in to change notification settings - Fork 700
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
base: main
Are you sure you want to change the base?
New concept: Optional #2913
Changes from all commits
e88392b
fbe3f46
0abf443
5c143e1
26a1397
f94749a
4c5e540
4b7a800
9d4cd39
6db542e
53cbbda
145d49b
9b20d73
c227af5
8f2afa8
27b6662
f047509
ec171ee
41076df
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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" | ||
] | ||
} |
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. |
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); | ||
``` | ||
|
||
`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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
|
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> |
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 | ||
|
||
josealonso marked this conversation as resolved.
Show resolved
Hide resolved
|
||
- You can call the method `getEmployeeById(int)` to get the employee. | ||
- Remember the syntax of the `ifPresentOrElse()` method. |
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]`. |
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); | ||
``` | ||
|
||
josealonso marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I'd also suggest |
||
``` | ||
|
||
`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} |
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." | ||
} |
There was a problem hiding this comment.
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 nevernull
). I'd also suggest calling the variableoptionalEmployee
becausenullable
tends mean you can assign anull
value to it.