From e88392badd263703d62992b5d0357f0e92816b8e Mon Sep 17 00:00:00 2001 From: josealonso Date: Tue, 11 Feb 2025 00:03:50 +0100 Subject: [PATCH 01/18] Write explanation for the Optional concept, as well as the test file, the stub implementation, the reference solution and other required files. Some files must be modified. This is a draft. --- config.json | 7 + .../tim-from-marketing-2/.docs/hints.md | 0 .../.docs/instructions.md | 45 ++++++ .../.docs/introduction.md | 55 ++++++++ .../.docs/introduction.md.tpl | 5 + .../tim-from-marketing-2/.meta/config.json | 13 ++ .../tim-from-marketing-2/.meta/design.md | 0 .../java/DontCommitEmployeeService.java | 130 ++++++++++++++++++ .../src/reference/java/EmployeeService.java | 66 +++++++++ .../concept/tim-from-marketing-2/build.gradle | 0 .../src/main/java/Employee.java | 23 ++++ .../src/main/java/EmployeeService.java | 33 +++++ .../src/test/java/EmployeeServiceTest.java | 101 ++++++++++++++ exercises/settings.gradle | 1 + 14 files changed, 479 insertions(+) create mode 100644 exercises/concept/tim-from-marketing-2/.docs/hints.md create mode 100644 exercises/concept/tim-from-marketing-2/.docs/instructions.md create mode 100644 exercises/concept/tim-from-marketing-2/.docs/introduction.md create mode 100644 exercises/concept/tim-from-marketing-2/.docs/introduction.md.tpl create mode 100644 exercises/concept/tim-from-marketing-2/.meta/config.json create mode 100644 exercises/concept/tim-from-marketing-2/.meta/design.md create mode 100644 exercises/concept/tim-from-marketing-2/.meta/src/reference/java/DontCommitEmployeeService.java create mode 100644 exercises/concept/tim-from-marketing-2/.meta/src/reference/java/EmployeeService.java create mode 100644 exercises/concept/tim-from-marketing-2/build.gradle create mode 100644 exercises/concept/tim-from-marketing-2/src/main/java/Employee.java create mode 100644 exercises/concept/tim-from-marketing-2/src/main/java/EmployeeService.java create mode 100644 exercises/concept/tim-from-marketing-2/src/test/java/EmployeeServiceTest.java diff --git a/config.json b/config.json index 86038a6b9..13de31349 100644 --- a/config.json +++ b/config.json @@ -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": [ diff --git a/exercises/concept/tim-from-marketing-2/.docs/hints.md b/exercises/concept/tim-from-marketing-2/.docs/hints.md new file mode 100644 index 000000000..e69de29bb diff --git a/exercises/concept/tim-from-marketing-2/.docs/instructions.md b/exercises/concept/tim-from-marketing-2/.docs/instructions.md new file mode 100644 index 000000000..68661bd96 --- /dev/null +++ b/exercises/concept/tim-from-marketing-2/.docs/instructions.md @@ -0,0 +1,45 @@ +# 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). +Assume that the ID of the first employee is 1, the ID of the second employee is 2, and so on. If an employee has an ID, the other two fields, name and department name, have valid values. + +Two methods are already implemented: + +- `getAllTheEmployeesById()` returns an Optional> object. Notice this method does NOT receive any parameter. +- `getEmployeeById(id)` returns an Optional object for the given ID, being Employee the following class: + +```java +class Employee { + private int id; + private String name; + private String departmentName; + // Getters and setters +} +``` + +## 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" +``` + diff --git a/exercises/concept/tim-from-marketing-2/.docs/introduction.md b/exercises/concept/tim-from-marketing-2/.docs/introduction.md new file mode 100644 index 000000000..90e184cfe --- /dev/null +++ b/exercises/concept/tim-from-marketing-2/.docs/introduction.md @@ -0,0 +1,55 @@ +# Introduction + +## Optional + +## Introduction + +The Optional type was introduced in Java 8 as a way to indicate that a method will return an object of type T or a null value. It is present in type signatures of many core Java methods. + +Before Java 8, developers had to implement null checks: + +```java +public Employee getEmployee(String name) { + // Assume that getEmployeeByName retrieves an Employee from a data base + 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 getEmployee(String name) { + // Assume that getEmployeeByName returns an Optional + 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 getEmployee(String name) { + // Assume that getEmployeeByName returns an Optional + 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 getEmployeeAge(String name) { + Optional optionalEmployee = getEmployeeByName(name); + return getEmployeeByName(name) + .map(Employee::getAge) + .orElse(0); +} +``` + +It is important to understand that the Optional API does not eliminate the null values. It defers the null checking until the end of a series of methods, as long as all those methods return an optional object. + diff --git a/exercises/concept/tim-from-marketing-2/.docs/introduction.md.tpl b/exercises/concept/tim-from-marketing-2/.docs/introduction.md.tpl new file mode 100644 index 000000000..0fd29ff28 --- /dev/null +++ b/exercises/concept/tim-from-marketing-2/.docs/introduction.md.tpl @@ -0,0 +1,5 @@ +# Introduction + +## Optional + +%{concept:optional} diff --git a/exercises/concept/tim-from-marketing-2/.meta/config.json b/exercises/concept/tim-from-marketing-2/.meta/config.json new file mode 100644 index 000000000..85d83c0a4 --- /dev/null +++ b/exercises/concept/tim-from-marketing-2/.meta/config.json @@ -0,0 +1,13 @@ +{ + "authors": ["josealonso"], + "files": { + "solution": [ + "src/main/java/TimFromMarketing2.java" + ], + "test": [ + "src/test/java/TimFromMarketing2Test.java" + ], + "exemplar": [] + }, + "blurb": "" +} diff --git a/exercises/concept/tim-from-marketing-2/.meta/design.md b/exercises/concept/tim-from-marketing-2/.meta/design.md new file mode 100644 index 000000000..e69de29bb diff --git a/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/DontCommitEmployeeService.java b/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/DontCommitEmployeeService.java new file mode 100644 index 000000000..d2d9f6792 --- /dev/null +++ b/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/DontCommitEmployeeService.java @@ -0,0 +1,130 @@ +import java.util.List; +import java.util.ArrayList; +import java.util.Optional; +import java.util.stream.*; + + +public class Main { + public static void main(String[] args) { + System.out.println("Hello"); + + var employeeService = new EmployeeService(); + System.out.println(employeeService.printAllEmployeesNamesById()); + System.out.println(employeeService.printEmployeeNameAndDepartmentById(1)); + System.out.println(employeeService.printEmployeeNameAndDepartmentById(2)); + System.out.println(employeeService.printEmployeeNameAndDepartmentById(3)); + System.out.println(employeeService.printEmployeeNameAndDepartmentById(4)); + } +} + +class EmployeeService { + + private Utils utils = new Utils(); + + public List> getAllTheEmployeesById() { + return utils.getAllTheEmployeesById() + .stream() + .map(employee -> Optional.ofNullable(employee)) + .collect(Collectors.toList()); + } + + public Optional getEmployeeById(int employeeId) { + /* Solution using Streams + + return getAllTheEmployeesById(employeesList).stream() + .filter(employee -> employee.getId() == id) + .orElse("Employee not found"); + */ + + return Optional.ofNullable(utils.getEmployeeById(employeeId)); + } + + public String printAllEmployeesNamesById() { + List> nullableEmployeesList = getAllTheEmployeesById(); + // var employee = new Employee(); + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < nullableEmployeesList.size(); i++) { + stringBuilder.append(i).append(" - "); + + nullableEmployeesList.get(i) + .flatMap(employee -> employee.getName()) + // .map(nullableEmployee -> nullableEmployee.get()) // Compile error + .ifPresentOrElse( + name -> stringBuilder.append(name).append("\n"), + () -> stringBuilder.append("No employee found\n") + ); + // .ifPresent(name -> stringBuilder.append(name)) + // .orElse("No employee found"); // compile error + } + 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(); + } + +} + +class Utils { + + private List listOfEmployees = new ArrayList<>(); + // List.of( // Immutable lists do not allow null elements +{ + listOfEmployees.add(new Employee(0, "Tim", "Direction")); + listOfEmployees.add(new Employee(1, "Mark", "Sales")); + listOfEmployees.add(new Employee(2, "John", "Engineering")); + listOfEmployees.add(null); // Adding a null element + listOfEmployees.add(new Employee(4, "Jane", "Sales")); +} + + public List getAllTheEmployeesById() { + return listOfEmployees; + } + + public Employee getEmployeeById(int employeeId) { + return listOfEmployees.get(employeeId); + } +} + + +class Employee { + private final int id; // It can't be final due to the empty constructor + 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 getId() { + return Optional.ofNullable(id); + } + + public Optional getName() { + return Optional.ofNullable(name); + } + + public Optional getDepartment() { + return Optional.ofNullable(department); + } +} diff --git a/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/EmployeeService.java b/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/EmployeeService.java new file mode 100644 index 000000000..78ceaf00a --- /dev/null +++ b/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/EmployeeService.java @@ -0,0 +1,66 @@ +import java.util.List; +import java.util.ArrayList; +import java.util.Optional; +import java.util.stream.*; + + +class EmployeeService { + + public List> getAllTheEmployeesById() { + return getAllTheEmployeesById() + .stream() + .map(employee -> Optional.ofNullable(employee)) + .collect(Collectors.toList()); + } + + public Optional 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> 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(); + } + +} + diff --git a/exercises/concept/tim-from-marketing-2/build.gradle b/exercises/concept/tim-from-marketing-2/build.gradle new file mode 100644 index 000000000..e69de29bb diff --git a/exercises/concept/tim-from-marketing-2/src/main/java/Employee.java b/exercises/concept/tim-from-marketing-2/src/main/java/Employee.java new file mode 100644 index 000000000..570c161e1 --- /dev/null +++ b/exercises/concept/tim-from-marketing-2/src/main/java/Employee.java @@ -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 getId() { + return Optional.ofNullable(id); + } + + public Optional getName() { + return Optional.ofNullable(name); + } + + public Optional getDepartment() { + return Optional.ofNullable(department); + } +} diff --git a/exercises/concept/tim-from-marketing-2/src/main/java/EmployeeService.java b/exercises/concept/tim-from-marketing-2/src/main/java/EmployeeService.java new file mode 100644 index 000000000..3256ffd81 --- /dev/null +++ b/exercises/concept/tim-from-marketing-2/src/main/java/EmployeeService.java @@ -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 + public List> getAllTheEmployeesById() { + return getAllTheEmployeesById() + .stream() + .map(employee -> Optional.ofNullable(employee)) + .collect(Collectors.toList()); + } + + public Optional 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"); + } + + +} diff --git a/exercises/concept/tim-from-marketing-2/src/test/java/EmployeeServiceTest.java b/exercises/concept/tim-from-marketing-2/src/test/java/EmployeeServiceTest.java new file mode 100644 index 000000000..ba5e16a35 --- /dev/null +++ b/exercises/concept/tim-from-marketing-2/src/test/java/EmployeeServiceTest.java @@ -0,0 +1,101 @@ +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.LifecycleManagement; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.ArrayList; +import java.util.Optional; +import java.util.stream.*; + +// Annotation in order to use @BeforeAll in a non-static method +@TestInstance(LifecycleManagement.Lifecycle.PER_CLASS) +public class EmployeeServiceTest { + + private EmployeeService employeeService; + private List listOfEmployees = new ArrayList<>(); + + private List expectedListOfPrintedEmployeesNames = List.of( + "0 - Tim", + "1 - Mark", + "2 - John", + "3 - No employee found", + "4 - Jane" + ); + + private List expectedListOfPrintedEmployeesNamesAndDepartments = List.of( + "0 - Tim - Direction", + "1 - Mark - Sales", + "2 - John - Engineering", + "3 - No employee found", + "4 - Jane - Sales" + ); + + public List getAllTheEmployeesById() { + return listOfEmployees; + } + + public Employee getEmployeeById(int employeeId) { + return listOfEmployees.get(employeeId); + } + + void initList() { + listOfEmployees.add(new Employee(0, "Tim", "Direction")); + listOfEmployees.add(new Employee(1, "Mark", "Sales")); + listOfEmployees.add(new Employee(2, "John", "Engineering")); + listOfEmployees.add(null); // Adding a null element + listOfEmployees.add(new Employee(4, "Jane", "Sales")); + } + + @BeforeAll + void setup() { + initList(); + employeeService = new EmployeeService(); + } + + @Test + @Tag("task:1") + @DisplayName("Printing all the employees names") + void printAllTheEmployeesNames_includingNonExistentOnes() { + assertThat(employeeService.printAllEmployeesNamesById()) + .isEqualTo(expectedListOfPrintedEmployeesNames); + } + + @ParameterizedTest + // @Tag("task:2") + @MethodSource("employeeTestData") + @DisplayName("Printing the details of an employee with various IDs") + void givenAnId_printAnEmployeeNameAndDepartment(int id, String expected) { + assertThat(employeeService.printEmployeeNameAndDepartmentById(1)) + .isEqualTo(expectedListOfPrintedEmployeesNamesAndDepartments.get(1)); + } + + static Stream employeeTestData() { + return Stream.of( + Arguments.of(0), expectedListOfPrintedEmployeesNamesAndDepartments.get(0), + Arguments.of(1), expectedListOfPrintedEmployeesNamesAndDepartments.get(1), + Arguments.of(2), expectedListOfPrintedEmployeesNamesAndDepartments.get(2), + Arguments.of(3), expectedListOfPrintedEmployeesNamesAndDepartments.get(3), + Arguments.of(4), expectedListOfPrintedEmployeesNamesAndDepartments.get(4) + ); + } + + + + + + + + + + + + + +} diff --git a/exercises/settings.gradle b/exercises/settings.gradle index a6686a473..a30afeaa2 100644 --- a/exercises/settings.gradle +++ b/exercises/settings.gradle @@ -21,6 +21,7 @@ include 'concept:salary-calculator' include 'concept:secrets' include 'concept:squeaky-clean' include 'concept:tim-from-marketing' +include 'concept:tim-from-marketing-2' include 'concept:wizards-and-warriors' include 'concept:wizards-and-warriors-2' From fbe3f4620bc7aacde70f24be2d8b087410bccb76 Mon Sep 17 00:00:00 2001 From: josealonso Date: Tue, 11 Feb 2025 00:35:18 +0100 Subject: [PATCH 02/18] Delete unused file. --- .../java/DontCommitEmployeeService.java | 130 ------------------ 1 file changed, 130 deletions(-) delete mode 100644 exercises/concept/tim-from-marketing-2/.meta/src/reference/java/DontCommitEmployeeService.java diff --git a/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/DontCommitEmployeeService.java b/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/DontCommitEmployeeService.java deleted file mode 100644 index d2d9f6792..000000000 --- a/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/DontCommitEmployeeService.java +++ /dev/null @@ -1,130 +0,0 @@ -import java.util.List; -import java.util.ArrayList; -import java.util.Optional; -import java.util.stream.*; - - -public class Main { - public static void main(String[] args) { - System.out.println("Hello"); - - var employeeService = new EmployeeService(); - System.out.println(employeeService.printAllEmployeesNamesById()); - System.out.println(employeeService.printEmployeeNameAndDepartmentById(1)); - System.out.println(employeeService.printEmployeeNameAndDepartmentById(2)); - System.out.println(employeeService.printEmployeeNameAndDepartmentById(3)); - System.out.println(employeeService.printEmployeeNameAndDepartmentById(4)); - } -} - -class EmployeeService { - - private Utils utils = new Utils(); - - public List> getAllTheEmployeesById() { - return utils.getAllTheEmployeesById() - .stream() - .map(employee -> Optional.ofNullable(employee)) - .collect(Collectors.toList()); - } - - public Optional getEmployeeById(int employeeId) { - /* Solution using Streams - - return getAllTheEmployeesById(employeesList).stream() - .filter(employee -> employee.getId() == id) - .orElse("Employee not found"); - */ - - return Optional.ofNullable(utils.getEmployeeById(employeeId)); - } - - public String printAllEmployeesNamesById() { - List> nullableEmployeesList = getAllTheEmployeesById(); - // var employee = new Employee(); - StringBuilder stringBuilder = new StringBuilder(); - for (int i = 0; i < nullableEmployeesList.size(); i++) { - stringBuilder.append(i).append(" - "); - - nullableEmployeesList.get(i) - .flatMap(employee -> employee.getName()) - // .map(nullableEmployee -> nullableEmployee.get()) // Compile error - .ifPresentOrElse( - name -> stringBuilder.append(name).append("\n"), - () -> stringBuilder.append("No employee found\n") - ); - // .ifPresent(name -> stringBuilder.append(name)) - // .orElse("No employee found"); // compile error - } - 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(); - } - -} - -class Utils { - - private List listOfEmployees = new ArrayList<>(); - // List.of( // Immutable lists do not allow null elements -{ - listOfEmployees.add(new Employee(0, "Tim", "Direction")); - listOfEmployees.add(new Employee(1, "Mark", "Sales")); - listOfEmployees.add(new Employee(2, "John", "Engineering")); - listOfEmployees.add(null); // Adding a null element - listOfEmployees.add(new Employee(4, "Jane", "Sales")); -} - - public List getAllTheEmployeesById() { - return listOfEmployees; - } - - public Employee getEmployeeById(int employeeId) { - return listOfEmployees.get(employeeId); - } -} - - -class Employee { - private final int id; // It can't be final due to the empty constructor - 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 getId() { - return Optional.ofNullable(id); - } - - public Optional getName() { - return Optional.ofNullable(name); - } - - public Optional getDepartment() { - return Optional.ofNullable(department); - } -} From 0abf4435d8078c4aad41df4076bfa0b376062a44 Mon Sep 17 00:00:00 2001 From: josealonso Date: Tue, 11 Feb 2025 18:59:10 +0100 Subject: [PATCH 03/18] Explain the flatmap operation. The hints.md and introduction.md files are not over yet. --- .../tim-from-marketing-2/.docs/hints.md | 11 ++++ .../.docs/instructions.md | 12 +---- .../.docs/introduction.md | 30 +++++++++-- .../.docs/introduction.md.tpl | 4 +- .../tim-from-marketing-2/.meta/config.json | 15 ++++-- .../tim-from-marketing-2/.meta/design.md | 51 +++++++++++++++++++ 6 files changed, 101 insertions(+), 22 deletions(-) diff --git a/exercises/concept/tim-from-marketing-2/.docs/hints.md b/exercises/concept/tim-from-marketing-2/.docs/hints.md index e69de29bb..25cbe29ce 100644 --- a/exercises/concept/tim-from-marketing-2/.docs/hints.md +++ b/exercises/concept/tim-from-marketing-2/.docs/hints.md @@ -0,0 +1,11 @@ +# Hints + +## 1.- Print the name of all the employees + +WIP + + +## 2.- Print the name and department of a given employee + +WIP + diff --git a/exercises/concept/tim-from-marketing-2/.docs/instructions.md b/exercises/concept/tim-from-marketing-2/.docs/instructions.md index 68661bd96..35616c8a1 100644 --- a/exercises/concept/tim-from-marketing-2/.docs/instructions.md +++ b/exercises/concept/tim-from-marketing-2/.docs/instructions.md @@ -3,22 +3,13 @@ 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). -Assume that the ID of the first employee is 1, the ID of the second employee is 2, and so on. If an employee has an ID, the other two fields, name and department name, have valid values. +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 types. Two methods are already implemented: - `getAllTheEmployeesById()` returns an Optional> object. Notice this method does NOT receive any parameter. - `getEmployeeById(id)` returns an Optional object for the given ID, being Employee the following class: -```java -class Employee { - private int id; - private String name; - private String departmentName; - // Getters and setters -} -``` - ## 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". @@ -42,4 +33,3 @@ printEmployeeNameAndDepartmentById(3) => "3 - Steve - Engineering" printEmployeeNameAndDepartmentById(4) => "4 - This employee does not exist" printEmployeeNameAndDepartmentById(5) => "5 - Charlotte - Owner" ``` - diff --git a/exercises/concept/tim-from-marketing-2/.docs/introduction.md b/exercises/concept/tim-from-marketing-2/.docs/introduction.md index 90e184cfe..c7ccb32ca 100644 --- a/exercises/concept/tim-from-marketing-2/.docs/introduction.md +++ b/exercises/concept/tim-from-marketing-2/.docs/introduction.md @@ -4,13 +4,13 @@ ## Introduction -The Optional type was introduced in Java 8 as a way to indicate that a method will return an object of type T or a null value. It is present in type signatures of many core Java methods. +The **Optional** 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. Before Java 8, developers had to implement null checks: ```java public Employee getEmployee(String name) { - // Assume that getEmployeeByName retrieves an Employee from a data base + // Assume that getEmployeeByName retrieves an Employee from a database Employee employee = getEmployeeByName(name); if (employee != null) { return employee; @@ -46,10 +46,32 @@ Provided all the invoked methods return Optional objects, many methods can be ch public Optional getEmployeeAge(String name) { Optional optionalEmployee = getEmployeeByName(name); return getEmployeeByName(name) - .map(Employee::getAge) + .map(employee -> employee.getAge()) .orElse(0); } ``` -It is important to understand that the Optional API does not eliminate the null values. It defers the null checking until the end of a series of methods, as long as all those methods return an optional object. +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. +## 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> listOfOptionals = Arrays.asList( + Optional.of("Java"), + Optional.empty(), + Optional.of("Kotlin") +); +``` + +```java +// Using flatMap to extract present values + List result = listOfOptionals.stream() + .flatMap(Optional::stream) + .collect(Collectors.toList()); + + System.out.println(result); // Output: [Java, Kotlin] + } +} +``` diff --git a/exercises/concept/tim-from-marketing-2/.docs/introduction.md.tpl b/exercises/concept/tim-from-marketing-2/.docs/introduction.md.tpl index 0fd29ff28..8e69eca80 100644 --- a/exercises/concept/tim-from-marketing-2/.docs/introduction.md.tpl +++ b/exercises/concept/tim-from-marketing-2/.docs/introduction.md.tpl @@ -1,5 +1,5 @@ # Introduction -## Optional +%{concept:optional-types} -%{concept:optional} +%{concept:flatMap-operation} diff --git a/exercises/concept/tim-from-marketing-2/.meta/config.json b/exercises/concept/tim-from-marketing-2/.meta/config.json index 85d83c0a4..4fcb4bcc1 100644 --- a/exercises/concept/tim-from-marketing-2/.meta/config.json +++ b/exercises/concept/tim-from-marketing-2/.meta/config.json @@ -1,13 +1,18 @@ { - "authors": ["josealonso"], + "authors": [ + "josealonso" + ], "files": { "solution": [ - "src/main/java/TimFromMarketing2.java" + "src/main/java/EmployeeService.java" ], "test": [ - "src/test/java/TimFromMarketing2Test.java" + "src/test/java/EmployeeServiceTest.java" + ], + "exemplar": [ + ".meta/src/reference/java/EmployeeService.java" ], - "exemplar": [] }, - "blurb": "" + "icon": "language-list", + "blurb": "Learn to use the Optional class by helping Tim print details of his company employees." } diff --git a/exercises/concept/tim-from-marketing-2/.meta/design.md b/exercises/concept/tim-from-marketing-2/.meta/design.md index e69de29bb..0fa9d791c 100644 --- a/exercises/concept/tim-from-marketing-2/.meta/design.md +++ b/exercises/concept/tim-from-marketing-2/.meta/design.md @@ -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`. +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 fields. +- Know how to use methods that return an Optional type. +- See the utility of some Stream methods, `flatMap` specifically. + +## Out of scope + +- Streams API. + +## Concepts + +This Concepts Exercise's Concepts are: + +- `Optional` 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 From 5c143e1a48d26d415d34a61f51be6678dd9db6ab Mon Sep 17 00:00:00 2001 From: josealonso Date: Sat, 15 Feb 2025 02:11:32 +0100 Subject: [PATCH 04/18] exercises/concept/tim-from-marketing-2/ --- .../.meta/src/reference/java/Employee.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 exercises/concept/tim-from-marketing-2/.meta/src/reference/java/Employee.java diff --git a/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/Employee.java b/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/Employee.java new file mode 100644 index 000000000..f444581c6 --- /dev/null +++ b/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/Employee.java @@ -0,0 +1,40 @@ +import java.util.Objects; +import java.util.Optional; + +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 getNullableId() { + return Optional.ofNullable(id); + } + + public Optional getNullableName() { + return Optional.ofNullable(name); + } + + public Optional getNullableDepartment() { + return Optional.ofNullable(department); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Employee employee = (Employee) o; + return id == employee.id && + Objects.equals(name, employee.name) && Objects.equals(department, employee.department); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, department); + } +} From 26a1397b85b860013ae49c2fee0ba7c1a8ac1153 Mon Sep 17 00:00:00 2001 From: josealonso Date: Sat, 15 Feb 2025 02:16:40 +0100 Subject: [PATCH 05/18] One method definition is now given to the student and more explanations have been added. The tests have been corrected and integrated in an IDE. What remains are the hints.md and the icon files, and the gradle files for this exercise. --- .../concept/tim-from-marketing-2/.classpath | 25 +++++ .../.docs/instructions.md | 28 +++--- .../.docs/introduction.md | 19 +++- .../tim-from-marketing-2/.meta/config.json | 2 +- .../tim-from-marketing-2/.meta/design.md | 12 +-- .../src/reference/java/EmployeeService.java | 72 +++++++-------- .../concept/tim-from-marketing-2/.project | 34 +++++++ .../org.eclipse.buildship.core.prefs | 2 + .../concept/tim-from-marketing-2/build.gradle | 23 +++++ .../src/main/java/Employee.java | 25 ++++- .../src/main/java/EmployeeService.java | 37 ++++---- .../src/test/java/EmployeeServiceTest.java | 92 +++++++------------ 12 files changed, 224 insertions(+), 147 deletions(-) create mode 100644 exercises/concept/tim-from-marketing-2/.classpath create mode 100644 exercises/concept/tim-from-marketing-2/.project create mode 100644 exercises/concept/tim-from-marketing-2/.settings/org.eclipse.buildship.core.prefs diff --git a/exercises/concept/tim-from-marketing-2/.classpath b/exercises/concept/tim-from-marketing-2/.classpath new file mode 100644 index 000000000..2407b744f --- /dev/null +++ b/exercises/concept/tim-from-marketing-2/.classpath @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exercises/concept/tim-from-marketing-2/.docs/instructions.md b/exercises/concept/tim-from-marketing-2/.docs/instructions.md index 35616c8a1..7726171bb 100644 --- a/exercises/concept/tim-from-marketing-2/.docs/instructions.md +++ b/exercises/concept/tim-from-marketing-2/.docs/instructions.md @@ -2,34 +2,30 @@ 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). -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 types. - -Two methods are already implemented: - -- `getAllTheEmployeesById()` returns an Optional> object. Notice this method does NOT receive any parameter. -- `getEmployeeById(id)` returns an Optional object for the given ID, being Employee the following class: +Employees have an ID, a name and a department name, like in the [tim-from-marketing](/exercises/concept/tim-from-marketing) exercise. +Assume that the ID of the first employee is 0, the ID of the second employee is 1, and so on. The three fields of an employee may be empty, that's why they are declared as Optional types. +The class constructor receives a parameter of type List>, which is populated in the tests. ## 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". +Implement the `printAllEmployeesNames()` method to print the names of all the employees, together with their id. If the employee does not exist, print "[id] - No employee found". ```java -"1 - Tim" -"2 - Bill" -"3 - Steve" -"4 - This employee does not exist" -"5 - Charlotte" +" +1 - Tim +2 - Bill +3 - Steve +4 - No employee found +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": +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] - No employee found". You will have to call the method `getEmployeeById(int employeeId)`, which returns an Optional and it's already defined. ```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" ``` diff --git a/exercises/concept/tim-from-marketing-2/.docs/introduction.md b/exercises/concept/tim-from-marketing-2/.docs/introduction.md index c7ccb32ca..a2129d034 100644 --- a/exercises/concept/tim-from-marketing-2/.docs/introduction.md +++ b/exercises/concept/tim-from-marketing-2/.docs/introduction.md @@ -40,6 +40,19 @@ public Optional getEmployee(String name) { } ``` +Other commonly used method is `ifPresentOrElse`, which is used to handle the case where the value is present and the case where the value is empty. + +```java +public Optional getEmployee(String name) { + // Assume that getEmployeeByName returns an Optional + 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 @@ -47,12 +60,14 @@ public Optional getEmployeeAge(String name) { Optional optionalEmployee = getEmployeeByName(name); return getEmployeeByName(name) .map(employee -> employee.getAge()) - .orElse(0); + .orElse("No employee found"); } ``` 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. +The fields of the Employee class have an Optional type. Notice that this is 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) + ## 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: @@ -68,7 +83,7 @@ List> listOfOptionals = Arrays.asList( ```java // Using flatMap to extract present values List result = listOfOptionals.stream() - .flatMap(Optional::stream) + .flatMap(language -> language.stream()) .collect(Collectors.toList()); System.out.println(result); // Output: [Java, Kotlin] diff --git a/exercises/concept/tim-from-marketing-2/.meta/config.json b/exercises/concept/tim-from-marketing-2/.meta/config.json index 4fcb4bcc1..4495e216a 100644 --- a/exercises/concept/tim-from-marketing-2/.meta/config.json +++ b/exercises/concept/tim-from-marketing-2/.meta/config.json @@ -13,6 +13,6 @@ ".meta/src/reference/java/EmployeeService.java" ], }, - "icon": "language-list", + "icon": "nullability", "blurb": "Learn to use the Optional class by helping Tim print details of his company employees." } diff --git a/exercises/concept/tim-from-marketing-2/.meta/design.md b/exercises/concept/tim-from-marketing-2/.meta/design.md index 0fa9d791c..ca2b45011 100644 --- a/exercises/concept/tim-from-marketing-2/.meta/design.md +++ b/exercises/concept/tim-from-marketing-2/.meta/design.md @@ -3,7 +3,7 @@ ## 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`. +We will use the most common methods: `ifPresent`, `orElse`, `ifPresentOrElse`, `orElseThrow`. 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. @@ -35,15 +35,11 @@ This Concept Exercise's prerequisites Concepts are: ## 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. +- `actionable`: If the solution uses `null` in any method, encourage the student to use `Optional` instead. +- `actionable`: If the solution uses the `get` or `isPresent` methods of the Optional API, encourage the student to use `orElse`, `orElseThrow` or `ifPresentOrElse` instead. +- `informative`: TODO. If the solution does not receive any of the above feedback, it must be exemplar. Leave a `celebratory` comment to celebrate the success! diff --git a/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/EmployeeService.java b/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/EmployeeService.java index 78ceaf00a..033d3aa4a 100644 --- a/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/EmployeeService.java +++ b/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/EmployeeService.java @@ -1,66 +1,60 @@ import java.util.List; import java.util.ArrayList; import java.util.Optional; -import java.util.stream.*; - class EmployeeService { - - public List> getAllTheEmployeesById() { - return getAllTheEmployeesById() - .stream() - .map(employee -> Optional.ofNullable(employee)) - .collect(Collectors.toList()); + + // This list is populated in the tests + private List> nullableEmployeesList = new ArrayList<>(); + + public EmployeeService(List> listOfEmployees) { + nullableEmployeesList = listOfEmployees; } public Optional getEmployeeById(int employeeId) { - /* Solution using Streams - - return getAllTheEmployeesById(employeesList).stream() - .filter(employee -> employee.getId() == id) - .orElse("Employee not found"); - */ - - return Optional.ofNullable(getEmployeeById(employeeId)); + return nullableEmployeesList + .stream() + .flatMap(employee -> employee.stream()) + .filter(employee -> employee.getNullableId() + .map(id -> id == employeeId) + .orElse(false)) + .findFirst(); } - public String printAllEmployeesNamesById() { - List> nullableEmployeesList = getAllTheEmployeesById(); + /* I could use IntStream.range(0, nullableEmployeesList.size()) instead of a for loop, but + understanding the Optional API is difficult enough. + I do not use method references for the same reason. */ + public String printAllEmployeesNames() { 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") - ); + .flatMap(employee -> employee.getNullableName()) + .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); + Optional employee = getEmployeeById(employeeId); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(employeeId).append(" - "); + // Handle Optional values 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") + e -> { + e.getNullableName().ifPresent(name -> + e.getNullableDepartment().ifPresent(department -> + stringBuilder.append(name).append(" - ").append(department) + ) + ); + }, + () -> stringBuilder.append("No employee found") ); return stringBuilder.toString(); - } + } } - diff --git a/exercises/concept/tim-from-marketing-2/.project b/exercises/concept/tim-from-marketing-2/.project new file mode 100644 index 000000000..8fe777e8e --- /dev/null +++ b/exercises/concept/tim-from-marketing-2/.project @@ -0,0 +1,34 @@ + + + tim-from-marketing-2 + Project tim-from-marketing-2 created by Buildship. + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature + + + + 1739322795991 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/exercises/concept/tim-from-marketing-2/.settings/org.eclipse.buildship.core.prefs b/exercises/concept/tim-from-marketing-2/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 000000000..81645a672 --- /dev/null +++ b/exercises/concept/tim-from-marketing-2/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,2 @@ +connection.project.dir=../tim-from-marketing +eclipse.preferences.version=1 diff --git a/exercises/concept/tim-from-marketing-2/build.gradle b/exercises/concept/tim-from-marketing-2/build.gradle index e69de29bb..d2eca9ec7 100644 --- a/exercises/concept/tim-from-marketing-2/build.gradle +++ b/exercises/concept/tim-from-marketing-2/build.gradle @@ -0,0 +1,23 @@ +plugins { + id "java" +} + +repositories { + mavenCentral() +} + +dependencies { + testImplementation platform("org.junit:junit-bom:5.10.0") + testImplementation "org.junit.jupiter:junit-jupiter" + testImplementation "org.assertj:assertj-core:3.25.1" +} + +test { + useJUnitPlatform() + + testLogging { + exceptionFormat = "full" + showStandardStreams = true + events = ["passed", "failed", "skipped"] + } +} diff --git a/exercises/concept/tim-from-marketing-2/src/main/java/Employee.java b/exercises/concept/tim-from-marketing-2/src/main/java/Employee.java index 570c161e1..f444581c6 100644 --- a/exercises/concept/tim-from-marketing-2/src/main/java/Employee.java +++ b/exercises/concept/tim-from-marketing-2/src/main/java/Employee.java @@ -1,3 +1,6 @@ +import java.util.Objects; +import java.util.Optional; + class Employee { private final int id; private final String name; @@ -9,15 +12,29 @@ public Employee(int id, String name, String department) { this.department = department; } - public Optional getId() { - return Optional.ofNullable(id); + public Optional getNullableId() { + return Optional.ofNullable(id); } - public Optional getName() { + public Optional getNullableName() { return Optional.ofNullable(name); } - public Optional getDepartment() { + public Optional getNullableDepartment() { return Optional.ofNullable(department); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Employee employee = (Employee) o; + return id == employee.id && + Objects.equals(name, employee.name) && Objects.equals(department, employee.department); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, department); + } } diff --git a/exercises/concept/tim-from-marketing-2/src/main/java/EmployeeService.java b/exercises/concept/tim-from-marketing-2/src/main/java/EmployeeService.java index 3256ffd81..9fc457ed9 100644 --- a/exercises/concept/tim-from-marketing-2/src/main/java/EmployeeService.java +++ b/exercises/concept/tim-from-marketing-2/src/main/java/EmployeeService.java @@ -1,33 +1,34 @@ +import java.util.ArrayList; import java.util.List; import java.util.Optional; -import java.util.stream.*; class EmployeeService { - /* - The getAllTheEmployeesById and getEmployeeById methods are already implemented. - */ + // This list is populated in the tests + private List> nullableEmployeesList = new ArrayList<>(); - // Convert the list of employees to a list of Optional - public List> getAllTheEmployeesById() { - return getAllTheEmployeesById() - .stream() - .map(employee -> Optional.ofNullable(employee)) - .collect(Collectors.toList()); - } - - public Optional getEmployeeById(int id) { - return Optional.ofNullable(getEmployeeById(employeeId)); + public EmployeeService(List> listOfEmployees) { + nullableEmployeesList = listOfEmployees; } + public Optional getEmployeeById(int employeeId) { + return nullableEmployeesList + .stream() + .flatMap(employee -> employee.stream()) + .filter(employee -> employee.getNullableId() + .map(id -> id == employeeId) + .orElse(false)) + .findFirst(); + } - public String printAllEmployeesNamesById() { - throw new UnsupportedOperationException("Please implement the EmployeeService.printAllEmployeesNamesById() method"); + public String printAllEmployeesNames() { + throw new UnsupportedOperationException( + "Please implement the EmployeeService.printAllEmployeesNames() method"); } public String printEmployeeNameAndDepartmentById(int employeeId) { - throw new UnsupportedOperationException("Please implement the EmployeeService.printEmployeeNameAndDepartmentById(id) method"); + throw new UnsupportedOperationException( + "Please implement the EmployeeService.printEmployeeNameAndDepartmentById(id) method"); } - } diff --git a/exercises/concept/tim-from-marketing-2/src/test/java/EmployeeServiceTest.java b/exercises/concept/tim-from-marketing-2/src/test/java/EmployeeServiceTest.java index ba5e16a35..3abb34ab5 100644 --- a/exercises/concept/tim-from-marketing-2/src/test/java/EmployeeServiceTest.java +++ b/exercises/concept/tim-from-marketing-2/src/test/java/EmployeeServiceTest.java @@ -3,99 +3,73 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.TestInstance.LifecycleManagement; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.api.TestInstance.Lifecycle; import static org.assertj.core.api.Assertions.assertThat; import java.util.List; import java.util.ArrayList; import java.util.Optional; -import java.util.stream.*; // Annotation in order to use @BeforeAll in a non-static method -@TestInstance(LifecycleManagement.Lifecycle.PER_CLASS) +@TestInstance(Lifecycle.PER_CLASS) public class EmployeeServiceTest { private EmployeeService employeeService; - private List listOfEmployees = new ArrayList<>(); + private List> listOfEmployees = new ArrayList<>(); private List expectedListOfPrintedEmployeesNames = List.of( - "0 - Tim", - "1 - Mark", - "2 - John", - "3 - No employee found", - "4 - Jane" + "0 - Tim\n" + "1 - No employee found\n" + "2 - John\n" ); private List expectedListOfPrintedEmployeesNamesAndDepartments = List.of( - "0 - Tim - Direction", - "1 - Mark - Sales", - "2 - John - Engineering", - "3 - No employee found", - "4 - Jane - Sales" + "0 - Tim - Direction", + "1 - No employee found", + "2 - John - Engineering" ); - public List getAllTheEmployeesById() { - return listOfEmployees; - } - - public Employee getEmployeeById(int employeeId) { - return listOfEmployees.get(employeeId); - } - void initList() { - listOfEmployees.add(new Employee(0, "Tim", "Direction")); - listOfEmployees.add(new Employee(1, "Mark", "Sales")); - listOfEmployees.add(new Employee(2, "John", "Engineering")); - listOfEmployees.add(null); // Adding a null element - listOfEmployees.add(new Employee(4, "Jane", "Sales")); + listOfEmployees.add(Optional.of(new Employee(0, "Tim", "Direction"))); + listOfEmployees.add(Optional.empty()); // Adding empty value. + listOfEmployees.add(Optional.of(new Employee(2, "John", "Engineering"))); } @BeforeAll void setup() { initList(); - employeeService = new EmployeeService(); + employeeService = new EmployeeService(listOfEmployees); } @Test @Tag("task:1") @DisplayName("Printing all the employees names") void printAllTheEmployeesNames_includingNonExistentOnes() { - assertThat(employeeService.printAllEmployeesNamesById()) - .isEqualTo(expectedListOfPrintedEmployeesNames); + String allEmployeesNames = String.join(", ", expectedListOfPrintedEmployeesNames); + assertThat(employeeService.printAllEmployeesNames()) + .isEqualTo(allEmployeesNames); } - @ParameterizedTest - // @Tag("task:2") - @MethodSource("employeeTestData") - @DisplayName("Printing the details of an employee with various IDs") - void givenAnId_printAnEmployeeNameAndDepartment(int id, String expected) { + @Test + @Tag("task:2") + @DisplayName("Printing the details of the first employee") + void givenTheFirstId_printAnEmployeeNameAndDepartment() { + assertThat(employeeService.printEmployeeNameAndDepartmentById(0)) + .isEqualTo(expectedListOfPrintedEmployeesNamesAndDepartments.get(0)); + } + + @Test + @Tag("task:3") + @DisplayName("Printing the details of a non-existent employee") + void givenAnIdWithNoEmployee_printAnEmployeeNameAndDepartment() { assertThat(employeeService.printEmployeeNameAndDepartmentById(1)) .isEqualTo(expectedListOfPrintedEmployeesNamesAndDepartments.get(1)); } - static Stream employeeTestData() { - return Stream.of( - Arguments.of(0), expectedListOfPrintedEmployeesNamesAndDepartments.get(0), - Arguments.of(1), expectedListOfPrintedEmployeesNamesAndDepartments.get(1), - Arguments.of(2), expectedListOfPrintedEmployeesNamesAndDepartments.get(2), - Arguments.of(3), expectedListOfPrintedEmployeesNamesAndDepartments.get(3), - Arguments.of(4), expectedListOfPrintedEmployeesNamesAndDepartments.get(4) - ); + @Test + @Tag("task:4") + @DisplayName("Printing the details of the last employee") + void givenTheLastId_printAnEmployeeNameAndDepartment() { + int lastEmployeeId = listOfEmployees.size() - 1; + assertThat(employeeService.printEmployeeNameAndDepartmentById(lastEmployeeId)) + .isEqualTo(expectedListOfPrintedEmployeesNamesAndDepartments.get(lastEmployeeId)); } - - - - - - - - - - - - - } From f94749a68db6af88755dc8dc1ff0f795d09f572d Mon Sep 17 00:00:00 2001 From: josealonso Date: Mon, 24 Feb 2025 00:22:34 +0100 Subject: [PATCH 06/18] Use @BeforeEach instead of @BeforeAll. --- exercises/concept/tim-from-marketing-2/.classpath | 7 +++++++ exercises/concept/tim-from-marketing-2/.docs/hints.md | 4 +--- .../concept/tim-from-marketing-2/.docs/instructions.md | 2 -- .../concept/tim-from-marketing-2/.docs/introduction.md | 4 +--- exercises/concept/tim-from-marketing-2/.meta/design.md | 3 +-- .../src/test/java/EmployeeServiceTest.java | 7 +++---- 6 files changed, 13 insertions(+), 14 deletions(-) diff --git a/exercises/concept/tim-from-marketing-2/.classpath b/exercises/concept/tim-from-marketing-2/.classpath index 2407b744f..d70073d0d 100644 --- a/exercises/concept/tim-from-marketing-2/.classpath +++ b/exercises/concept/tim-from-marketing-2/.classpath @@ -19,6 +19,13 @@ + + + + + + + diff --git a/exercises/concept/tim-from-marketing-2/.docs/hints.md b/exercises/concept/tim-from-marketing-2/.docs/hints.md index 25cbe29ce..ada7c9280 100644 --- a/exercises/concept/tim-from-marketing-2/.docs/hints.md +++ b/exercises/concept/tim-from-marketing-2/.docs/hints.md @@ -1,11 +1,9 @@ # Hints -## 1.- Print the name of all the employees +## 1.- Print the name of all the employees WIP ## 2.- Print the name and department of a given employee - WIP - diff --git a/exercises/concept/tim-from-marketing-2/.docs/instructions.md b/exercises/concept/tim-from-marketing-2/.docs/instructions.md index 7726171bb..dd08793bd 100644 --- a/exercises/concept/tim-from-marketing-2/.docs/instructions.md +++ b/exercises/concept/tim-from-marketing-2/.docs/instructions.md @@ -1,13 +1,11 @@ # 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 the [tim-from-marketing](/exercises/concept/tim-from-marketing) exercise. Assume that the ID of the first employee is 0, the ID of the second employee is 1, and so on. The three fields of an employee may be empty, that's why they are declared as Optional types. The class constructor receives a parameter of type List>, which is populated in the tests. ## 1.- Print the names of all the employees - Implement the `printAllEmployeesNames()` method to print the names of all the employees, together with their id. If the employee does not exist, print "[id] - No employee found". ```java diff --git a/exercises/concept/tim-from-marketing-2/.docs/introduction.md b/exercises/concept/tim-from-marketing-2/.docs/introduction.md index a2129d034..a720139f6 100644 --- a/exercises/concept/tim-from-marketing-2/.docs/introduction.md +++ b/exercises/concept/tim-from-marketing-2/.docs/introduction.md @@ -5,7 +5,6 @@ ## Introduction The **Optional** 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. - Before Java 8, developers had to implement null checks: ```java @@ -65,7 +64,6 @@ public Optional getEmployeeAge(String name) { ``` 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. - The fields of the Employee class have an Optional type. Notice that this is 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) ## Flatmap operation @@ -89,4 +87,4 @@ List> listOfOptionals = Arrays.asList( System.out.println(result); // Output: [Java, Kotlin] } } -``` +``` \ No newline at end of file diff --git a/exercises/concept/tim-from-marketing-2/.meta/design.md b/exercises/concept/tim-from-marketing-2/.meta/design.md index ca2b45011..39f6f2b9a 100644 --- a/exercises/concept/tim-from-marketing-2/.meta/design.md +++ b/exercises/concept/tim-from-marketing-2/.meta/design.md @@ -5,7 +5,6 @@ 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`, `orElseThrow`. 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 @@ -30,13 +29,13 @@ This Concepts Exercise's Concepts are: This Concept Exercise's prerequisites Concepts are: - `custom classes`. +- `lists`. - `generic-types`. - `streams`. ## Analyzer This exercise could benefit from the following rules in the [analyzer]: - - `actionable`: If the solution uses `null` in any method, encourage the student to use `Optional` instead. - `actionable`: If the solution uses the `get` or `isPresent` methods of the Optional API, encourage the student to use `orElse`, `orElseThrow` or `ifPresentOrElse` instead. - `informative`: TODO. diff --git a/exercises/concept/tim-from-marketing-2/src/test/java/EmployeeServiceTest.java b/exercises/concept/tim-from-marketing-2/src/test/java/EmployeeServiceTest.java index 3abb34ab5..bd3155c65 100644 --- a/exercises/concept/tim-from-marketing-2/src/test/java/EmployeeServiceTest.java +++ b/exercises/concept/tim-from-marketing-2/src/test/java/EmployeeServiceTest.java @@ -1,6 +1,6 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; @@ -10,8 +10,7 @@ import java.util.ArrayList; import java.util.Optional; -// Annotation in order to use @BeforeAll in a non-static method -@TestInstance(Lifecycle.PER_CLASS) +// @TestInstance(Lifecycle.PER_CLASS) public class EmployeeServiceTest { private EmployeeService employeeService; @@ -33,7 +32,7 @@ void initList() { listOfEmployees.add(Optional.of(new Employee(2, "John", "Engineering"))); } - @BeforeAll + @BeforeEach void setup() { initList(); employeeService = new EmployeeService(listOfEmployees); From 4c5e540cfb3210b295ac73fb92428808648ac16a Mon Sep 17 00:00:00 2001 From: josealonso Date: Mon, 24 Feb 2025 03:19:19 +0100 Subject: [PATCH 07/18] Simplify the solution and the asked methods so streams are not used. --- .../concept/tim-from-marketing-2/.classpath | 2 +- .../.docs/instructions.md | 1 + .../.docs/introduction.md.tpl | 2 - .../tim-from-marketing-2/.meta/design.md | 3 +- .../.meta/src/reference/java/Employee.java | 20 +-- .../src/reference/java/EmployeeService.java | 60 --------- .../.meta/src/reference/java/Main.java | 115 ++++++++++++++++++ 7 files changed, 129 insertions(+), 74 deletions(-) delete mode 100644 exercises/concept/tim-from-marketing-2/.meta/src/reference/java/EmployeeService.java create mode 100644 exercises/concept/tim-from-marketing-2/.meta/src/reference/java/Main.java diff --git a/exercises/concept/tim-from-marketing-2/.classpath b/exercises/concept/tim-from-marketing-2/.classpath index d70073d0d..bdf6fa2e8 100644 --- a/exercises/concept/tim-from-marketing-2/.classpath +++ b/exercises/concept/tim-from-marketing-2/.classpath @@ -28,5 +28,5 @@ - + diff --git a/exercises/concept/tim-from-marketing-2/.docs/instructions.md b/exercises/concept/tim-from-marketing-2/.docs/instructions.md index dd08793bd..476db688e 100644 --- a/exercises/concept/tim-from-marketing-2/.docs/instructions.md +++ b/exercises/concept/tim-from-marketing-2/.docs/instructions.md @@ -6,6 +6,7 @@ Assume that the ID of the first employee is 0, the ID of the second employee is The class constructor receives a parameter of type List>, which is populated in the tests. ## 1.- Print the names of all the employees + Implement the `printAllEmployeesNames()` method to print the names of all the employees, together with their id. If the employee does not exist, print "[id] - No employee found". ```java diff --git a/exercises/concept/tim-from-marketing-2/.docs/introduction.md.tpl b/exercises/concept/tim-from-marketing-2/.docs/introduction.md.tpl index 8e69eca80..fab1aaf25 100644 --- a/exercises/concept/tim-from-marketing-2/.docs/introduction.md.tpl +++ b/exercises/concept/tim-from-marketing-2/.docs/introduction.md.tpl @@ -1,5 +1,3 @@ # Introduction %{concept:optional-types} - -%{concept:flatMap-operation} diff --git a/exercises/concept/tim-from-marketing-2/.meta/design.md b/exercises/concept/tim-from-marketing-2/.meta/design.md index 39f6f2b9a..82a24b502 100644 --- a/exercises/concept/tim-from-marketing-2/.meta/design.md +++ b/exercises/concept/tim-from-marketing-2/.meta/design.md @@ -31,12 +31,11 @@ This Concept Exercise's prerequisites Concepts are: - `custom classes`. - `lists`. - `generic-types`. -- `streams`. ## Analyzer This exercise could benefit from the following rules in the [analyzer]: -- `actionable`: If the solution uses `null` in any method, encourage the student to use `Optional` instead. +- `essential`: If the solution uses `null` in any method, encourage the student to use `Optional` instead. - `actionable`: If the solution uses the `get` or `isPresent` methods of the Optional API, encourage the student to use `orElse`, `orElseThrow` or `ifPresentOrElse` instead. - `informative`: TODO. diff --git a/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/Employee.java b/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/Employee.java index f444581c6..d9f7702e3 100644 --- a/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/Employee.java +++ b/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/Employee.java @@ -2,7 +2,7 @@ import java.util.Optional; class Employee { - private final int id; + private final int id; private final String name; private final String department; @@ -12,22 +12,24 @@ public Employee(int id, String name, String department) { this.department = department; } - public Optional getNullableId() { - return Optional.ofNullable(id); + public int getId() { + return id; } - public Optional getNullableName() { - return Optional.ofNullable(name); + public String getName() { + return name; } - public Optional getNullableDepartment() { - return Optional.ofNullable(department); + public String getDepartment() { + return department; } @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; Employee employee = (Employee) o; return id == employee.id && Objects.equals(name, employee.name) && Objects.equals(department, employee.department); diff --git a/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/EmployeeService.java b/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/EmployeeService.java deleted file mode 100644 index 033d3aa4a..000000000 --- a/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/EmployeeService.java +++ /dev/null @@ -1,60 +0,0 @@ -import java.util.List; -import java.util.ArrayList; -import java.util.Optional; - -class EmployeeService { - - // This list is populated in the tests - private List> nullableEmployeesList = new ArrayList<>(); - - public EmployeeService(List> listOfEmployees) { - nullableEmployeesList = listOfEmployees; - } - - public Optional getEmployeeById(int employeeId) { - return nullableEmployeesList - .stream() - .flatMap(employee -> employee.stream()) - .filter(employee -> employee.getNullableId() - .map(id -> id == employeeId) - .orElse(false)) - .findFirst(); - } - - /* I could use IntStream.range(0, nullableEmployeesList.size()) instead of a for loop, but - understanding the Optional API is difficult enough. - I do not use method references for the same reason. */ - public String printAllEmployeesNames() { - StringBuilder stringBuilder = new StringBuilder(); - for (int i = 0; i < nullableEmployeesList.size(); i++) { - stringBuilder.append(i).append(" - "); - - nullableEmployeesList.get(i) - .flatMap(employee -> employee.getNullableName()) - .ifPresentOrElse( - name -> stringBuilder.append(name).append("\n"), - () -> stringBuilder.append("No employee found\n") - ); - } - return stringBuilder.toString(); - } - - public String printEmployeeNameAndDepartmentById(int employeeId) { - Optional employee = getEmployeeById(employeeId); - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append(employeeId).append(" - "); - // Handle Optional values - employee.ifPresentOrElse( - e -> { - e.getNullableName().ifPresent(name -> - e.getNullableDepartment().ifPresent(department -> - stringBuilder.append(name).append(" - ").append(department) - ) - ); - }, - () -> stringBuilder.append("No employee found") - ); - return stringBuilder.toString(); - } - -} diff --git a/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/Main.java b/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/Main.java new file mode 100644 index 000000000..eea9dfbee --- /dev/null +++ b/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/Main.java @@ -0,0 +1,115 @@ +import java.util.List; +import java.util.ArrayList; +import java.util.Optional; +import java.util.Objects; + +public class Main { + public static void main(String[] args) { + List employees = new ArrayList<>(); + employees.add(new Employee(1, "Tim", "Marketing")); + employees.add(new Employee(2, "Joe", "Sales")); + employees.add(new Employee(3, "Jane", "IT")); + employees.add(new Employee(4, null, null)); + + StringBuilder stringBuilder = new StringBuilder(); + EmployeeService employeeService = new EmployeeService(employees); + + int employeeId = 1; + Optional employee = employeeService.getEmployeeById(employeeId); + employee.ifPresentOrElse( + employee1 -> { + Optional.ofNullable(employee1.getName()) + .ifPresentOrElse(name -> System.out.println(stringBuilder.append(employeeId).append(" - ") + .append(employee1.getName()).append(" - ") + .append(employee1.getDepartment()).toString()), () -> { + throw new RuntimeException("No employee found for id: " + employeeId); + }); + }, + () -> { + throw new RuntimeException("No employee found for id: " + employeeId); + }); + } + +} + +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 int getId() { + return id; + } + + public String getName() { + return name; + } + + public String getDepartment() { + return department; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + Employee employee = (Employee) o; + return id == employee.id && Objects.equals(name, employee.name) + && Objects.equals(department, employee.department); + } + + @Override + public String toString() { + return "Employee{" + + "id=" + id + + ", name='" + name + '\'' + + ", department='" + department + '\'' + + '}'; + } + +} + +class EmployeeService { + + // This list is populated in the tests + private List employeesList = new ArrayList<>(); + + public EmployeeService(List listOfEmployees) { + employeesList = listOfEmployees; + } + + public Optional getEmployeeById(int employeeId) { + for (Employee employee : employeesList) { + if (Objects.equals(employee.getId(), employeeId)) { + return Optional.of(employee); + } + } + return Optional.empty(); + } + + /* + * public String printEmployeeNameAndDepartmentById(int employeeId) { + * Optional employee = getEmployeeById(employeeId); + * StringBuilder stringBuilder = new StringBuilder(); + * stringBuilder.append(employeeId).append(" - "); + * // Handle Optional values + * employee.ifPresentOrElse( + * e -> { + * e.getNullableName().ifPresent(name -> e.getNullableDepartment() + * .ifPresent(department -> + * stringBuilder.append(name).append(" - ").append(department))); + * }, + * () -> stringBuilder.append("No employee found")); + * return stringBuilder.toString(); + * } + */ + +} From 4b7a80037a821c3b86ad5eb9ec74d174531fdb6f Mon Sep 17 00:00:00 2001 From: josealonso Date: Tue, 25 Feb 2025 02:09:31 +0100 Subject: [PATCH 08/18] Modify many files, including Hints.md, to adapt them to the new exercise without streams. --- .../tim-from-marketing-2/.docs/hints.md | 9 +- .../.docs/instructions.md | 34 +++--- .../.meta/src/reference/java/Employee.java | 15 ++- .../src/reference/java/EmployeeService.java | 47 +++++++ .../.meta/src/reference/java/Main.java | 115 ------------------ .../src/main/java/Employee.java | 22 ++-- .../src/main/java/EmployeeService.java | 53 +++++--- .../src/test/java/EmployeeServiceTest.java | 80 ++++++------ 8 files changed, 174 insertions(+), 201 deletions(-) create mode 100644 exercises/concept/tim-from-marketing-2/.meta/src/reference/java/EmployeeService.java delete mode 100644 exercises/concept/tim-from-marketing-2/.meta/src/reference/java/Main.java diff --git a/exercises/concept/tim-from-marketing-2/.docs/hints.md b/exercises/concept/tim-from-marketing-2/.docs/hints.md index ada7c9280..72eec5bbe 100644 --- a/exercises/concept/tim-from-marketing-2/.docs/hints.md +++ b/exercises/concept/tim-from-marketing-2/.docs/hints.md @@ -1,9 +1,10 @@ # Hints +## 1. Get an employee by ID -## 1.- Print the name of all the employees -WIP +- This method returns an `Optional` object, not an `Employee` one. +## 2. Return the name and department of a given employee in a certain format -## 2.- Print the name and department of a given employee -WIP +- You can call the method `getEmployeeById(int)` to get the employee. +- Remember the syntax of the `ifPresentOrElse()` method. \ No newline at end of file diff --git a/exercises/concept/tim-from-marketing-2/.docs/instructions.md b/exercises/concept/tim-from-marketing-2/.docs/instructions.md index 476db688e..a39767bd6 100644 --- a/exercises/concept/tim-from-marketing-2/.docs/instructions.md +++ b/exercises/concept/tim-from-marketing-2/.docs/instructions.md @@ -1,30 +1,24 @@ # Instructions -In this exercise you will be writing code to print all the names of the factory employees. +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. -Assume that the ID of the first employee is 0, the ID of the second employee is 1, and so on. The three fields of an employee may be empty, that's why they are declared as Optional types. -The class constructor receives a parameter of type List>, which is populated in the tests. +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, which is populated in the tests. +You will be writing two methods: `getEmployeeById(int)` and `getEmployeeDetailsById(int)`. -## 1.- Print the names of all the employees +## 1. Get an employee by ID -Implement the `printAllEmployeesNames()` method to print the names of all the employees, together with their id. If the employee does not exist, print "[id] - No employee found". +Implement the `getEmployeeById(int)` method so that it returns an Optional object. +If the employee does not exist, returns an empty Optional instance. -```java -" -1 - Tim -2 - Bill -3 - Steve -4 - No employee found -5 - Charlotte -" -``` +## 2. Return the name and department of a given employee in a certain format -## 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] - No employee found". You will have to call the method `getEmployeeById(int employeeId)`, which returns an Optional and it's already defined. +Implement the `getEmployeeDetailsById(int)` method to return a string containing the id, the name and +the department of a given employee: ```java -printEmployeeNameAndDepartmentById(1) => "1 - Tim - Marketing" -printEmployeeNameAndDepartmentById(3) => "3 - Steve - Engineering" -printEmployeeNameAndDepartmentById(4) => "4 - This employee does not exist" +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]`. \ No newline at end of file diff --git a/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/Employee.java b/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/Employee.java index d9f7702e3..98d897e1a 100644 --- a/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/Employee.java +++ b/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/Employee.java @@ -31,12 +31,23 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; Employee employee = (Employee) o; - return id == employee.id && - Objects.equals(name, employee.name) && Objects.equals(department, employee.department); + return id == employee.id && Objects.equals(name, employee.name) + && Objects.equals(department, employee.department); } @Override public int hashCode() { return Objects.hash(id, name, department); } + + @Override + public String toString() { + return "Employee{" + + "id=" + id + + ", name='" + name + '\'' + + ", department='" + department + '\'' + + '}'; + } + } + diff --git a/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/EmployeeService.java b/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/EmployeeService.java new file mode 100644 index 000000000..9029ccbc5 --- /dev/null +++ b/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/EmployeeService.java @@ -0,0 +1,47 @@ +import java.util.List; +import java.util.Optional; +import java.util.Objects; + +class EmployeeService { + + // This list is populated in the tests + private List employeesList; + + public EmployeeService(List listOfEmployees) { + employeesList = listOfEmployees; + } + + public Optional getEmployeeById(int employeeId) { + for (Employee employee : employeesList) { + if (Objects.equals(employee.getId(), employeeId)) { + return Optional.of(employee); + } + } + return Optional.empty(); + } + + public String getEmployeeDetailsById(int employeeId) { + Optional nullableEmployee = getEmployeeById(employeeId); + StringBuilder stringBuilder = new StringBuilder(); + nullableEmployee.ifPresentOrElse( + employee1 -> { + Optional.ofNullable(employee1.getName()) + .ifPresentOrElse(name -> stringBuilder.append(employeeId).append(" - ") + .append(employee1.getName()).append(" - ") + .append(employee1.getDepartment()), + () -> { + stringBuilder.append("No employee found for id: ").append(employeeId); + }); + }, + () -> { + stringBuilder.append("That id does not exist: ").append(employeeId); + }); + return stringBuilder.toString(); + } +} + + + + + + diff --git a/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/Main.java b/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/Main.java deleted file mode 100644 index eea9dfbee..000000000 --- a/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/Main.java +++ /dev/null @@ -1,115 +0,0 @@ -import java.util.List; -import java.util.ArrayList; -import java.util.Optional; -import java.util.Objects; - -public class Main { - public static void main(String[] args) { - List employees = new ArrayList<>(); - employees.add(new Employee(1, "Tim", "Marketing")); - employees.add(new Employee(2, "Joe", "Sales")); - employees.add(new Employee(3, "Jane", "IT")); - employees.add(new Employee(4, null, null)); - - StringBuilder stringBuilder = new StringBuilder(); - EmployeeService employeeService = new EmployeeService(employees); - - int employeeId = 1; - Optional employee = employeeService.getEmployeeById(employeeId); - employee.ifPresentOrElse( - employee1 -> { - Optional.ofNullable(employee1.getName()) - .ifPresentOrElse(name -> System.out.println(stringBuilder.append(employeeId).append(" - ") - .append(employee1.getName()).append(" - ") - .append(employee1.getDepartment()).toString()), () -> { - throw new RuntimeException("No employee found for id: " + employeeId); - }); - }, - () -> { - throw new RuntimeException("No employee found for id: " + employeeId); - }); - } - -} - -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 int getId() { - return id; - } - - public String getName() { - return name; - } - - public String getDepartment() { - return department; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - Employee employee = (Employee) o; - return id == employee.id && Objects.equals(name, employee.name) - && Objects.equals(department, employee.department); - } - - @Override - public String toString() { - return "Employee{" + - "id=" + id + - ", name='" + name + '\'' + - ", department='" + department + '\'' + - '}'; - } - -} - -class EmployeeService { - - // This list is populated in the tests - private List employeesList = new ArrayList<>(); - - public EmployeeService(List listOfEmployees) { - employeesList = listOfEmployees; - } - - public Optional getEmployeeById(int employeeId) { - for (Employee employee : employeesList) { - if (Objects.equals(employee.getId(), employeeId)) { - return Optional.of(employee); - } - } - return Optional.empty(); - } - - /* - * public String printEmployeeNameAndDepartmentById(int employeeId) { - * Optional employee = getEmployeeById(employeeId); - * StringBuilder stringBuilder = new StringBuilder(); - * stringBuilder.append(employeeId).append(" - "); - * // Handle Optional values - * employee.ifPresentOrElse( - * e -> { - * e.getNullableName().ifPresent(name -> e.getNullableDepartment() - * .ifPresent(department -> - * stringBuilder.append(name).append(" - ").append(department))); - * }, - * () -> stringBuilder.append("No employee found")); - * return stringBuilder.toString(); - * } - */ - -} diff --git a/exercises/concept/tim-from-marketing-2/src/main/java/Employee.java b/exercises/concept/tim-from-marketing-2/src/main/java/Employee.java index f444581c6..69fc246f6 100644 --- a/exercises/concept/tim-from-marketing-2/src/main/java/Employee.java +++ b/exercises/concept/tim-from-marketing-2/src/main/java/Employee.java @@ -1,5 +1,4 @@ import java.util.Objects; -import java.util.Optional; class Employee { private final int id; @@ -12,16 +11,16 @@ public Employee(int id, String name, String department) { this.department = department; } - public Optional getNullableId() { - return Optional.ofNullable(id); + public int getId() { + return id; } - public Optional getNullableName() { - return Optional.ofNullable(name); + public String getName() { + return name; } - public Optional getNullableDepartment() { - return Optional.ofNullable(department); + public String getDepartment() { + return department; } @Override @@ -37,4 +36,13 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(id, name, department); } + + @Override + public String toString() { + return "Employee{" + + "id=" + id + + ", name='" + name + '\'' + + ", department='" + department + '\'' + + '}'; + } } diff --git a/exercises/concept/tim-from-marketing-2/src/main/java/EmployeeService.java b/exercises/concept/tim-from-marketing-2/src/main/java/EmployeeService.java index 9fc457ed9..090f4db87 100644 --- a/exercises/concept/tim-from-marketing-2/src/main/java/EmployeeService.java +++ b/exercises/concept/tim-from-marketing-2/src/main/java/EmployeeService.java @@ -1,34 +1,51 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Optional; class EmployeeService { - // This list is populated in the tests - private List> nullableEmployeesList = new ArrayList<>(); + private List employeesList; - public EmployeeService(List> listOfEmployees) { - nullableEmployeesList = listOfEmployees; + public EmployeeService(List listOfEmployees) { + // This list is populated in the tests + employeesList = listOfEmployees; } public Optional getEmployeeById(int employeeId) { - return nullableEmployeesList - .stream() - .flatMap(employee -> employee.stream()) - .filter(employee -> employee.getNullableId() - .map(id -> id == employeeId) - .orElse(false)) - .findFirst(); + for (Employee employee : employeesList) { + if (Objects.equals(employee.getId(), employeeId)) { + return Optional.of(employee); + } + } + return Optional.empty(); } - public String printAllEmployeesNames() { - throw new UnsupportedOperationException( - "Please implement the EmployeeService.printAllEmployeesNames() method"); + public String getEmployeeDetailsById(int employeeId) { + Optional nullableEmployee = getEmployeeById(employeeId); + StringBuilder stringBuilder = new StringBuilder(); + nullableEmployee.ifPresentOrElse( + employee1 -> { + Optional.ofNullable(employee1.getName()) + .ifPresentOrElse(name -> stringBuilder.append(employeeId).append(" - ") + .append(employee1.getName()).append(" - ") + .append(employee1.getDepartment()), + () -> { + stringBuilder.append("No employee found for id: ").append(employeeId); + }); + }, + () -> { + stringBuilder.append("No employee found for id: ").append(employeeId); + }); + return stringBuilder.toString(); } - public String printEmployeeNameAndDepartmentById(int employeeId) { - throw new UnsupportedOperationException( - "Please implement the EmployeeService.printEmployeeNameAndDepartmentById(id) method"); - } +// public Optional getEmployeeById(int employeeId) { +// throw new UnsupportedOperationException("Please implement this method"); +// } +// +// public String getEmployeeDetailsById(int employeeId) { +// throw new UnsupportedOperationException("Please implement this method"); +// } } diff --git a/exercises/concept/tim-from-marketing-2/src/test/java/EmployeeServiceTest.java b/exercises/concept/tim-from-marketing-2/src/test/java/EmployeeServiceTest.java index bd3155c65..1c9533f6b 100644 --- a/exercises/concept/tim-from-marketing-2/src/test/java/EmployeeServiceTest.java +++ b/exercises/concept/tim-from-marketing-2/src/test/java/EmployeeServiceTest.java @@ -2,34 +2,22 @@ import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.TestInstance.Lifecycle; import static org.assertj.core.api.Assertions.assertThat; import java.util.List; import java.util.ArrayList; import java.util.Optional; -// @TestInstance(Lifecycle.PER_CLASS) public class EmployeeServiceTest { private EmployeeService employeeService; - private List> listOfEmployees = new ArrayList<>(); - - private List expectedListOfPrintedEmployeesNames = List.of( - "0 - Tim\n" + "1 - No employee found\n" + "2 - John\n" - ); - - private List expectedListOfPrintedEmployeesNamesAndDepartments = List.of( - "0 - Tim - Direction", - "1 - No employee found", - "2 - John - Engineering" - ); + private List listOfEmployees = new ArrayList<>(); void initList() { - listOfEmployees.add(Optional.of(new Employee(0, "Tim", "Direction"))); - listOfEmployees.add(Optional.empty()); // Adding empty value. - listOfEmployees.add(Optional.of(new Employee(2, "John", "Engineering"))); + listOfEmployees.add(new Employee(1, "Tim", "Marketing")); + listOfEmployees.add(new Employee(2, "Joe", "Sales")); + listOfEmployees.add(new Employee(3, "Jane", "IT")); + listOfEmployees.add(new Employee(4, null, null)); } @BeforeEach @@ -40,35 +28,57 @@ void setup() { @Test @Tag("task:1") - @DisplayName("Printing all the employees names") - void printAllTheEmployeesNames_includingNonExistentOnes() { - String allEmployeesNames = String.join(", ", expectedListOfPrintedEmployeesNames); - assertThat(employeeService.printAllEmployeesNames()) - .isEqualTo(allEmployeesNames); + @DisplayName("Retrieve one employee by id") + void retrieveOneEmployeeById() { + assertThat(employeeService.getEmployeeById(2)) + .isEqualTo(Optional.of(listOfEmployees.get(1))); } @Test @Tag("task:2") - @DisplayName("Printing the details of the first employee") - void givenTheFirstId_printAnEmployeeNameAndDepartment() { - assertThat(employeeService.printEmployeeNameAndDepartmentById(0)) - .isEqualTo(expectedListOfPrintedEmployeesNamesAndDepartments.get(0)); + @DisplayName("Retrieve other employee by id") + void retrieveOtherEmployeeById() { + assertThat(employeeService.getEmployeeById(3)) + .isEqualTo(Optional.of(listOfEmployees.get(2))); } @Test @Tag("task:3") - @DisplayName("Printing the details of a non-existent employee") - void givenAnIdWithNoEmployee_printAnEmployeeNameAndDepartment() { - assertThat(employeeService.printEmployeeNameAndDepartmentById(1)) - .isEqualTo(expectedListOfPrintedEmployeesNamesAndDepartments.get(1)); + @DisplayName("Retrieve employee by id when some fields are null") + void retrieveEmployeeById_withNullFields() { + assertThat(employeeService.getEmployeeById(4)) + .isEqualTo(Optional.of(listOfEmployees.get(3))); } @Test @Tag("task:4") - @DisplayName("Printing the details of the last employee") - void givenTheLastId_printAnEmployeeNameAndDepartment() { - int lastEmployeeId = listOfEmployees.size() - 1; - assertThat(employeeService.printEmployeeNameAndDepartmentById(lastEmployeeId)) - .isEqualTo(expectedListOfPrintedEmployeesNamesAndDepartments.get(lastEmployeeId)); + @DisplayName("Retrieve employee by id when the id does not exist") + void retrieveEmployeeById_forANonExistingId() { + assertThat(employeeService.getEmployeeById(7)) + .isEqualTo(Optional.empty()); + } + + @Test + @Tag("task:5") + @DisplayName("Retrieve employee details by id") + void retrieveEmployeeDetailsById() { + assertThat(employeeService.getEmployeeDetailsById(2)) + .isEqualTo("2 - Joe - Sales"); + } + + @Test + @Tag("task:6") + @DisplayName("Retrieve employee details by id when some fields are null") + void retrieveEmployeeDetailsById_withNullFields() { + assertThat(employeeService.getEmployeeDetailsById(4)) + .isEqualTo("No employee found for id: 4"); + } + + @Test + @Tag("task:7") + @DisplayName("Retrieve employee details by id when the id does not exist") + void retrieveEmployeeDetailsById_forANonExistingId() { + assertThat(employeeService.getEmployeeDetailsById(7)) + .isEqualTo("No employee found for id: 7"); } } From 9d4cd3988489ce8034b3cbbc8e9e4bac213088be Mon Sep 17 00:00:00 2001 From: josealonso Date: Tue, 25 Feb 2025 02:16:05 +0100 Subject: [PATCH 09/18] Delete explanation about flatMap. --- .../.docs/introduction.md | 29 +++---------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/exercises/concept/tim-from-marketing-2/.docs/introduction.md b/exercises/concept/tim-from-marketing-2/.docs/introduction.md index a720139f6..4326002c5 100644 --- a/exercises/concept/tim-from-marketing-2/.docs/introduction.md +++ b/exercises/concept/tim-from-marketing-2/.docs/introduction.md @@ -63,28 +63,7 @@ public Optional getEmployeeAge(String name) { } ``` -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. -The fields of the Employee class have an Optional type. Notice that this is 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) - -## 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> listOfOptionals = Arrays.asList( - Optional.of("Java"), - Optional.empty(), - Optional.of("Kotlin") -); -``` - -```java -// Using flatMap to extract present values - List result = listOfOptionals.stream() - .flatMap(language -> language.stream()) - .collect(Collectors.toList()); - - System.out.println(result); // Output: [Java, Kotlin] - } -} -``` \ No newline at end of file +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. +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) \ No newline at end of file From 6db542e0d0ef641bd4a92e4c44117b5e62be2a91 Mon Sep 17 00:00:00 2001 From: josealonso Date: Tue, 25 Feb 2025 02:22:09 +0100 Subject: [PATCH 10/18] Fix error. --- exercises/concept/tim-from-marketing-2/.docs/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/tim-from-marketing-2/.docs/introduction.md b/exercises/concept/tim-from-marketing-2/.docs/introduction.md index 4326002c5..b92d080f5 100644 --- a/exercises/concept/tim-from-marketing-2/.docs/introduction.md +++ b/exercises/concept/tim-from-marketing-2/.docs/introduction.md @@ -14,7 +14,7 @@ public Employee getEmployee(String name) { if (employee != null) { return employee; } else { - return throw new IllegalArgumentException("Employee not found"); + throw new IllegalArgumentException("Employee not found"); } } ``` From 53cbbdad92345ec353d8b51a502ea7cd650e17b1 Mon Sep 17 00:00:00 2001 From: josealonso Date: Fri, 14 Mar 2025 02:42:52 +0100 Subject: [PATCH 11/18] Make some improvements, although it's not complete yet. --- .../concept/tim-from-marketing-2/.classpath | 20 ++++++------ .../tim-from-marketing-2/.docs/about.md | 25 +++++++++++++++ .../tim-from-marketing-2/.docs/hints.md | 4 +-- .../.docs/instructions.md | 5 +-- .../.docs/introduction.md | 31 ++++++++----------- .../tim-from-marketing-2/.meta/config.json | 14 +++++++-- .../tim-from-marketing-2/.meta/design.md | 6 ++-- .../.meta/src/reference/java/Employee.java | 5 ++- ...oyeeService.java => EmployeeDatabase.java} | 4 +-- .../.settings/org.eclipse.jdt.core.prefs | 5 +++ .../concept/tim-from-marketing-2/build.gradle | 2 ++ ...oyeeService.java => EmployeeDatabase.java} | 5 ++- ...iceTest.java => EmployeeDatabaseTest.java} | 20 ++++++------ 13 files changed, 92 insertions(+), 54 deletions(-) create mode 100644 exercises/concept/tim-from-marketing-2/.docs/about.md rename exercises/concept/tim-from-marketing-2/.meta/src/reference/java/{EmployeeService.java => EmployeeDatabase.java} (94%) create mode 100644 exercises/concept/tim-from-marketing-2/.settings/org.eclipse.jdt.core.prefs rename exercises/concept/tim-from-marketing-2/src/main/java/{EmployeeService.java => EmployeeDatabase.java} (94%) rename exercises/concept/tim-from-marketing-2/src/test/java/{EmployeeServiceTest.java => EmployeeDatabaseTest.java} (79%) diff --git a/exercises/concept/tim-from-marketing-2/.classpath b/exercises/concept/tim-from-marketing-2/.classpath index bdf6fa2e8..a4c8967ba 100644 --- a/exercises/concept/tim-from-marketing-2/.classpath +++ b/exercises/concept/tim-from-marketing-2/.classpath @@ -1,9 +1,10 @@ - + - - + + + @@ -12,13 +13,6 @@ - - - - - - - @@ -26,6 +20,12 @@ + + + + + + diff --git a/exercises/concept/tim-from-marketing-2/.docs/about.md b/exercises/concept/tim-from-marketing-2/.docs/about.md new file mode 100644 index 000000000..d070eb60d --- /dev/null +++ b/exercises/concept/tim-from-marketing-2/.docs/about.md @@ -0,0 +1,25 @@ +# ??? + +The **Optional** 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"); + } +} +``` + +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. + +TBD: Rephrase this last paragraph. + +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). diff --git a/exercises/concept/tim-from-marketing-2/.docs/hints.md b/exercises/concept/tim-from-marketing-2/.docs/hints.md index 72eec5bbe..e53a4fdad 100644 --- a/exercises/concept/tim-from-marketing-2/.docs/hints.md +++ b/exercises/concept/tim-from-marketing-2/.docs/hints.md @@ -4,7 +4,7 @@ - This method returns an `Optional` object, not an `Employee` one. -## 2. Return the name and department of a given employee in a certain format +## 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. \ No newline at end of file +- Remember the syntax of the `ifPresentOrElse()` method. diff --git a/exercises/concept/tim-from-marketing-2/.docs/instructions.md b/exercises/concept/tim-from-marketing-2/.docs/instructions.md index a39767bd6..1c9004561 100644 --- a/exercises/concept/tim-from-marketing-2/.docs/instructions.md +++ b/exercises/concept/tim-from-marketing-2/.docs/instructions.md @@ -8,7 +8,8 @@ You will be writing two methods: `getEmployeeById(int)` and `getEmployeeDetailsB ## 1. Get an employee by ID -Implement the `getEmployeeById(int)` method so that it returns an Optional object. +Implement the `getEmployeeById(int)` method so that it returns an Optional 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 @@ -21,4 +22,4 @@ 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]`. \ No newline at end of file +If the employee does not exist or is null, it returns `No employee found for id: [id]`. diff --git a/exercises/concept/tim-from-marketing-2/.docs/introduction.md b/exercises/concept/tim-from-marketing-2/.docs/introduction.md index b92d080f5..562837a5e 100644 --- a/exercises/concept/tim-from-marketing-2/.docs/introduction.md +++ b/exercises/concept/tim-from-marketing-2/.docs/introduction.md @@ -2,24 +2,24 @@ ## Optional -## Introduction +The **Optional** type was introduced in Java 8 as a way to indicate that a method _may_ return a value. -The **Optional** 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. -Before Java 8, developers had to implement null checks: +In other words, there is a chance the method returns "no value" at all. + +## Creating an Optional object + +Given an object of type Employee, an Optional object is created as follows: ```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"); - } -} +Employee employee = new Employee(); +Optional optionalEmployee = Optional.of(employee); ``` -With the Optional API, the code above can be simplified to: +`optionalEmployee` is a wrapper of `employee`. + +TBD: explain empty, present and get. + +## Usage ```java public Optional getEmployee(String name) { @@ -62,8 +62,3 @@ public Optional getEmployeeAge(String name) { .orElse("No employee found"); } ``` - -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. -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) \ No newline at end of file diff --git a/exercises/concept/tim-from-marketing-2/.meta/config.json b/exercises/concept/tim-from-marketing-2/.meta/config.json index 4495e216a..6b371e8fb 100644 --- a/exercises/concept/tim-from-marketing-2/.meta/config.json +++ b/exercises/concept/tim-from-marketing-2/.meta/config.json @@ -1,17 +1,25 @@ { + "name": "tim-from-marketing-2", + "uuid": "a6cfc286-8c62-4f5b-8e59-f6bfc4374092", + "concepts": [ + "optional-type" + ], "authors": [ "josealonso" ], + "contributors": [ + "kahgoh" + ], "files": { "solution": [ - "src/main/java/EmployeeService.java" + "src/main/java/EmployeeDatabase.java" ], "test": [ - "src/test/java/EmployeeServiceTest.java" + "src/test/java/EmployeeDatabaseTest.java" ], "exemplar": [ ".meta/src/reference/java/EmployeeService.java" - ], + ] }, "icon": "nullability", "blurb": "Learn to use the Optional class by helping Tim print details of his company employees." diff --git a/exercises/concept/tim-from-marketing-2/.meta/design.md b/exercises/concept/tim-from-marketing-2/.meta/design.md index 82a24b502..3ef6bdaa4 100644 --- a/exercises/concept/tim-from-marketing-2/.meta/design.md +++ b/exercises/concept/tim-from-marketing-2/.meta/design.md @@ -3,9 +3,8 @@ ## 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`, `orElseThrow`. +We will use the most common methods: `ifPresent`, `orElse`, `ifPresentOrElse`, `orElseThrow`. 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 @@ -35,8 +34,9 @@ This Concept Exercise's prerequisites Concepts are: ## Analyzer This exercise could benefit from the following rules in the [analyzer]: + - `essential`: If the solution uses `null` in any method, encourage the student to use `Optional` instead. -- `actionable`: If the solution uses the `get` or `isPresent` methods of the Optional API, encourage the student to use `orElse`, `orElseThrow` or `ifPresentOrElse` instead. +- `actionable`: If the solution uses the `get` or `isPresent` methods of the Optional API, encourage the student to use `orElse`, `orElseThrow` or `ifPresentOrElse` instead. - `informative`: TODO. If the solution does not receive any of the above feedback, it must be exemplar. diff --git a/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/Employee.java b/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/Employee.java index 98d897e1a..650951be4 100644 --- a/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/Employee.java +++ b/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/Employee.java @@ -1,5 +1,8 @@ import java.util.Objects; -import java.util.Optional; + +/** + * Holds information about an employee. There is no need to change this file. +*/ class Employee { private final int id; diff --git a/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/EmployeeService.java b/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/EmployeeDatabase.java similarity index 94% rename from exercises/concept/tim-from-marketing-2/.meta/src/reference/java/EmployeeService.java rename to exercises/concept/tim-from-marketing-2/.meta/src/reference/java/EmployeeDatabase.java index 9029ccbc5..ddd114c09 100644 --- a/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/EmployeeService.java +++ b/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/EmployeeDatabase.java @@ -2,12 +2,12 @@ import java.util.Optional; import java.util.Objects; -class EmployeeService { +class EmployeeDatabase { // This list is populated in the tests private List employeesList; - public EmployeeService(List listOfEmployees) { + public EmployeeDatabase(List listOfEmployees) { employeesList = listOfEmployees; } diff --git a/exercises/concept/tim-from-marketing-2/.settings/org.eclipse.jdt.core.prefs b/exercises/concept/tim-from-marketing-2/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..bbf9f297a --- /dev/null +++ b/exercises/concept/tim-from-marketing-2/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,5 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.classpath.outputOverlappingAnotherSource=ignore +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.compliance=17 +org.eclipse.jdt.core.compiler.source=17 diff --git a/exercises/concept/tim-from-marketing-2/build.gradle b/exercises/concept/tim-from-marketing-2/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/concept/tim-from-marketing-2/build.gradle +++ b/exercises/concept/tim-from-marketing-2/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/concept/tim-from-marketing-2/src/main/java/EmployeeService.java b/exercises/concept/tim-from-marketing-2/src/main/java/EmployeeDatabase.java similarity index 94% rename from exercises/concept/tim-from-marketing-2/src/main/java/EmployeeService.java rename to exercises/concept/tim-from-marketing-2/src/main/java/EmployeeDatabase.java index 090f4db87..2ed4e480f 100644 --- a/exercises/concept/tim-from-marketing-2/src/main/java/EmployeeService.java +++ b/exercises/concept/tim-from-marketing-2/src/main/java/EmployeeDatabase.java @@ -1,13 +1,12 @@ -import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Optional; -class EmployeeService { +class EmployeeDatabase { private List employeesList; - public EmployeeService(List listOfEmployees) { + public EmployeeDatabase(List listOfEmployees) { // This list is populated in the tests employeesList = listOfEmployees; } diff --git a/exercises/concept/tim-from-marketing-2/src/test/java/EmployeeServiceTest.java b/exercises/concept/tim-from-marketing-2/src/test/java/EmployeeDatabaseTest.java similarity index 79% rename from exercises/concept/tim-from-marketing-2/src/test/java/EmployeeServiceTest.java rename to exercises/concept/tim-from-marketing-2/src/test/java/EmployeeDatabaseTest.java index 1c9533f6b..69cf84753 100644 --- a/exercises/concept/tim-from-marketing-2/src/test/java/EmployeeServiceTest.java +++ b/exercises/concept/tim-from-marketing-2/src/test/java/EmployeeDatabaseTest.java @@ -8,9 +8,9 @@ import java.util.ArrayList; import java.util.Optional; -public class EmployeeServiceTest { +public class EmployeeDatabaseTest { - private EmployeeService employeeService; + private EmployeeDatabase employeeDatabase; private List listOfEmployees = new ArrayList<>(); void initList() { @@ -23,14 +23,14 @@ void initList() { @BeforeEach void setup() { initList(); - employeeService = new EmployeeService(listOfEmployees); + employeeDatabase = new EmployeeDatabase(listOfEmployees); } @Test @Tag("task:1") @DisplayName("Retrieve one employee by id") void retrieveOneEmployeeById() { - assertThat(employeeService.getEmployeeById(2)) + assertThat(employeeDatabase.getEmployeeById(2)) .isEqualTo(Optional.of(listOfEmployees.get(1))); } @@ -38,7 +38,7 @@ void retrieveOneEmployeeById() { @Tag("task:2") @DisplayName("Retrieve other employee by id") void retrieveOtherEmployeeById() { - assertThat(employeeService.getEmployeeById(3)) + assertThat(employeeDatabase.getEmployeeById(3)) .isEqualTo(Optional.of(listOfEmployees.get(2))); } @@ -46,7 +46,7 @@ void retrieveOtherEmployeeById() { @Tag("task:3") @DisplayName("Retrieve employee by id when some fields are null") void retrieveEmployeeById_withNullFields() { - assertThat(employeeService.getEmployeeById(4)) + assertThat(employeeDatabase.getEmployeeById(4)) .isEqualTo(Optional.of(listOfEmployees.get(3))); } @@ -54,7 +54,7 @@ void retrieveEmployeeById_withNullFields() { @Tag("task:4") @DisplayName("Retrieve employee by id when the id does not exist") void retrieveEmployeeById_forANonExistingId() { - assertThat(employeeService.getEmployeeById(7)) + assertThat(employeeDatabase.getEmployeeById(7)) .isEqualTo(Optional.empty()); } @@ -62,7 +62,7 @@ void retrieveEmployeeById_forANonExistingId() { @Tag("task:5") @DisplayName("Retrieve employee details by id") void retrieveEmployeeDetailsById() { - assertThat(employeeService.getEmployeeDetailsById(2)) + assertThat(employeeDatabase.getEmployeeDetailsById(2)) .isEqualTo("2 - Joe - Sales"); } @@ -70,7 +70,7 @@ void retrieveEmployeeDetailsById() { @Tag("task:6") @DisplayName("Retrieve employee details by id when some fields are null") void retrieveEmployeeDetailsById_withNullFields() { - assertThat(employeeService.getEmployeeDetailsById(4)) + assertThat(employeeDatabase.getEmployeeDetailsById(4)) .isEqualTo("No employee found for id: 4"); } @@ -78,7 +78,7 @@ void retrieveEmployeeDetailsById_withNullFields() { @Tag("task:7") @DisplayName("Retrieve employee details by id when the id does not exist") void retrieveEmployeeDetailsById_forANonExistingId() { - assertThat(employeeService.getEmployeeDetailsById(7)) + assertThat(employeeDatabase.getEmployeeDetailsById(7)) .isEqualTo("No employee found for id: 7"); } } From 9b20d73347b40324bbd590d9533aca6314c7768e Mon Sep 17 00:00:00 2001 From: josealonso Date: Fri, 14 Mar 2025 17:21:30 +0100 Subject: [PATCH 12/18] Make more improvements, including a new "about.md" document. --- .../tim-from-marketing-2/.docs/about.md | 25 ------------- .../.docs/instructions.md | 5 ++- .../.docs/introduction.md | 36 ++++++++++++++++--- .../tim-from-marketing-2/.meta/design.md | 2 +- .../org.eclipse.buildship.core.prefs | 2 +- 5 files changed, 35 insertions(+), 35 deletions(-) delete mode 100644 exercises/concept/tim-from-marketing-2/.docs/about.md diff --git a/exercises/concept/tim-from-marketing-2/.docs/about.md b/exercises/concept/tim-from-marketing-2/.docs/about.md deleted file mode 100644 index d070eb60d..000000000 --- a/exercises/concept/tim-from-marketing-2/.docs/about.md +++ /dev/null @@ -1,25 +0,0 @@ -# ??? - -The **Optional** 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"); - } -} -``` - -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. - -TBD: Rephrase this last paragraph. - -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). diff --git a/exercises/concept/tim-from-marketing-2/.docs/instructions.md b/exercises/concept/tim-from-marketing-2/.docs/instructions.md index 1c9004561..2690431a2 100644 --- a/exercises/concept/tim-from-marketing-2/.docs/instructions.md +++ b/exercises/concept/tim-from-marketing-2/.docs/instructions.md @@ -1,7 +1,7 @@ # 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. +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, which is populated in the tests. You will be writing two methods: `getEmployeeById(int)` and `getEmployeeDetailsById(int)`. @@ -14,8 +14,7 @@ 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: +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" diff --git a/exercises/concept/tim-from-marketing-2/.docs/introduction.md b/exercises/concept/tim-from-marketing-2/.docs/introduction.md index 562837a5e..563d42a1b 100644 --- a/exercises/concept/tim-from-marketing-2/.docs/introduction.md +++ b/exercises/concept/tim-from-marketing-2/.docs/introduction.md @@ -8,19 +8,37 @@ In other words, there is a chance the method returns "no value" at all. ## Creating an Optional object -Given an object of type Employee, an Optional object is created as follows: +Given an object of type Employee, an Optional object can be created using the static [of][optional-of-javadoc] method: ```java Employee employee = new Employee(); Optional optionalEmployee = Optional.of(employee); ``` -`optionalEmployee` is a wrapper of `employee`. +If the employee _may_ be not present, the static [ofNullable][optional-ofNullable-javadoc] method must be used: -TBD: explain empty, present and get. +```java +Employee nullableEmployee = new Employee(); +Optional 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. + +```java +Employee employee = new Employee("Tim", 45); +Optional 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 getEmployee(String name) { // Assume that getEmployeeByName returns an Optional @@ -29,7 +47,7 @@ public Optional getEmployee(String name) { } ``` -If a default value must be returned, the `orElse` method can be used. +If a default value must be returned, the [orElse][optional-orElse-javadoc] method can be used. ```java public Optional getEmployee(String name) { @@ -39,7 +57,7 @@ public Optional getEmployee(String name) { } ``` -Other commonly used method is `ifPresentOrElse`, which is used to handle the case where the value is present and the case where the value is empty. +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 getEmployee(String name) { @@ -62,3 +80,11 @@ public Optional getEmployeeAge(String name) { .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) diff --git a/exercises/concept/tim-from-marketing-2/.meta/design.md b/exercises/concept/tim-from-marketing-2/.meta/design.md index 3ef6bdaa4..911c913ae 100644 --- a/exercises/concept/tim-from-marketing-2/.meta/design.md +++ b/exercises/concept/tim-from-marketing-2/.meta/design.md @@ -27,7 +27,7 @@ This Concepts Exercise's Concepts are: This Concept Exercise's prerequisites Concepts are: -- `custom classes`. +- `custom-classes`. - `lists`. - `generic-types`. diff --git a/exercises/concept/tim-from-marketing-2/.settings/org.eclipse.buildship.core.prefs b/exercises/concept/tim-from-marketing-2/.settings/org.eclipse.buildship.core.prefs index 81645a672..62e3e7e80 100644 --- a/exercises/concept/tim-from-marketing-2/.settings/org.eclipse.buildship.core.prefs +++ b/exercises/concept/tim-from-marketing-2/.settings/org.eclipse.buildship.core.prefs @@ -1,2 +1,2 @@ -connection.project.dir=../tim-from-marketing +connection.project.dir=../.. eclipse.preferences.version=1 From c227af51b7ba38bda2ceebae7187bc5205e024cd Mon Sep 17 00:00:00 2001 From: josealonso Date: Fri, 14 Mar 2025 18:02:36 +0100 Subject: [PATCH 13/18] Add two tests. --- .../.meta/src/reference/java/Employee.java | 6 +++-- .../src/reference/java/EmployeeDatabase.java | 13 ++++++----- .../src/test/java/EmployeeDatabaseTest.java | 22 +++++++++++++++++-- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/Employee.java b/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/Employee.java index 650951be4..f43bf8d42 100644 --- a/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/Employee.java +++ b/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/Employee.java @@ -29,10 +29,12 @@ public String getDepartment() { @Override public boolean equals(Object o) { - if (this == o) + if (this == o) { return true; - if (o == null || getClass() != o.getClass()) + } + if (o == null || getClass() != o.getClass()) { return false; + } Employee employee = (Employee) o; return id == employee.id && Objects.equals(name, employee.name) && Objects.equals(department, employee.department); diff --git a/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/EmployeeDatabase.java b/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/EmployeeDatabase.java index ddd114c09..0b1eebf4a 100644 --- a/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/EmployeeDatabase.java +++ b/exercises/concept/tim-from-marketing-2/.meta/src/reference/java/EmployeeDatabase.java @@ -26,12 +26,13 @@ public String getEmployeeDetailsById(int employeeId) { nullableEmployee.ifPresentOrElse( employee1 -> { Optional.ofNullable(employee1.getName()) - .ifPresentOrElse(name -> stringBuilder.append(employeeId).append(" - ") - .append(employee1.getName()).append(" - ") - .append(employee1.getDepartment()), - () -> { - stringBuilder.append("No employee found for id: ").append(employeeId); - }); + .ifPresentOrElse(name -> stringBuilder + .append(employeeId).append(" - ") + .append(employee1.getName()).append(" - ") + .append(employee1.getDepartment()), + () -> { + stringBuilder.append("No employee found for id: ").append(employeeId); + }); }, () -> { stringBuilder.append("That id does not exist: ").append(employeeId); diff --git a/exercises/concept/tim-from-marketing-2/src/test/java/EmployeeDatabaseTest.java b/exercises/concept/tim-from-marketing-2/src/test/java/EmployeeDatabaseTest.java index 69cf84753..ecef40352 100644 --- a/exercises/concept/tim-from-marketing-2/src/test/java/EmployeeDatabaseTest.java +++ b/exercises/concept/tim-from-marketing-2/src/test/java/EmployeeDatabaseTest.java @@ -18,6 +18,8 @@ void initList() { listOfEmployees.add(new Employee(2, "Joe", "Sales")); listOfEmployees.add(new Employee(3, "Jane", "IT")); listOfEmployees.add(new Employee(4, null, null)); + listOfEmployees.add(new Employee(5, null, "IT")); + listOfEmployees.add(new Employee(6, "Alice", null)); } @BeforeEach @@ -76,9 +78,25 @@ void retrieveEmployeeDetailsById_withNullFields() { @Test @Tag("task:7") + @DisplayName("Retrieve employee details by id when name is null and department is not null") + void retrieveEmployeeDetailsById_whenNameIsNull() { + assertThat(employeeDatabase.getEmployeeDetailsById(5)) + .isEqualTo("No employee found for id: 5"); + } + + @Test + @Tag("task:8") + @DisplayName("Retrieve employee details by id when department is null and name is not null") + void retrieveEmployeeDetailsById_whenDepartmentIsNull() { + assertThat(employeeDatabase.getEmployeeDetailsById(6)) + .isEqualTo("No employee found for id: 6"); + } + + @Test + @Tag("task:9") @DisplayName("Retrieve employee details by id when the id does not exist") void retrieveEmployeeDetailsById_forANonExistingId() { - assertThat(employeeDatabase.getEmployeeDetailsById(7)) - .isEqualTo("No employee found for id: 7"); + assertThat(employeeDatabase.getEmployeeDetailsById(10)) + .isEqualTo("No employee found for id: 10"); } } From 8f2afa8e1da00488aa2d044b3c84c43aa95623c5 Mon Sep 17 00:00:00 2001 From: josealonso Date: Fri, 14 Mar 2025 18:12:36 +0100 Subject: [PATCH 14/18] Fix configuration issues. --- config.json | 2 +- exercises/concept/tim-from-marketing-2/.meta/config.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config.json b/config.json index 13a34c3ff..66c66e3ea 100644 --- a/config.json +++ b/config.json @@ -317,7 +317,7 @@ "optional-type" ], "prerequisites": [ - "custom classes", + "custom-classes", "lists", "generic-types" ] diff --git a/exercises/concept/tim-from-marketing-2/.meta/config.json b/exercises/concept/tim-from-marketing-2/.meta/config.json index 6b371e8fb..e65124bc0 100644 --- a/exercises/concept/tim-from-marketing-2/.meta/config.json +++ b/exercises/concept/tim-from-marketing-2/.meta/config.json @@ -18,7 +18,7 @@ "src/test/java/EmployeeDatabaseTest.java" ], "exemplar": [ - ".meta/src/reference/java/EmployeeService.java" + ".meta/src/reference/java/EmployeeDatabase.java" ] }, "icon": "nullability", From 27b66624faf06285c56de585983699a21e4ee8d0 Mon Sep 17 00:00:00 2001 From: josealonso Date: Fri, 14 Mar 2025 18:31:10 +0100 Subject: [PATCH 15/18] Changed some prerequisites names. --- concepts/optional-type/.meta/config.json | 9 +++ concepts/optional-type/about.md | 59 ++++++++++++++++ concepts/optional-type/introduction.md | 90 ++++++++++++++++++++++++ concepts/optional-type/links.json | 0 config.json | 1 - 5 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 concepts/optional-type/.meta/config.json create mode 100644 concepts/optional-type/about.md create mode 100644 concepts/optional-type/introduction.md create mode 100644 concepts/optional-type/links.json diff --git a/concepts/optional-type/.meta/config.json b/concepts/optional-type/.meta/config.json new file mode 100644 index 000000000..074ff8b5e --- /dev/null +++ b/concepts/optional-type/.meta/config.json @@ -0,0 +1,9 @@ +{ + "blurb": "Learn to use the Optional class by helping Tim print details of his company employees.", + "authors": [ + "josealonso" + ], + "contributors": [ + "kahgoh" + ] +} diff --git a/concepts/optional-type/about.md b/concepts/optional-type/about.md new file mode 100644 index 000000000..1890bc981 --- /dev/null +++ b/concepts/optional-type/about.md @@ -0,0 +1,59 @@ +# About the Optional Type + +The **Optional** 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 getEmployee(String name) { + // Assume that getEmployeeByName returns an Optional + 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 object. + +```java +List> employees = new ArrayList<>(); +employees.add(getEmployee("Tim")); +employees.add(getEmployee("Bob")); +employees.add(getEmployee("Alice")); +``` + +```java +public List> 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. diff --git a/concepts/optional-type/introduction.md b/concepts/optional-type/introduction.md new file mode 100644 index 000000000..563d42a1b --- /dev/null +++ b/concepts/optional-type/introduction.md @@ -0,0 +1,90 @@ +# Introduction + +## Optional + +The **Optional** 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 object + +Given an object of type Employee, an Optional object can be created using the static [of][optional-of-javadoc] method: + +```java +Employee employee = new Employee(); +Optional 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 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. + +```java +Employee employee = new Employee("Tim", 45); +Optional 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 getEmployee(String name) { + // Assume that getEmployeeByName returns an Optional + 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 getEmployee(String name) { + // Assume that getEmployeeByName returns an Optional + 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 getEmployee(String name) { + // Assume that getEmployeeByName returns an Optional + 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 getEmployeeAge(String name) { + Optional 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) diff --git a/concepts/optional-type/links.json b/concepts/optional-type/links.json new file mode 100644 index 000000000..e69de29bb diff --git a/config.json b/config.json index 66c66e3ea..36e84a35a 100644 --- a/config.json +++ b/config.json @@ -317,7 +317,6 @@ "optional-type" ], "prerequisites": [ - "custom-classes", "lists", "generic-types" ] From f0475099e2a95588b704cfed7f6d467a6d1213f9 Mon Sep 17 00:00:00 2001 From: josealonso Date: Fri, 14 Mar 2025 18:37:24 +0100 Subject: [PATCH 16/18] The if-staments concept is now called if-else-statements. --- config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.json b/config.json index 36e84a35a..ff219616b 100644 --- a/config.json +++ b/config.json @@ -959,7 +959,7 @@ "practices": [], "prerequisites": [ "arrays", - "if-statements" + "if-else-statements" ], "difficulty": 5 }, From ec171ee7fe571a6c2f64f8dc812e95443126504d Mon Sep 17 00:00:00 2001 From: josealonso Date: Fri, 14 Mar 2025 18:51:01 +0100 Subject: [PATCH 17/18] Add optional-type to the concepts array. --- config.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config.json b/config.json index ff219616b..dbfadb827 100644 --- a/config.json +++ b/config.json @@ -1983,6 +1983,11 @@ "uuid": "78f3c7b2-cb9c-4d21-8cb4-7106a188f713", "slug": "ternary-operators", "name": "Ternary Operators" + }, + { + "uuid": "a6cfc286-8c62-4f5b-8e59-f6bfc4374092", + "slug": "optional-type", + "name": "Optional Type" } ], "key_features": [ From 41076df1b7f11b0c9c3aefa08cb273eee780eb90 Mon Sep 17 00:00:00 2001 From: josealonso Date: Fri, 14 Mar 2025 19:35:24 +0100 Subject: [PATCH 18/18] Fix configlet issues. --- concepts/optional-type/links.json | 2 ++ config.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/concepts/optional-type/links.json b/concepts/optional-type/links.json index e69de29bb..7dd438752 100644 --- a/concepts/optional-type/links.json +++ b/concepts/optional-type/links.json @@ -0,0 +1,2 @@ +[] + diff --git a/config.json b/config.json index dbfadb827..2881f33ae 100644 --- a/config.json +++ b/config.json @@ -1985,7 +1985,7 @@ "name": "Ternary Operators" }, { - "uuid": "a6cfc286-8c62-4f5b-8e59-f6bfc4374092", + "uuid": "", "slug": "optional-type", "name": "Optional Type" }