Skip to content
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