-
-
Notifications
You must be signed in to change notification settings - Fork 756
New concept: Optional #2913
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
josealonso
wants to merge
19
commits into
exercism:main
Choose a base branch
from
josealonso:2555-new-concept-optional
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
New concept: Optional #2913
Changes from 5 commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
e88392b
Write explanation for the Optional concept, as well as the test file,…
josealonso fbe3f46
Delete unused file.
josealonso 0abf443
Explain the flatmap operation. The hints.md and introduction.md files…
josealonso 5c143e1
exercises/concept/tim-from-marketing-2/
josealonso 26a1397
One method definition is now given to the student and more explanatio…
josealonso f94749a
Use @BeforeEach instead of @BeforeAll.
josealonso 4c5e540
Simplify the solution and the asked methods so streams are not used.
josealonso 4b7a800
Modify many files, including Hints.md, to adapt them to the new exerc…
josealonso 9d4cd39
Delete explanation about flatMap.
josealonso 6db542e
Fix error.
josealonso 53cbbda
Make some improvements, although it's not complete yet.
josealonso 145d49b
Merge branch 'main' into 2555-new-concept-optional
josealonso 9b20d73
Make more improvements, including a new "about.md" document.
josealonso c227af5
Add two tests.
josealonso 8f2afa8
Fix configuration issues.
josealonso 27b6662
Changed some prerequisites names.
josealonso f047509
The if-staments concept is now called if-else-statements.
josealonso ec171ee
Add optional-type to the concepts array.
josealonso 41076df
Fix configlet issues.
josealonso File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <classpath> | ||
| <classpathentry kind="src" output="bin/main" path=".meta/src/reference/java"> | ||
| <attributes> | ||
| <attribute name="gradle_scope" value="main"/> | ||
| <attribute name="gradle_used_by_scope" value="main,test"/> | ||
| </attributes> | ||
| </classpathentry> | ||
| <classpathentry kind="src" output="bin/starterSource" path="src/main/java"> | ||
| <attributes> | ||
| <attribute name="gradle_scope" value="starterSource"/> | ||
| <attribute name="gradle_used_by_scope" value="starterSource"/> | ||
| </attributes> | ||
| </classpathentry> | ||
| <classpathentry kind="src" output="bin/starterTest" path="src/test/java"> | ||
| <attributes> | ||
| <attribute name="gradle_scope" value="starterTest"/> | ||
| <attribute name="gradle_used_by_scope" value="starterTest"/> | ||
| <attribute name="test" value="true"/> | ||
| </attributes> | ||
| </classpathentry> | ||
| <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17/"/> | ||
| <classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/> | ||
| <classpathentry kind="output" path="bin"/> | ||
| </classpath> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
|
|
||
31 changes: 31 additions & 0 deletions
31
exercises/concept/tim-from-marketing-2/.docs/instructions.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| # 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<T> types. | ||
| The class constructor receives a parameter of type List<Optional<Employee>>, 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 | ||
| " | ||
| 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] - No employee found". You will have to call the method `getEmployeeById(int employeeId)`, which returns an Optional<Employee> and it's already defined. | ||
|
|
||
| ```java | ||
| printEmployeeNameAndDepartmentById(1) => "1 - Tim - Marketing" | ||
| printEmployeeNameAndDepartmentById(3) => "3 - Steve - Engineering" | ||
| printEmployeeNameAndDepartmentById(4) => "4 - This employee does not exist" | ||
| ``` |
92 changes: 92 additions & 0 deletions
92
exercises/concept/tim-from-marketing-2/.docs/introduction.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| # Introduction | ||
|
|
||
| ## Optional | ||
|
|
||
| ## Introduction | ||
|
|
||
| The **Optional<T>** type was introduced in Java 8 as a way to indicate that a method will return an object of type T or an empty value. It is present in type signatures of many core Java methods. | ||
|
josealonso marked this conversation as resolved.
Outdated
|
||
|
|
||
| Before Java 8, developers had to implement null checks: | ||
|
josealonso marked this conversation as resolved.
Outdated
|
||
|
|
||
| ```java | ||
| public Employee getEmployee(String name) { | ||
| // Assume that getEmployeeByName retrieves an Employee from a database | ||
| Employee employee = getEmployeeByName(name); | ||
| if (employee != null) { | ||
| return employee; | ||
| } else { | ||
| return throw new IllegalArgumentException("Employee not found"); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
|
josealonso marked this conversation as resolved.
|
||
| With the Optional API, the code above can be simplified to: | ||
|
|
||
| ```java | ||
| public Optional<Employee> getEmployee(String name) { | ||
| // Assume that getEmployeeByName returns an Optional<Employee> | ||
| return getEmployeeByName(name) | ||
| .orElseThrow(() -> new IllegalArgumentException("Employee not found")); | ||
| } | ||
| ``` | ||
|
|
||
| If a default value must be returned, the `orElse` method can be used. | ||
|
|
||
| ```java | ||
| public Optional<Employee> getEmployee(String name) { | ||
| // Assume that getEmployeeByName returns an Optional<Employee> | ||
| return getEmployeeByName(name) | ||
| .orElse(new Employee("Daniel")); | ||
| } | ||
| ``` | ||
|
|
||
| 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<Employee> getEmployee(String name) { | ||
| // Assume that getEmployeeByName returns an Optional<Employee> | ||
| return getEmployeeByName(name) | ||
| .ifPresentOrElse( | ||
| employee -> System.out.println(employee.getName()), | ||
| () -> System.out.println("Employee not found") | ||
| ); | ||
| } | ||
| ``` | ||
|
|
||
| Provided all the invoked methods return Optional objects, many methods can be chained without having to worry about null checking: | ||
|
|
||
| ```java | ||
| public Optional<Integer> getEmployeeAge(String name) { | ||
| Optional<Employee> optionalEmployee = getEmployeeByName(name); | ||
| return getEmployeeByName(name) | ||
| .map(employee -> employee.getAge()) | ||
| .orElse("No employee found"); | ||
| } | ||
| ``` | ||
|
|
||
| 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<Optional<String>> listOfOptionals = Arrays.asList( | ||
| Optional.of("Java"), | ||
| Optional.empty(), | ||
| Optional.of("Kotlin") | ||
| ); | ||
| ``` | ||
|
|
||
| ```java | ||
| // Using flatMap to extract present values | ||
| List<String> result = listOfOptionals.stream() | ||
| .flatMap(language -> language.stream()) | ||
| .collect(Collectors.toList()); | ||
|
|
||
| System.out.println(result); // Output: [Java, Kotlin] | ||
| } | ||
| } | ||
| ``` | ||
5 changes: 5 additions & 0 deletions
5
exercises/concept/tim-from-marketing-2/.docs/introduction.md.tpl
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| # Introduction | ||
|
|
||
| %{concept:optional-types} | ||
|
|
||
| %{concept:flatMap-operation} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| { | ||
| "authors": [ | ||
| "josealonso" | ||
| ], | ||
| "files": { | ||
| "solution": [ | ||
| "src/main/java/EmployeeService.java" | ||
| ], | ||
| "test": [ | ||
| "src/test/java/EmployeeServiceTest.java" | ||
| ], | ||
| "exemplar": [ | ||
| ".meta/src/reference/java/EmployeeService.java" | ||
| ], | ||
| }, | ||
| "icon": "nullability", | ||
| "blurb": "Learn to use the Optional class by helping Tim print details of his company employees." | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| # 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`, `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. | ||
|
josealonso marked this conversation as resolved.
Outdated
|
||
|
|
||
| ## Learning objectives | ||
|
|
||
| - Know what optional types are. | ||
| - Know how to use Optional<T> fields. | ||
| - Know how to use methods that return an Optional<T> type. | ||
| - See the utility of some Stream methods, `flatMap` specifically. | ||
|
|
||
| ## Out of scope | ||
|
|
||
| - Streams API. | ||
|
|
||
| ## Concepts | ||
|
|
||
| This Concepts Exercise's Concepts are: | ||
|
|
||
| - `Optional<T>` class and some methods that mimic a null check. | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| This Concept Exercise's prerequisites Concepts are: | ||
|
|
||
| - `custom classes`. | ||
| - `generic-types`. | ||
| - `streams`. | ||
|
|
||
| ## Analyzer | ||
|
|
||
| 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<T>` instead. | ||
|
josealonso marked this conversation as resolved.
Outdated
|
||
| - `actionable`: If the solution uses the `get` or `isPresent` methods of the Optional<T> 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! | ||
|
|
||
| [analyzer]: https://github.com/exercism/java-analyzer | ||
40 changes: 40 additions & 0 deletions
40
exercises/concept/tim-from-marketing-2/.meta/src/reference/java/Employee.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| import java.util.Objects; | ||
| import java.util.Optional; | ||
|
|
||
|
josealonso marked this conversation as resolved.
|
||
| class Employee { | ||
| private final int id; | ||
| private final String name; | ||
| private final String department; | ||
|
|
||
| public Employee(int id, String name, String department) { | ||
| this.id = id; | ||
| this.name = name; | ||
| this.department = department; | ||
| } | ||
|
|
||
| public Optional<Integer> getNullableId() { | ||
| return Optional.ofNullable(id); | ||
| } | ||
|
|
||
| public Optional<String> getNullableName() { | ||
| return Optional.ofNullable(name); | ||
| } | ||
|
|
||
| public Optional<String> 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); | ||
| } | ||
| } | ||
60 changes: 60 additions & 0 deletions
60
exercises/concept/tim-from-marketing-2/.meta/src/reference/java/EmployeeService.java
|
josealonso marked this conversation as resolved.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| import java.util.List; | ||
| import java.util.ArrayList; | ||
| import java.util.Optional; | ||
|
|
||
| class EmployeeService { | ||
|
|
||
| // This list is populated in the tests | ||
| private List<Optional<Employee>> nullableEmployeesList = new ArrayList<>(); | ||
|
|
||
| public EmployeeService(List<Optional<Employee>> listOfEmployees) { | ||
| nullableEmployeesList = listOfEmployees; | ||
| } | ||
|
|
||
| public Optional<Employee> 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> 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(); | ||
| } | ||
|
|
||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <projectDescription> | ||
| <name>tim-from-marketing-2</name> | ||
| <comment>Project tim-from-marketing-2 created by Buildship.</comment> | ||
| <projects> | ||
| </projects> | ||
| <buildSpec> | ||
| <buildCommand> | ||
| <name>org.eclipse.jdt.core.javabuilder</name> | ||
| <arguments> | ||
| </arguments> | ||
| </buildCommand> | ||
| <buildCommand> | ||
| <name>org.eclipse.buildship.core.gradleprojectbuilder</name> | ||
| <arguments> | ||
| </arguments> | ||
| </buildCommand> | ||
| </buildSpec> | ||
| <natures> | ||
| <nature>org.eclipse.jdt.core.javanature</nature> | ||
| <nature>org.eclipse.buildship.core.gradleprojectnature</nature> | ||
| </natures> | ||
| <filteredResources> | ||
| <filter> | ||
| <id>1739322795991</id> | ||
| <name></name> | ||
| <type>30</type> | ||
| <matcher> | ||
| <id>org.eclipse.core.resources.regexFilterMatcher</id> | ||
| <arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments> | ||
| </matcher> | ||
| </filter> | ||
| </filteredResources> | ||
| </projectDescription> |
2 changes: 2 additions & 0 deletions
2
exercises/concept/tim-from-marketing-2/.settings/org.eclipse.buildship.core.prefs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| connection.project.dir=../tim-from-marketing | ||
| eclipse.preferences.version=1 |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.