Skip to content

Commit 26a1397

Browse files
committed
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.
1 parent 5c143e1 commit 26a1397

File tree

12 files changed

+224
-147
lines changed

12 files changed

+224
-147
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<classpath>
3+
<classpathentry kind="src" output="bin/main" path=".meta/src/reference/java">
4+
<attributes>
5+
<attribute name="gradle_scope" value="main"/>
6+
<attribute name="gradle_used_by_scope" value="main,test"/>
7+
</attributes>
8+
</classpathentry>
9+
<classpathentry kind="src" output="bin/starterSource" path="src/main/java">
10+
<attributes>
11+
<attribute name="gradle_scope" value="starterSource"/>
12+
<attribute name="gradle_used_by_scope" value="starterSource"/>
13+
</attributes>
14+
</classpathentry>
15+
<classpathentry kind="src" output="bin/starterTest" path="src/test/java">
16+
<attributes>
17+
<attribute name="gradle_scope" value="starterTest"/>
18+
<attribute name="gradle_used_by_scope" value="starterTest"/>
19+
<attribute name="test" value="true"/>
20+
</attributes>
21+
</classpathentry>
22+
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17/"/>
23+
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
24+
<classpathentry kind="output" path="bin"/>
25+
</classpath>

exercises/concept/tim-from-marketing-2/.docs/instructions.md

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,30 @@
22

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

5-
Employees have an ID, a name and a department name, like in [tim-from-marketing](/exercises/concept/tim-from-marketing).
6-
Assume that the ID of the first employee is 1, the ID of the second employee is 2, and so on. The three fields of an employee may be empty, That's why they are declared as Optional<T> types.
7-
8-
Two methods are already implemented:
9-
10-
- `getAllTheEmployeesById()` returns an Optional<List<Employee>> object. Notice this method does NOT receive any parameter.
11-
- `getEmployeeById(id)` returns an Optional<Employee> object for the given ID, being Employee the following class:
5+
Employees have an ID, a name and a department name, like in the [tim-from-marketing](/exercises/concept/tim-from-marketing) exercise.
6+
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.
7+
The class constructor receives a parameter of type List<Optional<Employee>>, which is populated in the tests.
128

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

15-
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".
11+
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".
1612

1713
```java
18-
"1 - Tim"
19-
"2 - Bill"
20-
"3 - Steve"
21-
"4 - This employee does not exist"
22-
"5 - Charlotte"
14+
"
15+
1 - Tim
16+
2 - Bill
17+
3 - Steve
18+
4 - No employee found
19+
5 - Charlotte
20+
"
2321
```
2422

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

27-
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":
25+
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.
2826

2927
```java
3028
printEmployeeNameAndDepartmentById(1) => "1 - Tim - Marketing"
31-
printEmployeeNameAndDepartmentById(2) => "2 - Bill - Sales"
3229
printEmployeeNameAndDepartmentById(3) => "3 - Steve - Engineering"
3330
printEmployeeNameAndDepartmentById(4) => "4 - This employee does not exist"
34-
printEmployeeNameAndDepartmentById(5) => "5 - Charlotte - Owner"
3531
```

exercises/concept/tim-from-marketing-2/.docs/introduction.md

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,34 @@ public Optional<Employee> getEmployee(String name) {
4040
}
4141
```
4242

43+
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.
44+
45+
```java
46+
public Optional<Employee> getEmployee(String name) {
47+
// Assume that getEmployeeByName returns an Optional<Employee>
48+
return getEmployeeByName(name)
49+
.ifPresentOrElse(
50+
employee -> System.out.println(employee.getName()),
51+
() -> System.out.println("Employee not found")
52+
);
53+
}
54+
```
55+
4356
Provided all the invoked methods return Optional objects, many methods can be chained without having to worry about null checking:
4457

4558
```java
4659
public Optional<Integer> getEmployeeAge(String name) {
4760
Optional<Employee> optionalEmployee = getEmployeeByName(name);
4861
return getEmployeeByName(name)
4962
.map(employee -> employee.getAge())
50-
.orElse(0);
63+
.orElse("No employee found");
5164
}
5265
```
5366

5467
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.
5568

69+
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)
70+
5671
## Flatmap operation
5772

5873
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<Optional<String>> listOfOptionals = Arrays.asList(
6883
```java
6984
// Using flatMap to extract present values
7085
List<String> result = listOfOptionals.stream()
71-
.flatMap(Optional::stream)
86+
.flatMap(language -> language.stream())
7287
.collect(Collectors.toList());
7388

