Skip to content

New concept: Optional #2913

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

Draft
wants to merge 19 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 3 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
7 changes: 7 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,13 @@
"generic-types"
],
"status": "beta"
},
{
"slug": "tim-from-marketing-2",
"name": "tim-from-marketing-2",
"uuid": "a6cfc286-8c62-4f5b-8e59-f6bfc4374092",
"concepts": [],
"prerequisites": []
}
],
"practice": [
Expand Down
11 changes: 11 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,11 @@
# Hints

## 1.- Print the name of all the employees

WIP


Check failure on line 7 in exercises/concept/tim-from-marketing-2/.docs/hints.md

View workflow job for this annotation

GitHub Actions / Lint Markdown files

Multiple consecutive blank lines

exercises/concept/tim-from-marketing-2/.docs/hints.md:7 MD012/no-multiple-blanks Multiple consecutive blank lines [Expected: 1; Actual: 2] https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md012.md
## 2.- Print the name and department of a given employee

WIP

35 changes: 35 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,35 @@
# Instructions

In this exercise you will be writing code to print all the names of the factory employees.

Employees have an ID, a name and a department name, like in [tim-from-marketing](/exercises/concept/tim-from-marketing).

Check failure on line 5 in exercises/concept/tim-from-marketing-2/.docs/instructions.md

View workflow job for this annotation

GitHub Actions / Lint Markdown files

Trailing spaces

exercises/concept/tim-from-marketing-2/.docs/instructions.md:5:121 MD009/no-trailing-spaces Trailing spaces [Expected: 0 or 2; Actual: 1] https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md009.md
Assume that the ID of the first employee is 1, the ID of the second employee is 2, and so on. The three fields of an employee may be empty, That's why they are declared as Optional<T> types.

Two methods are already implemented:

- `getAllTheEmployeesById()` returns an Optional<List<Employee>> object. Notice this method does NOT receive any parameter.
- `getEmployeeById(id)` returns an Optional<Employee> object for the given ID, being Employee the following class:

## 1.- Print the names of all the employees

Implement the `printAllEmployeesNamesById()` method to print the names of all the employees, together with their id. If the employee does not exist, print "[id] - This employee does not exist".

```java
"1 - Tim"
"2 - Bill"
"3 - Steve"
"4 - This employee does not exist"
"5 - Charlotte"
```

## 2.- Print the name and department of a given employee

Implement the `printEmployeeNameAndDepartmentById(id)` method to print the name and department of a given employee, together with their id. If the employee does not exist, print "[id] - This employee does not exist":

```java
printEmployeeNameAndDepartmentById(1) => "1 - Tim - Marketing"
printEmployeeNameAndDepartmentById(2) => "2 - Bill - Sales"
printEmployeeNameAndDepartmentById(3) => "3 - Steve - Engineering"
printEmployeeNameAndDepartmentById(4) => "4 - This employee does not exist"
printEmployeeNameAndDepartmentById(5) => "5 - Charlotte - Owner"
```
77 changes: 77 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,77 @@
# Introduction

## Optional

## Introduction

The **Optional<T>** type was introduced in Java 8 as a way to indicate that a method will return an object of type T or an empty value. It is present in type signatures of many core Java methods.

Check failure on line 7 in exercises/concept/tim-from-marketing-2/.docs/introduction.md

View workflow job for this annotation

GitHub Actions / Lint Markdown files

Trailing spaces

exercises/concept/tim-from-marketing-2/.docs/introduction.md:7:196 MD009/no-trailing-spaces Trailing spaces [Expected: 0 or 2; Actual: 1] https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md009.md

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 {
return 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"));
}
```

If a default value must be returned, the `orElse` 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"));
}
```

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(0);
}
```

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.

Check failure on line 54 in exercises/concept/tim-from-marketing-2/.docs/introduction.md

View workflow job for this annotation

GitHub Actions / Lint Markdown files

Trailing spaces

exercises/concept/tim-from-marketing-2/.docs/introduction.md:54:201 MD009/no-trailing-spaces Trailing spaces [Expected: 0 or 2; Actual: 1] https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md009.md

## Flatmap operation

The **flatMap** method flattens a List of Optional objects into a List of those objects. In other words, extracts the value of each list element, discarding empty Optionals. For example:

```java
List<Optional<String>> listOfOptionals = Arrays.asList(
Optional.of("Java"),
Optional.empty(),
Optional.of("Kotlin")
);
```

```java
// Using flatMap to extract present values
List<String> result = listOfOptionals.stream()
.flatMap(Optional::stream)
.collect(Collectors.toList());

System.out.println(result); // Output: [Java, Kotlin]
}
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Introduction

%{concept:optional-types}