7489
System.out.println(result); // Output: [Java, Kotlin]

exercises/concept/tim-from-marketing-2/.meta/config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@
1313
".meta/src/reference/java/EmployeeService.java"
1414
],
1515
},
16-
"icon": "language-list",
16+
"icon": "nullability",
1717
"blurb": "Learn to use the Optional class by helping Tim print details of his company employees."
1818
}

exercises/concept/tim-from-marketing-2/.meta/design.md

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
## Goal
44

55
The goal of this exercise is to teach the student how to use the Optional API.
6-
We will use the most common methods: `ifPresent`, `orElse`, `ifPresentOrElse`, `orElseThrown`.
6+
We will use the most common methods: `ifPresent`, `orElse`, `ifPresentOrElse`, `orElseThrow`.
77
The `isPresent` and `get` methods are not presented, since they do not provide any value over an ordinary null check.
88

99
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:
3535

3636
## Analyzer
3737

38-
wip
39-
4038
This exercise could benefit from the following rules in the [analyzer]:
4139

42-
- `actionable`: If the solution did not use `contains` in the method `containsLanguage`, instruct the student to do so.
43-
- `actionable`: If the solution did not use `isEmpty` in the method `isEmpty`, instruct the student to do so.
44-
- `informative`: If the student did not reuse the implementation of the `containsLanguage` method in the `isExciting` method, instruct them to do so.
45-
Explain that reusing existing code instead of copy-pasting can help make code easier to maintain.
46-
- `informative`: If the solution uses an `if statement` in the `containsLanguage` method, instruct the student to return directly the `contains` method.
40+
- `actionable`: If the solution uses `null` in any method, encourage the student to use `Optional<T>` instead.
41+
- `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.
42+
- `informative`: TODO.
4743

4844
If the solution does not receive any of the above feedback, it must be exemplar.
4945
Leave a `celebratory` comment to celebrate the success!
Lines changed: 33 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,60 @@
11
import java.util.List;
22
import java.util.ArrayList;
33
import java.util.Optional;
4-
import java.util.stream.*;
5-
64

75
class EmployeeService {
8-
9-
public List<Optional<Employee>> getAllTheEmployeesById() {
10-
return getAllTheEmployeesById()
11-
.stream()
12-
.map(employee -> Optional.ofNullable(employee))
13-
.collect(Collectors.toList());
6+
7+
// This list is populated in the tests
8+
private List<Optional<Employee>> nullableEmployeesList = new ArrayList<>();
9+
10+
public EmployeeService(List<Optional<Employee>> listOfEmployees) {
11+
nullableEmployeesList = listOfEmployees;
1412
}
1513

1614
public Optional<Employee> getEmployeeById(int employeeId) {
17-
/* Solution using Streams
18-
19-
return getAllTheEmployeesById(employeesList).stream()
20-
.filter(employee -> employee.getId() == id)
21-
.orElse("Employee not found");
22-
*/
23-
24-
return Optional.ofNullable(getEmployeeById(employeeId));
15+
return nullableEmployeesList
16+
.stream()
17+
.flatMap(employee -> employee.stream())
18+
.filter(employee -> employee.getNullableId()
19+
.map(id -> id == employeeId)
20+
.orElse(false))
21+
.findFirst();
2522
}
2623

27-
public String printAllEmployeesNamesById() {
28-
List<Optional<Employee>> nullableEmployeesList = getAllTheEmployeesById();
24+
/* I could use IntStream.range(0, nullableEmployeesList.size()) instead of a for loop, but
25+
understanding the Optional API is difficult enough.
26+
I do not use method references for the same reason. */
27+
public String printAllEmployeesNames() {
2928
StringBuilder stringBuilder = new StringBuilder();
3029
for (int i = 0; i < nullableEmployeesList.size(); i++) {
3130
stringBuilder.append(i).append(" - ");
3231

3332
nullableEmployeesList.get(i)
34-
.flatMap(employee -> employee.getName())
35-
.ifPresentOrElse(
36-
name -> stringBuilder.append(name).append("\n"),
37-
() -> stringBuilder.append("No employee found\n")
38-
);
33+
.flatMap(employee -> employee.getNullableName())
34+
.ifPresentOrElse(
35+
name -> stringBuilder.append(name).append("\n"),
36+
() -> stringBuilder.append("No employee found\n")
37+
);
3938
}
4039
return stringBuilder.toString();
4140
}
4241

4342
public String printEmployeeNameAndDepartmentById(int employeeId) {
44-
45-
var employee = getEmployeeById(employeeId);
43+
Optional<Employee> employee = getEmployeeById(employeeId);
4644
StringBuilder stringBuilder = new StringBuilder();
4745
stringBuilder.append(employeeId).append(" - ");
46+
// Handle Optional values
4847
employee.ifPresentOrElse(
49-
e -> {
50-
// Handle Optional values
51-
e.getName().ifPresentOrElse(
52-
name -> stringBuilder.append(name).append(" - "),
53-
() -> {}
54-
);
55-
e.getDepartment().ifPresentOrElse(
56-
department -> stringBuilder.append(department),
57-
() -> {}
58-
);
59-
},
60-
() -> stringBuilder.append("No employee found")
48+
e -> {
49+
e.getNullableName().ifPresent(name ->
50+
e.getNullableDepartment().ifPresent(department ->
51+
stringBuilder.append(name).append(" - ").append(department)
52+
)
53+
);
54+
},
55+
() -> stringBuilder.append("No employee found")
6156
);
6257
return stringBuilder.toString();
63-
}
58+
}
6459

6560
}
66-
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<projectDescription>
3+
<name>tim-from-marketing-2</name>
4+
<comment>Project tim-from-marketing-2 created by Buildship.</comment>
5+
<projects>
6+
</projects>
7+
<buildSpec>
8+
<buildCommand>
9+
<name>org.eclipse.jdt.core.javabuilder</name>
10+
<arguments>
11+
</arguments>
12+
</buildCommand>
13+
<buildCommand>
14+
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
15+
<arguments>
16+
</arguments>
17+
</buildCommand>
18+
</buildSpec>
19+
<natures>
20+
<nature>org.eclipse.jdt.core.javanature</nature>
21+
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
22+
</natures>
23+
<filteredResources>
24+
<filter>
25+
<id>1739322795991</id>
26+
<name></name>
27+
<type>30</type>
28+
<matcher>
29+
<id>org.eclipse.core.resources.regexFilterMatcher</id>
30+
<arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
31+
</matcher>
32+
</filter>
33+
</filteredResources>
34+
</projectDescription>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
connection.project.dir=../tim-from-marketing
2+
eclipse.preferences.version=1
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
plugins {
2+
id "java"
3+
}
4+
5+
repositories {
6+
mavenCentral()
7+
}
8+
9+
dependencies {
10+
testImplementation platform("org.junit:junit-bom:5.10.0")
11+
testImplementation "org.junit.jupiter:junit-jupiter"
12+
testImplementation "org.assertj:assertj-core:3.25.1"
13+
}
14+
15+
test {
16+
useJUnitPlatform()
17+
18+
testLogging {
19+
exceptionFormat = "full"
20+
showStandardStreams = true
21+
events = ["passed", "failed", "skipped"]
22+
}
23+
}
Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import java.util.Objects;
2+
import java.util.Optional;
3+
14
class Employee {
25
private final int id;
36
private final String name;
@@ -9,15 +12,29 @@ public Employee(int id, String name, String department) {
912
this.department = department;
1013
}
1114

12-
public Optional<Integer> getId() {
13-
return Optional.ofNullable(id);
15+
public Optional<Integer> getNullableId() {
16+
return Optional.ofNullable(id);
1417
}
1518

16-
public Optional<String> getName() {
19+
public Optional<String> getNullableName() {
1720
return Optional.ofNullable(name);
1821
}
1922

20-
public Optional<String> getDepartment() {
23+
public Optional<String> getNullableDepartment() {
2124
return Optional.ofNullable(department);
2225
}
26+
27+
@Override
28+
public boolean equals(Object o) {
29+
if (this == o) return true;
30+
if (o == null || getClass() != o.getClass()) return false;
31+
Employee employee = (Employee) o;
32+
return id == employee.id &&
33+
Objects.equals(name, employee.name) && Objects.equals(department, employee.department);
34+
}
35+
36+
@Override
37+
public int hashCode() {
38+
return Objects.hash(id, name, department);
39+
}
2340
}

0 commit comments

Comments
 (0)