%{concept:flatMap-operation}
18 changes: 18 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,18 @@
{
"authors": [
"josealonso"
],
"files": {
"solution": [
"src/main/java/EmployeeService.java"
],
"test": [
"src/test/java/EmployeeServiceTest.java"
],
"exemplar": [
".meta/src/reference/java/EmployeeService.java"
],
},
"icon": "language-list",
"blurb": "Learn to use the Optional class by helping Tim print details of his company employees."
}
51 changes: 51 additions & 0 deletions exercises/concept/tim-from-marketing-2/.meta/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Design

## Goal

The goal of this exercise is to teach the student how to use the Optional API.
We will use the most common methods: `ifPresent`, `orElse`, `ifPresentOrElse`, `orElseThrown`.

Check failure on line 6 in exercises/concept/tim-from-marketing-2/.meta/design.md

View workflow job for this annotation

GitHub Actions / Lint Markdown files

Trailing spaces

exercises/concept/tim-from-marketing-2/.meta/design.md:6:95 MD009/no-trailing-spaces Trailing spaces [Expected: 0 or 2; Actual: 1] https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md009.md
The `isPresent` and `get` methods are not presented, since they do not provide any value over an ordinary null check.

Some methods of the Stream API are needed. This is a bit problematic, since they have not been explained in the current Java track.

## Learning objectives

- Know what optional types are.
- Know how to use Optional<T> fields.
- Know how to use methods that return an Optional<T> type.
- See the utility of some Stream methods, `flatMap` specifically.

## Out of scope

- Streams API.

## Concepts

This Concepts Exercise's Concepts are:

- `Optional<T>` class and some methods that mimic a null check.

## Prerequisites

This Concept Exercise's prerequisites Concepts are:

- `custom classes`.
- `generic-types`.
- `streams`.

## Analyzer

wip

This exercise could benefit from the following rules in the [analyzer]:

- `actionable`: If the solution did not use `contains` in the method `containsLanguage`, instruct the student to do so.
- `actionable`: If the solution did not use `isEmpty` in the method `isEmpty`, instruct the student to do so.
- `informative`: If the student did not reuse the implementation of the `containsLanguage` method in the `isExciting` method, instruct them to do so.
Explain that reusing existing code instead of copy-pasting can help make code easier to maintain.
- `informative`: If the solution uses an `if statement` in the `containsLanguage` method, instruct the student to return directly the `contains` method.

If the solution does not receive any of the above feedback, it must be exemplar.
Leave a `celebratory` comment to celebrate the success!

[analyzer]: https://github.com/exercism/java-analyzer
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import java.util.List;
import java.util.ArrayList;
import java.util.Optional;
import java.util.stream.*;


class EmployeeService {

Copy link
Member

Choose a reason for hiding this comment

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

To be honest, I didn't quite get the purpose of these two methods (getAllTheEmployeesById and getEmployeeById) especially as we're not expecting the students to implement these and I don't think its clear when we expect them to call them.

Since the Employee have methods that return Optional objects, I'd suggest having the test pass Employee objects or a List of them instead.

Is it also worth having a task for where the student has to return an Optional as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for your feedback.
I agree, I did it like that because I had all the code in one single file.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this has been resolved.

Copy link
Member

@kahgoh kahgoh Feb 15, 2025

Choose a reason for hiding this comment

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

I had another thought - the getEmployeeById could also be left up to the student to implement. We already have the foreach concept, so they could use that to find the employee and that'll give them the chance to practice making an Optional object (assuming they are looking through a List<Employee>) and there would be no need for streams in the class.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@kahgoh, despite I spent many hours creating this exercise, I have to admit it's probably a bit complex for a learning exercise. This is what I propose:

  • Leaving this one for a future practice exercise.
  • Create a concept exercise for Streams first. And maybe another one for Suppliers or functional interfaces, based on my article or a similar one.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll leave a longer answer tomorrow, but I just wanted to thank you for your support and clarify that my struggle is due to being my first exercise designed from scratch and also because I wasn't not very familiar with the Optional API.
I would cover more simple streams operations in a first Streams learning exercise, like map, filter and other simple operations.
@kahgoh

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I had another thought - the getEmployeeById could also be left up to the student to implement. We already have the foreach concept, so they could use that to find the employee and that'll give them the chance to practice making an Optional object (assuming they are looking through a List<Employee>) and there would be no need for streams in the class.

Are you saying I can implement the same methods without using streams ? I'm not sure that's doable. 🤔

Copy link
Member

@kahgoh kahgoh Feb 20, 2025

Choose a reason for hiding this comment

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

I was thinking the EmployService could take in a List<Employee> in the constructor, so then the students could be tasked with finding the Employee by id. I imagine the stub would like this:

class EmployeeService {
  public EmployeeService(List<Employee> employees) {
    throw new UnsupportedOperationException("Please implement the EmployeeService constructor");
  }

  public Optional<Employee> getEmployeeById(int id) {
     throw new UnsupportedOperationException("Please implement the EmployeeService.getEmployeeById() method");
  }
}

Students can still make a solution for this using a for / while loop that goes through the list until it finds the Employee. Here's one rough way:

for (Employee candidate: employeesList) {
  if (Objects.equals(candidate.getId(), Optional.of(id)) {
    return Optional.of(candidate);
  }
}

return Optional.empty();

Alternatively, I think passing the method a List<Employee> to the method would also work.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was thinking the EmployService could take in a List<Employee> in the constructor, so then the students could be tasked with finding the Employee by id. I imagine the stub would like this:

class EmployeeService {
  public EmployeeService(List<Employee> employees) {
    throw new UnsupportedOperationException("Please implement the EmployeeService constructor");
  }

  public Optional<Employee> getEmployeeById(int id) {
     throw new UnsupportedOperationException("Please implement the EmployeeService.getEmployeeById() method");
  }
}

Students can still make a solution for this using a for / while loop that goes through the list until it finds the Employee. Here's one rough way:

for (Employee candidate: employeesList) {
  if (Objects.equals(candidate.getId(), Optional.of(id)) {
    return Optional.of(candidate);
  }
}

return Optional.empty();

Alternatively, I think passing the method a List<Employee> to the method would also work.

Thanks @kahgoh. I'll try to change it this weekend. I was stuck, because I designed this exercise to try to mimic the access to a database. But you're right, I made it too complex.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for your help, @kahgoh. Take a look when you have a chance. I think I did it the way you suggested. I'm sure it can be improved, but streams are not used in the reference solution.

public List<Optional<Employee>> getAllTheEmployeesById() {
return getAllTheEmployeesById()
.stream()
.map(employee -> Optional.ofNullable(employee))
.collect(Collectors.toList());
}

public Optional<Employee> getEmployeeById(int employeeId) {
/* Solution using Streams

return getAllTheEmployeesById(employeesList).stream()
.filter(employee -> employee.getId() == id)
.orElse("Employee not found");
*/

return Optional.ofNullable(getEmployeeById(employeeId));
}

public String printAllEmployeesNamesById() {
List<Optional<Employee>> nullableEmployeesList = getAllTheEmployeesById();
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < nullableEmployeesList.size(); i++) {
stringBuilder.append(i).append(" - ");

nullableEmployeesList.get(i)
.flatMap(employee -> employee.getName())
.ifPresentOrElse(
name -> stringBuilder.append(name).append("\n"),
() -> stringBuilder.append("No employee found\n")
);
}
return stringBuilder.toString();
}

public String printEmployeeNameAndDepartmentById(int employeeId) {

var employee = getEmployeeById(employeeId);
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(employeeId).append(" - ");
employee.ifPresentOrElse(
e -> {
// Handle Optional values
e.getName().ifPresentOrElse(
name -> stringBuilder.append(name).append(" - "),
() -> {}
);
e.getDepartment().ifPresentOrElse(
department -> stringBuilder.append(department),
() -> {}
);
},
() -> stringBuilder.append("No employee found")
);
return stringBuilder.toString();
}

}

Empty file.
23 changes: 23 additions & 0 deletions exercises/concept/tim-from-marketing-2/src/main/java/Employee.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
class Employee {
private final int id;
private final String name;
private final String department;

public Employee(int id, String name, String department) {
this.id = id;
this.name = name;
this.department = department;
}

public Optional<Integer> getId() {
return Optional.ofNullable(id);
}

public Optional<String> getName() {
return Optional.ofNullable(name);
}

public Optional<String> getDepartment() {
return Optional.ofNullable(department);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import java.util.List;
import java.util.Optional;
import java.util.stream.*;

class EmployeeService {

/*
The getAllTheEmployeesById and getEmployeeById methods are already implemented.
*/

// Convert the list of employees to a list of Optional<Employee>
public List<Optional<Employee>> getAllTheEmployeesById() {
return getAllTheEmployeesById()
.stream()
.map(employee -> Optional.ofNullable(employee))
.collect(Collectors.toList());
}

public Optional<Employee> getEmployeeById(int id) {
return Optional.ofNullable(getEmployeeById(employeeId));
}


public String printAllEmployeesNamesById() {
throw new UnsupportedOperationException("Please implement the EmployeeService.printAllEmployeesNamesById() method");
}

public String printEmployeeNameAndDepartmentById(int employeeId) {
throw new UnsupportedOperationException("Please implement the EmployeeService.printEmployeeNameAndDepartmentById(id) method");
}


}
Loading
Loading