From f781ed164c1edff9ac2594efa117ebdeb29d5e0e Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Thu, 11 Apr 2013 21:13:31 +0100 Subject: [PATCH 01/61] Make tests pass with a prejava8 (imperative) solution. --- .../lambda/tutorial/exercise1/Shapes.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise1/Shapes.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise1/Shapes.java index 6978e32..f49873b 100644 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise1/Shapes.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise1/Shapes.java @@ -13,14 +13,21 @@ public class Shapes { public static void colorAll(List shapes, Color newColor) { - // [your code here] + for (Shape s: shapes) { + s.setColor(newColor); + } } public static void makeStringOfAllColors(List shapes, StringBuilder stringBuilder) { - // [your code here] + for (Shape s: shapes) { + stringBuilder.append(s); + } } public static void changeColorAndMakeStringOfOldColors(List shapes, Color newColor, StringBuilder stringBuilder) { - // [your code here] + for (Shape s: shapes) { + stringBuilder.append("[" + s.getColor() + "]"); + s.setColor(newColor); + } } } From 874e2ed27aabe22e7ab41dc5e1262d1952b2ac01 Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Thu, 11 Apr 2013 21:13:31 +0100 Subject: [PATCH 02/61] Make tests pass with a prejava8 (imperative) solution. --- .../lambda/tutorial/exercise1/Shapes.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise1/Shapes.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise1/Shapes.java index 6978e32..f49873b 100644 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise1/Shapes.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise1/Shapes.java @@ -13,14 +13,21 @@ public class Shapes { public static void colorAll(List shapes, Color newColor) { - // [your code here] + for (Shape s: shapes) { + s.setColor(newColor); + } } public static void makeStringOfAllColors(List shapes, StringBuilder stringBuilder) { - // [your code here] + for (Shape s: shapes) { + stringBuilder.append(s); + } } public static void changeColorAndMakeStringOfOldColors(List shapes, Color newColor, StringBuilder stringBuilder) { - // [your code here] + for (Shape s: shapes) { + stringBuilder.append("[" + s.getColor() + "]"); + s.setColor(newColor); + } } } From 8fd74c9da77a7b5e88e3efa1e53f8eea78c1f3e3 Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Thu, 11 Apr 2013 23:16:53 +0100 Subject: [PATCH 03/61] Add post jdk8 solution for first test of exercise 2. --- .../adoptopenjdk/lambda/tutorial/exercise2/VotingRules.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/VotingRules.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/VotingRules.java index e8fe55a..658ebd9 100644 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/VotingRules.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/VotingRules.java @@ -8,9 +8,9 @@ public class VotingRules { public static List eligibleVoters(List potentialVoters, int legalAgeOfVoting) { - // [your code here] - - return Collections.emptyList(); + return potentialVoters.stream() + .filter(p -> p.age >= legalAgeOfVoting) + .collect(Collectors.toList()); } } From 8200a87592086f98dfff2b8452ec0f8e76b04a64 Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Thu, 11 Apr 2013 23:25:07 +0100 Subject: [PATCH 04/61] Add a pre jdk 8 solution. --- .../lambda/tutorial/exercise2/VotingRules.java | 14 +++++++------- .../lambda/tutorial/Exercise_2_Test.java | 10 +++++----- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/VotingRules.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/VotingRules.java index e8fe55a..2981a5a 100644 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/VotingRules.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/VotingRules.java @@ -1,16 +1,16 @@ package org.adoptopenjdk.lambda.tutorial.exercise2; import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; public class VotingRules { public static List eligibleVoters(List potentialVoters, int legalAgeOfVoting) { - // [your code here] - - - return Collections.emptyList(); + List eligible = new ArrayList<>(); + for (Person p: potentialVoters) { + if (p.age >= legalAgeOfVoting) { + eligible.add(p); + } + } + return eligible; } } diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java index 341a461..b08f6d0 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java @@ -16,6 +16,7 @@ import static org.hamcrest.Matchers.everyItem; import static org.hamcrest.Matchers.hasSize; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -106,11 +107,10 @@ public class Exercise_2_Test { */ @Test public void getAllPersonsEligibleToVote() { - Person tom = new Person("Tom", 24); - Person dick = new Person("Dick", 75); - Person harry = new Person("Harry", 17); - - List potentialVoters = Arrays.asList(tom, dick, harry); + List potentialVoters = new ArrayList<>(); + potentialVoters.add(new Person("Tom", 24)); + potentialVoters.add(new Person("Dick", 75)); + potentialVoters.add(new Person("Harry", 17)); int legalAgeOfVoting = 18; List eligibleVoters = VotingRules.eligibleVoters(potentialVoters, legalAgeOfVoting); From 3c5bb2ab24c7b20141434e353804cd543ba1035c Mon Sep 17 00:00:00 2001 From: Grundlefleck Date: Sat, 13 Apr 2013 00:11:32 +0100 Subject: [PATCH 05/61] Add a second example for the filtering & collecting test. --- .../tutorial/exercise2/ElectoralDistrict.java | 33 ++++++++ .../tutorial/exercise2/RegisteredVoter.java | 31 ++++++++ .../lambda/tutorial/Exercise_2_Test.java | 76 ++++++++++++++----- 3 files changed, 123 insertions(+), 17 deletions(-) create mode 100644 src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java create mode 100644 src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/RegisteredVoter.java diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java new file mode 100644 index 0000000..2df69ec --- /dev/null +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java @@ -0,0 +1,33 @@ +package org.adoptopenjdk.lambda.tutorial.exercise2; + +import java.util.Collection; +import java.util.Collections; +import java.util.Set; + +/** + * Lambda Tutorial -- Adopt Open JDK + * + * @author Graham Allan grundlefleck at gmail dot com + */ +public enum ElectoralDistrict { + + // Some (inaccurate) London electrical districts + CROYDON("CR"), + BARKING("BA"), + HACKNEY("HA"), + EDMONTON("ED"); + + // ... ~ 650 more for the UK + + public final String prefix; + + ElectoralDistrict(String prefix) { + this.prefix = prefix; + } + + public static Set votersIn(ElectoralDistrict district, Collection voters) { + // [your code here] + + return Collections.emptySet(); + } +} diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/RegisteredVoter.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/RegisteredVoter.java new file mode 100644 index 0000000..f22904b --- /dev/null +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/RegisteredVoter.java @@ -0,0 +1,31 @@ +package org.adoptopenjdk.lambda.tutorial.exercise2; + +/** + * Lambda Tutorial -- Adopt Open JDK + * + * @author Graham Allan grundlefleck at gmail dot com + */ +public final class RegisteredVoter { + public final String electorId; + + public RegisteredVoter(String electorId) { + this.electorId = electorId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + RegisteredVoter that = (RegisteredVoter) o; + + if (!electorId.equals(that.electorId)) return false; + + return true; + } + + @Override + public int hashCode() { + return electorId.hashCode(); + } +} diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java index 341a461..a946b88 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java @@ -1,29 +1,29 @@ package org.adoptopenjdk.lambda.tutorial; -import org.adoptopenjdk.lambda.tutorial.exercise1.Color; -import org.adoptopenjdk.lambda.tutorial.exercise1.Shape; +import org.adoptopenjdk.lambda.tutorial.exercise2.ElectoralDistrict; import org.adoptopenjdk.lambda.tutorial.exercise2.Person; +import org.adoptopenjdk.lambda.tutorial.exercise2.RegisteredVoter; import org.adoptopenjdk.lambda.tutorial.exercise2.VotingRules; import org.hamcrest.FeatureMatcher; import org.hamcrest.Matcher; import org.hamcrest.Matchers; import org.junit.Test; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.everyItem; -import static org.hamcrest.Matchers.hasSize; - -import java.util.Arrays; +import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collector; import java.util.stream.Collectors; import java.util.stream.Stream; +import static java.util.Arrays.asList; +import static org.adoptopenjdk.lambda.tutorial.exercise2.ElectoralDistrict.HACKNEY; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.hasSize; + /** * Exercise 2 - Filtering and Collecting * @@ -100,17 +100,14 @@ public class Exercise_2_Test { /** - * Use List.filter() to produce a list containing only those Persons eligible to vote. - * + * Use Stream.filter() to produce a list containing only those Persons eligible to vote. * + * @see Person#age */ @Test public void getAllPersonsEligibleToVote() { - Person tom = new Person("Tom", 24); - Person dick = new Person("Dick", 75); - Person harry = new Person("Harry", 17); - - List potentialVoters = Arrays.asList(tom, dick, harry); + List potentialVoters = + new ArrayList<>(asList(new Person("Tom", 24), new Person("Dick", 75), new Person("Harry", 17))); int legalAgeOfVoting = 18; List eligibleVoters = VotingRules.eligibleVoters(potentialVoters, legalAgeOfVoting); @@ -123,6 +120,43 @@ public void getAllPersonsEligibleToVote() { assertThat(potentialVoters, containsInAnyOrder(aPersonNamed("Tom"), aPersonNamed("Dick"), aPersonNamed("Harry"))); } + /** + * Uses Stream.filter() to find all the voters residing in a given district. + * + * The resulting collection is to be used for quick lookups to find if a given + * voter resides in a district. Performance measurements indicate we should + * prefer the result to be a java.util.Set. Use Stream.collect() to produce a + * Set containing the result, rather than a List. + * + * HINT: sometimes type inference is "not there yet", help out the compiler + * with explicit generic arguments if you have to. + * + * @see Stream#collect(java.util.stream.Collector) + * @see java.util.stream.Collectors#toSet() + * + * @see ElectoralDistrict#prefix + * @see RegisteredVoter#electorId + */ + @Test public void setOfVotersInDistrict() { + List allVoters = new ArrayList<>(asList( + new RegisteredVoter("CR2345"), + new RegisteredVoter("HA7654"), + new RegisteredVoter("HA2213"), + new RegisteredVoter("BA9987"), + new RegisteredVoter("CR6203"), + new RegisteredVoter("ED9876") + // ... and many more + )); + + Set votersInHackney = ElectoralDistrict.votersIn(HACKNEY, allVoters); + + assertThat(votersInHackney, hasSize(2)); + assertThat(votersInHackney, containsInAnyOrder(aVoterWithId("HA7654"), aVoterWithId("HA2213"))); + } + + + // Test helpers + private static Matcher aPersonNamed(String name) { return new FeatureMatcher(Matchers.is(name), "is a person", "name") { @Override protected String featureValueOf(Person person) { @@ -130,4 +164,12 @@ private static Matcher aPersonNamed(String name) { } }; } + + private static Matcher aVoterWithId(String name) { + return new FeatureMatcher(Matchers.is(name), "is a voter", "electorId") { + @Override protected String featureValueOf(RegisteredVoter voter) { + return voter.electorId; + } + }; + } } From d75a0c0f87808c2c50cdaef53a3e86398b5f1b6c Mon Sep 17 00:00:00 2001 From: Grundlefleck Date: Sat, 13 Apr 2013 00:30:06 +0100 Subject: [PATCH 06/61] Reinstate accidentally removed jdk8 solution for exercise 1. --- .../org/adoptopenjdk/lambda/tutorial/exercise1/Shapes.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise1/Shapes.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise1/Shapes.java index 6978e32..1839713 100644 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise1/Shapes.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise1/Shapes.java @@ -13,14 +13,15 @@ public class Shapes { public static void colorAll(List shapes, Color newColor) { - // [your code here] + shapes.forEach(s -> s.setColor(newColor)); } public static void makeStringOfAllColors(List shapes, StringBuilder stringBuilder) { - // [your code here] + shapes.forEach(s -> stringBuilder.append(s)); } public static void changeColorAndMakeStringOfOldColors(List shapes, Color newColor, StringBuilder stringBuilder) { - // [your code here] + shapes.forEach(s -> { stringBuilder.append("[" + s.getColor() + "]"); s.setColor(newColor); }); + } } From 2b22244515ef34d954bff9754955782764c1c882 Mon Sep 17 00:00:00 2001 From: Grundlefleck Date: Sat, 13 Apr 2013 00:31:33 +0100 Subject: [PATCH 07/61] Add a jdk 8 solution for the second test. --- .../lambda/tutorial/exercise2/ElectoralDistrict.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java index 2df69ec..975a221 100644 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java @@ -3,6 +3,7 @@ import java.util.Collection; import java.util.Collections; import java.util.Set; +import java.util.stream.Collectors; /** * Lambda Tutorial -- Adopt Open JDK @@ -26,8 +27,8 @@ public enum ElectoralDistrict { } public static Set votersIn(ElectoralDistrict district, Collection voters) { - // [your code here] - - return Collections.emptySet(); + return voters.stream() + .filter(v -> v.electorId.startsWith(district.prefix)) + .collect(Collectors.toSet()); } } From a8dc95d4056b66cfbf4f3ae94fff0e236d67a960 Mon Sep 17 00:00:00 2001 From: Grundlefleck Date: Sat, 13 Apr 2013 00:11:32 +0100 Subject: [PATCH 08/61] Merge latest test from master. --- .../tutorial/exercise2/ElectoralDistrict.java | 33 +++++++++ .../tutorial/exercise2/RegisteredVoter.java | 31 ++++++++ .../lambda/tutorial/Exercise_2_Test.java | 74 +++++++++++++++---- 3 files changed, 122 insertions(+), 16 deletions(-) create mode 100644 src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java create mode 100644 src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/RegisteredVoter.java diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java new file mode 100644 index 0000000..2df69ec --- /dev/null +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java @@ -0,0 +1,33 @@ +package org.adoptopenjdk.lambda.tutorial.exercise2; + +import java.util.Collection; +import java.util.Collections; +import java.util.Set; + +/** + * Lambda Tutorial -- Adopt Open JDK + * + * @author Graham Allan grundlefleck at gmail dot com + */ +public enum ElectoralDistrict { + + // Some (inaccurate) London electrical districts + CROYDON("CR"), + BARKING("BA"), + HACKNEY("HA"), + EDMONTON("ED"); + + // ... ~ 650 more for the UK + + public final String prefix; + + ElectoralDistrict(String prefix) { + this.prefix = prefix; + } + + public static Set votersIn(ElectoralDistrict district, Collection voters) { + // [your code here] + + return Collections.emptySet(); + } +} diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/RegisteredVoter.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/RegisteredVoter.java new file mode 100644 index 0000000..f22904b --- /dev/null +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/RegisteredVoter.java @@ -0,0 +1,31 @@ +package org.adoptopenjdk.lambda.tutorial.exercise2; + +/** + * Lambda Tutorial -- Adopt Open JDK + * + * @author Graham Allan grundlefleck at gmail dot com + */ +public final class RegisteredVoter { + public final String electorId; + + public RegisteredVoter(String electorId) { + this.electorId = electorId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + RegisteredVoter that = (RegisteredVoter) o; + + if (!electorId.equals(that.electorId)) return false; + + return true; + } + + @Override + public int hashCode() { + return electorId.hashCode(); + } +} diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java index b08f6d0..a946b88 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java @@ -1,30 +1,29 @@ package org.adoptopenjdk.lambda.tutorial; -import org.adoptopenjdk.lambda.tutorial.exercise1.Color; -import org.adoptopenjdk.lambda.tutorial.exercise1.Shape; +import org.adoptopenjdk.lambda.tutorial.exercise2.ElectoralDistrict; import org.adoptopenjdk.lambda.tutorial.exercise2.Person; +import org.adoptopenjdk.lambda.tutorial.exercise2.RegisteredVoter; import org.adoptopenjdk.lambda.tutorial.exercise2.VotingRules; import org.hamcrest.FeatureMatcher; import org.hamcrest.Matcher; import org.hamcrest.Matchers; import org.junit.Test; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.everyItem; -import static org.hamcrest.Matchers.hasSize; - import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collector; import java.util.stream.Collectors; import java.util.stream.Stream; +import static java.util.Arrays.asList; +import static org.adoptopenjdk.lambda.tutorial.exercise2.ElectoralDistrict.HACKNEY; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.hasSize; + /** * Exercise 2 - Filtering and Collecting * @@ -101,16 +100,14 @@ public class Exercise_2_Test { /** - * Use List.filter() to produce a list containing only those Persons eligible to vote. - * + * Use Stream.filter() to produce a list containing only those Persons eligible to vote. * + * @see Person#age */ @Test public void getAllPersonsEligibleToVote() { - List potentialVoters = new ArrayList<>(); - potentialVoters.add(new Person("Tom", 24)); - potentialVoters.add(new Person("Dick", 75)); - potentialVoters.add(new Person("Harry", 17)); + List potentialVoters = + new ArrayList<>(asList(new Person("Tom", 24), new Person("Dick", 75), new Person("Harry", 17))); int legalAgeOfVoting = 18; List eligibleVoters = VotingRules.eligibleVoters(potentialVoters, legalAgeOfVoting); @@ -123,6 +120,43 @@ public void getAllPersonsEligibleToVote() { assertThat(potentialVoters, containsInAnyOrder(aPersonNamed("Tom"), aPersonNamed("Dick"), aPersonNamed("Harry"))); } + /** + * Uses Stream.filter() to find all the voters residing in a given district. + * + * The resulting collection is to be used for quick lookups to find if a given + * voter resides in a district. Performance measurements indicate we should + * prefer the result to be a java.util.Set. Use Stream.collect() to produce a + * Set containing the result, rather than a List. + * + * HINT: sometimes type inference is "not there yet", help out the compiler + * with explicit generic arguments if you have to. + * + * @see Stream#collect(java.util.stream.Collector) + * @see java.util.stream.Collectors#toSet() + * + * @see ElectoralDistrict#prefix + * @see RegisteredVoter#electorId + */ + @Test public void setOfVotersInDistrict() { + List allVoters = new ArrayList<>(asList( + new RegisteredVoter("CR2345"), + new RegisteredVoter("HA7654"), + new RegisteredVoter("HA2213"), + new RegisteredVoter("BA9987"), + new RegisteredVoter("CR6203"), + new RegisteredVoter("ED9876") + // ... and many more + )); + + Set votersInHackney = ElectoralDistrict.votersIn(HACKNEY, allVoters); + + assertThat(votersInHackney, hasSize(2)); + assertThat(votersInHackney, containsInAnyOrder(aVoterWithId("HA7654"), aVoterWithId("HA2213"))); + } + + + // Test helpers + private static Matcher aPersonNamed(String name) { return new FeatureMatcher(Matchers.is(name), "is a person", "name") { @Override protected String featureValueOf(Person person) { @@ -130,4 +164,12 @@ private static Matcher aPersonNamed(String name) { } }; } + + private static Matcher aVoterWithId(String name) { + return new FeatureMatcher(Matchers.is(name), "is a voter", "electorId") { + @Override protected String featureValueOf(RegisteredVoter voter) { + return voter.electorId; + } + }; + } } From c40f4167a1eb5003c274cdbe2c0db2beec4f4a5c Mon Sep 17 00:00:00 2001 From: Grundlefleck Date: Sat, 13 Apr 2013 00:36:30 +0100 Subject: [PATCH 09/61] Add pre jdk8 solution for exercise 2 test 2. --- .../lambda/tutorial/exercise2/ElectoralDistrict.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java index 2df69ec..91dd030 100644 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java @@ -2,6 +2,7 @@ import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.Set; /** @@ -26,8 +27,13 @@ public enum ElectoralDistrict { } public static Set votersIn(ElectoralDistrict district, Collection voters) { - // [your code here] - - return Collections.emptySet(); + Set votersInDistrict = new HashSet<>(); + for (RegisteredVoter v: voters) { + if (v.electorId.startsWith(district.prefix)) { + votersInDistrict.add(v); + } + } + + return votersInDistrict; } } From 54f216142fdbb4f454b9aa56d8a798caf3bbd91f Mon Sep 17 00:00:00 2001 From: Grundlefleck Date: Sun, 21 Apr 2013 12:37:57 +0100 Subject: [PATCH 10/61] Add another filter example. --- .../lambda/tutorial/exercise2/Ballot.java | 32 ++++++++ .../tutorial/exercise2/ElectoralDistrict.java | 25 +++++- .../lambda/tutorial/exercise2/Party.java | 14 ++++ .../tutorial/exercise2/RegisteredVoter.java | 7 ++ .../lambda/tutorial/Exercise_2_Test.java | 81 +++++++++++++++++-- 5 files changed, 148 insertions(+), 11 deletions(-) create mode 100644 src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/Ballot.java create mode 100644 src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/Party.java diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/Ballot.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/Ballot.java new file mode 100644 index 0000000..71f31be --- /dev/null +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/Ballot.java @@ -0,0 +1,32 @@ +package org.adoptopenjdk.lambda.tutorial.exercise2; + +/** + * Lambda Tutorial -- Adopt Open JDK + * + * @author Graham Allan grundlefleck at gmail dot com + */ +public final class Ballot { + public final boolean isSpoiled; + public final Party party; + + private Ballot(Party party) { + this.party = party; + this.isSpoiled = party == null; + } + + @Override + public String toString() { + return "Ballot{" + + "isSpoiled=" + isSpoiled + + ", party=" + party + + '}'; + } + + public static Ballot voteFor(Party party) { + return new Ballot(party); + } + + public static Ballot spoiled() { + return new Ballot(null); + } +} diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java index 975a221..94e182a 100644 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java @@ -1,9 +1,21 @@ package org.adoptopenjdk.lambda.tutorial.exercise2; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; +import java.util.List; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collector; import java.util.stream.Collectors; +import java.util.stream.DelegatingStream; +import java.util.stream.Stream; /** * Lambda Tutorial -- Adopt Open JDK @@ -27,8 +39,15 @@ public enum ElectoralDistrict { } public static Set votersIn(ElectoralDistrict district, Collection voters) { - return voters.stream() - .filter(v -> v.electorId.startsWith(district.prefix)) - .collect(Collectors.toSet()); + // [your code here] + + return Collections.emptySet(); + } + + public static Set unspoiledBallots(Set votes) { + // [your code here] + + return Collections.emptySet(); } } + diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/Party.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/Party.java new file mode 100644 index 0000000..6e4a63d --- /dev/null +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/Party.java @@ -0,0 +1,14 @@ +package org.adoptopenjdk.lambda.tutorial.exercise2; + +/** + * Lambda Tutorial -- Adopt Open JDK + * + * @author Graham Allan grundlefleck at gmail dot com + */ +public enum Party { + CONSERVATIVE, + LABOUR, + LIBERAL_DEMOCRATS, + GREEN_PARTY, + MONSTER_RAVING_LOONY_PARTY +} diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/RegisteredVoter.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/RegisteredVoter.java index f22904b..b2ef97d 100644 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/RegisteredVoter.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/RegisteredVoter.java @@ -28,4 +28,11 @@ public boolean equals(Object o) { public int hashCode() { return electorId.hashCode(); } + + @Override + public String toString() { + return "RegisteredVoter{" + + "electorId='" + electorId + '\'' + + '}'; + } } diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java index a946b88..9ab5fae 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java @@ -1,6 +1,8 @@ package org.adoptopenjdk.lambda.tutorial; +import org.adoptopenjdk.lambda.tutorial.exercise2.Ballot; import org.adoptopenjdk.lambda.tutorial.exercise2.ElectoralDistrict; +import org.adoptopenjdk.lambda.tutorial.exercise2.Party; import org.adoptopenjdk.lambda.tutorial.exercise2.Person; import org.adoptopenjdk.lambda.tutorial.exercise2.RegisteredVoter; import org.adoptopenjdk.lambda.tutorial.exercise2.VotingRules; @@ -11,18 +13,28 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collector; import java.util.stream.Collectors; import java.util.stream.Stream; import static java.util.Arrays.asList; +import static java.util.Arrays.binarySearch; import static org.adoptopenjdk.lambda.tutorial.exercise2.ElectoralDistrict.HACKNEY; +import static org.adoptopenjdk.lambda.tutorial.exercise2.ElectoralDistrict.votersIn; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.everyItem; import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; /** * Exercise 2 - Filtering and Collecting @@ -154,22 +166,75 @@ public void getAllPersonsEligibleToVote() { assertThat(votersInHackney, containsInAnyOrder(aVoterWithId("HA7654"), aVoterWithId("HA2213"))); } + @Test public void removeAllSpoiledBallots() { + Set votes = new HashSet<>(asList( + Ballot.voteFor(Party.LABOUR), + Ballot.voteFor(Party.CONSERVATIVE), + Ballot.spoiled(), + Ballot.voteFor(Party.MONSTER_RAVING_LOONY_PARTY), + Ballot.voteFor(Party.LIBERAL_DEMOCRATS), + Ballot.spoiled(), + Ballot.voteFor(Party.GREEN_PARTY), + Ballot.voteFor(Party.GREEN_PARTY) + // ... and many more + )); + + Set unspoiledBallots = ElectoralDistrict.unspoiledBallots(votes); + + assertThat(unspoiledBallots, hasSize(6)); + assertThat(unspoiledBallots, everyItem(is(not(spoiled())))); + } + + /** + * Ensure that the Set returned cannot be modified by callers by wrapping the result + * in Collections.unmodifiableSet(). + * + * The Streams API does not provide a way to wrap the final result, as one of its operations. So just wrap the + * result in an unmodifiableSet yourself. Sometimes it's just as important to know what an API _doesn't_ do. + * + * @see Stream#collect(java.util.stream.Collector) + * @see java.util.stream.Collectors#toSet() + * @see Collections#unmodifiableSet(java.util.Set) + */ + @Test public void setOfVotersInDistrictInUnmodifiableSet() throws ClassNotFoundException { + List allVoters = new ArrayList<>(asList( + new RegisteredVoter("CR2345"), + new RegisteredVoter("HA7654"), + new RegisteredVoter("HA2213"), + new RegisteredVoter("BA9987"), + new RegisteredVoter("CR6203"), + new RegisteredVoter("ED9876") + // ... and many more + )); + + Set votersInHackney = ElectoralDistrict.votersIn(HACKNEY, allVoters); + + assertThat(votersInHackney, instanceOf(Class.forName("java.util.Collections$UnmodifiableSet"))); + } // Test helpers private static Matcher aPersonNamed(String name) { - return new FeatureMatcher(Matchers.is(name), "is a person", "name") { - @Override protected String featureValueOf(Person person) { - return person.name; - } - }; + return featureMatcher(is(name), "a person", "name", p -> p.name); } private static Matcher aVoterWithId(String name) { - return new FeatureMatcher(Matchers.is(name), "is a voter", "electorId") { - @Override protected String featureValueOf(RegisteredVoter voter) { - return voter.electorId; + return featureMatcher(is(name), "a voter", "electorId", v -> v.electorId); + } + + private static Matcher spoiled() { + return featureMatcher(equalTo(true), "a spoiled ballot", "isSpoiled", b -> b.isSpoiled); + } + + private static Matcher featureMatcher(Matcher featureMatcher, + String description, + String name, + Function extractor) { + return new FeatureMatcher(featureMatcher, description, name) { + @Override protected FEATURE featureValueOf(FROM t) { + return extractor.apply(t); } }; } + } From e774fbe08158de005bbc855aa1bcd113bf73a3aa Mon Sep 17 00:00:00 2001 From: Grundlefleck Date: Sun, 21 Apr 2013 12:37:57 +0100 Subject: [PATCH 11/61] Add another filter example. --- .../lambda/tutorial/exercise2/Ballot.java | 32 ++++++++ .../tutorial/exercise2/ElectoralDistrict.java | 19 +++++ .../lambda/tutorial/exercise2/Party.java | 14 ++++ .../tutorial/exercise2/RegisteredVoter.java | 7 ++ .../lambda/tutorial/Exercise_2_Test.java | 81 +++++++++++++++++-- 5 files changed, 145 insertions(+), 8 deletions(-) create mode 100644 src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/Ballot.java create mode 100644 src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/Party.java diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/Ballot.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/Ballot.java new file mode 100644 index 0000000..71f31be --- /dev/null +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/Ballot.java @@ -0,0 +1,32 @@ +package org.adoptopenjdk.lambda.tutorial.exercise2; + +/** + * Lambda Tutorial -- Adopt Open JDK + * + * @author Graham Allan grundlefleck at gmail dot com + */ +public final class Ballot { + public final boolean isSpoiled; + public final Party party; + + private Ballot(Party party) { + this.party = party; + this.isSpoiled = party == null; + } + + @Override + public String toString() { + return "Ballot{" + + "isSpoiled=" + isSpoiled + + ", party=" + party + + '}'; + } + + public static Ballot voteFor(Party party) { + return new Ballot(party); + } + + public static Ballot spoiled() { + return new Ballot(null); + } +} diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java index 91dd030..91d551e 100644 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java @@ -1,9 +1,21 @@ package org.adoptopenjdk.lambda.tutorial.exercise2; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import java.util.stream.DelegatingStream; +import java.util.stream.Stream; /** * Lambda Tutorial -- Adopt Open JDK @@ -36,4 +48,11 @@ public static Set votersIn(ElectoralDistrict district, Collecti return votersInDistrict; } + + public static Set unspoiledBallots(Set votes) { + // [your code here] + + return Collections.emptySet(); + } } + diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/Party.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/Party.java new file mode 100644 index 0000000..6e4a63d --- /dev/null +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/Party.java @@ -0,0 +1,14 @@ +package org.adoptopenjdk.lambda.tutorial.exercise2; + +/** + * Lambda Tutorial -- Adopt Open JDK + * + * @author Graham Allan grundlefleck at gmail dot com + */ +public enum Party { + CONSERVATIVE, + LABOUR, + LIBERAL_DEMOCRATS, + GREEN_PARTY, + MONSTER_RAVING_LOONY_PARTY +} diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/RegisteredVoter.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/RegisteredVoter.java index f22904b..b2ef97d 100644 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/RegisteredVoter.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/RegisteredVoter.java @@ -28,4 +28,11 @@ public boolean equals(Object o) { public int hashCode() { return electorId.hashCode(); } + + @Override + public String toString() { + return "RegisteredVoter{" + + "electorId='" + electorId + '\'' + + '}'; + } } diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java index a946b88..9ab5fae 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java @@ -1,6 +1,8 @@ package org.adoptopenjdk.lambda.tutorial; +import org.adoptopenjdk.lambda.tutorial.exercise2.Ballot; import org.adoptopenjdk.lambda.tutorial.exercise2.ElectoralDistrict; +import org.adoptopenjdk.lambda.tutorial.exercise2.Party; import org.adoptopenjdk.lambda.tutorial.exercise2.Person; import org.adoptopenjdk.lambda.tutorial.exercise2.RegisteredVoter; import org.adoptopenjdk.lambda.tutorial.exercise2.VotingRules; @@ -11,18 +13,28 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collector; import java.util.stream.Collectors; import java.util.stream.Stream; import static java.util.Arrays.asList; +import static java.util.Arrays.binarySearch; import static org.adoptopenjdk.lambda.tutorial.exercise2.ElectoralDistrict.HACKNEY; +import static org.adoptopenjdk.lambda.tutorial.exercise2.ElectoralDistrict.votersIn; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.everyItem; import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; /** * Exercise 2 - Filtering and Collecting @@ -154,22 +166,75 @@ public void getAllPersonsEligibleToVote() { assertThat(votersInHackney, containsInAnyOrder(aVoterWithId("HA7654"), aVoterWithId("HA2213"))); } + @Test public void removeAllSpoiledBallots() { + Set votes = new HashSet<>(asList( + Ballot.voteFor(Party.LABOUR), + Ballot.voteFor(Party.CONSERVATIVE), + Ballot.spoiled(), + Ballot.voteFor(Party.MONSTER_RAVING_LOONY_PARTY), + Ballot.voteFor(Party.LIBERAL_DEMOCRATS), + Ballot.spoiled(), + Ballot.voteFor(Party.GREEN_PARTY), + Ballot.voteFor(Party.GREEN_PARTY) + // ... and many more + )); + + Set unspoiledBallots = ElectoralDistrict.unspoiledBallots(votes); + + assertThat(unspoiledBallots, hasSize(6)); + assertThat(unspoiledBallots, everyItem(is(not(spoiled())))); + } + + /** + * Ensure that the Set returned cannot be modified by callers by wrapping the result + * in Collections.unmodifiableSet(). + * + * The Streams API does not provide a way to wrap the final result, as one of its operations. So just wrap the + * result in an unmodifiableSet yourself. Sometimes it's just as important to know what an API _doesn't_ do. + * + * @see Stream#collect(java.util.stream.Collector) + * @see java.util.stream.Collectors#toSet() + * @see Collections#unmodifiableSet(java.util.Set) + */ + @Test public void setOfVotersInDistrictInUnmodifiableSet() throws ClassNotFoundException { + List allVoters = new ArrayList<>(asList( + new RegisteredVoter("CR2345"), + new RegisteredVoter("HA7654"), + new RegisteredVoter("HA2213"), + new RegisteredVoter("BA9987"), + new RegisteredVoter("CR6203"), + new RegisteredVoter("ED9876") + // ... and many more + )); + + Set votersInHackney = ElectoralDistrict.votersIn(HACKNEY, allVoters); + + assertThat(votersInHackney, instanceOf(Class.forName("java.util.Collections$UnmodifiableSet"))); + } // Test helpers private static Matcher aPersonNamed(String name) { - return new FeatureMatcher(Matchers.is(name), "is a person", "name") { - @Override protected String featureValueOf(Person person) { - return person.name; - } - }; + return featureMatcher(is(name), "a person", "name", p -> p.name); } private static Matcher aVoterWithId(String name) { - return new FeatureMatcher(Matchers.is(name), "is a voter", "electorId") { - @Override protected String featureValueOf(RegisteredVoter voter) { - return voter.electorId; + return featureMatcher(is(name), "a voter", "electorId", v -> v.electorId); + } + + private static Matcher spoiled() { + return featureMatcher(equalTo(true), "a spoiled ballot", "isSpoiled", b -> b.isSpoiled); + } + + private static Matcher featureMatcher(Matcher featureMatcher, + String description, + String name, + Function extractor) { + return new FeatureMatcher(featureMatcher, description, name) { + @Override protected FEATURE featureValueOf(FROM t) { + return extractor.apply(t); } }; } + } From f3eb2e29c3753c86f4e554611c5ad199d8cfc89c Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Tue, 23 Apr 2013 21:48:12 +0100 Subject: [PATCH 12/61] clarify what I'm talking about when I say inference 'is no there yet' --- .../java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java index 9ab5fae..e2b854d 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java @@ -140,7 +140,7 @@ public void getAllPersonsEligibleToVote() { * prefer the result to be a java.util.Set. Use Stream.collect() to produce a * Set containing the result, rather than a List. * - * HINT: sometimes type inference is "not there yet", help out the compiler + * HINT: sometimes type inference is "not there yet", in either the IDE or javac, help out the compiler * with explicit generic arguments if you have to. * * @see Stream#collect(java.util.stream.Collector) From c74dbbb113fd6fda28c35b40839477583afe4877 Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Tue, 23 Apr 2013 21:48:12 +0100 Subject: [PATCH 13/61] clarify what I'm talking about when I say inference 'is no there yet' --- .../java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java index 9ab5fae..e2b854d 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java @@ -140,7 +140,7 @@ public void getAllPersonsEligibleToVote() { * prefer the result to be a java.util.Set. Use Stream.collect() to produce a * Set containing the result, rather than a List. * - * HINT: sometimes type inference is "not there yet", help out the compiler + * HINT: sometimes type inference is "not there yet", in either the IDE or javac, help out the compiler * with explicit generic arguments if you have to. * * @see Stream#collect(java.util.stream.Collector) From ef2bca3b73f6f641e3b0ac917428c0c42341a6cf Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Tue, 23 Apr 2013 21:59:19 +0100 Subject: [PATCH 14/61] Add pre java 8 solution for filtering unspoiled ballots. --- .../tutorial/exercise2/ElectoralDistrict.java | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java index 91d551e..bbb3e48 100644 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java @@ -1,21 +1,9 @@ package org.adoptopenjdk.lambda.tutorial.exercise2; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; -import java.util.List; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.BiConsumer; -import java.util.function.BiFunction; -import java.util.function.BinaryOperator; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Collector; -import java.util.stream.Collectors; -import java.util.stream.DelegatingStream; -import java.util.stream.Stream; /** * Lambda Tutorial -- Adopt Open JDK @@ -46,13 +34,18 @@ public static Set votersIn(ElectoralDistrict district, Collecti } } - return votersInDistrict; + return Collections.unmodifiableSet(votersInDistrict); } public static Set unspoiledBallots(Set votes) { - // [your code here] + Set unspoiledBallots = new HashSet<>(); + for (Ballot v: votes) { + if (!v.isSpoiled) { + unspoiledBallots.add(v); + } + } - return Collections.emptySet(); + return unspoiledBallots; } } From 026679959209c4aafb8a6bbf702f6a30d44f9308 Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Tue, 23 Apr 2013 22:04:42 +0100 Subject: [PATCH 15/61] Add lambda solutions for exercise 2. --- .../tutorial/exercise2/ElectoralDistrict.java | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java index 94e182a..39559bf 100644 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java @@ -1,21 +1,9 @@ package org.adoptopenjdk.lambda.tutorial.exercise2; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashSet; -import java.util.List; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.BiConsumer; -import java.util.function.BiFunction; -import java.util.function.BinaryOperator; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Collector; import java.util.stream.Collectors; -import java.util.stream.DelegatingStream; -import java.util.stream.Stream; /** * Lambda Tutorial -- Adopt Open JDK @@ -39,15 +27,18 @@ public enum ElectoralDistrict { } public static Set votersIn(ElectoralDistrict district, Collection voters) { - // [your code here] + Set fromDistrict = voters.stream() + .filter(v -> v.electorId.startsWith(district.prefix)) + .collect(Collectors.toSet()); - return Collections.emptySet(); + return Collections.unmodifiableSet(fromDistrict); } public static Set unspoiledBallots(Set votes) { - // [your code here] + return votes.stream() + .filter(v -> !v.isSpoiled) + .collect(Collectors.toSet()); - return Collections.emptySet(); } } From c8f424c23e759139b287cebf93e6fac0c28eba5e Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Tue, 23 Apr 2013 22:08:28 +0100 Subject: [PATCH 16/61] Add javadoc for the final unit test for filtering. --- .../org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java index e2b854d..8a12668 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java @@ -133,7 +133,7 @@ public void getAllPersonsEligibleToVote() { } /** - * Uses Stream.filter() to find all the voters residing in a given district. + * Use Stream.filter() to find all the voters residing in a given district. * * The resulting collection is to be used for quick lookups to find if a given * voter resides in a district. Performance measurements indicate we should @@ -166,6 +166,12 @@ public void getAllPersonsEligibleToVote() { assertThat(votersInHackney, containsInAnyOrder(aVoterWithId("HA7654"), aVoterWithId("HA2213"))); } + /** + * Use Stream.filter() to remove all the ballots that have been spoiled. + * + * @see ElectoralDistrict#unspoiledBallots(Set) + * @see Ballot#isSpoiled + */ @Test public void removeAllSpoiledBallots() { Set votes = new HashSet<>(asList( Ballot.voteFor(Party.LABOUR), From ddf78a83ff40a5f33697042fec17850ee62a08cb Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Tue, 23 Apr 2013 22:08:28 +0100 Subject: [PATCH 17/61] Add javadoc for the final unit test for filtering. --- .../org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java index e2b854d..8a12668 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java @@ -133,7 +133,7 @@ public void getAllPersonsEligibleToVote() { } /** - * Uses Stream.filter() to find all the voters residing in a given district. + * Use Stream.filter() to find all the voters residing in a given district. * * The resulting collection is to be used for quick lookups to find if a given * voter resides in a district. Performance measurements indicate we should @@ -166,6 +166,12 @@ public void getAllPersonsEligibleToVote() { assertThat(votersInHackney, containsInAnyOrder(aVoterWithId("HA7654"), aVoterWithId("HA2213"))); } + /** + * Use Stream.filter() to remove all the ballots that have been spoiled. + * + * @see ElectoralDistrict#unspoiledBallots(Set) + * @see Ballot#isSpoiled + */ @Test public void removeAllSpoiledBallots() { Set votes = new HashSet<>(asList( Ballot.voteFor(Party.LABOUR), From c46911871b44c61bc6c39674188ed406399c450d Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Tue, 7 May 2013 19:20:33 +0100 Subject: [PATCH 18/61] Replace all anonymous implementations of FeatureMatcher with call to factory method given an extractor function. --- .../lambda/tutorial/Exercise_1_Test.java | 8 ++----- .../lambda/tutorial/Exercise_2_Test.java | 24 ++++--------------- .../lambda/tutorial/util/FeatureMatchers.java | 23 ++++++++++++++++++ 3 files changed, 30 insertions(+), 25 deletions(-) create mode 100644 src/test/java/org/adoptopenjdk/lambda/tutorial/util/FeatureMatchers.java diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_1_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_1_Test.java index a556cad..8330b2b 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_1_Test.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_1_Test.java @@ -4,7 +4,7 @@ import org.adoptopenjdk.lambda.tutorial.exercise1.Color; import org.adoptopenjdk.lambda.tutorial.exercise1.Shape; import org.adoptopenjdk.lambda.tutorial.exercise1.Shapes; -import org.hamcrest.FeatureMatcher; +import org.adoptopenjdk.lambda.tutorial.util.FeatureMatchers; import org.hamcrest.Matcher; import org.hamcrest.Matchers; import org.junit.Test; @@ -139,11 +139,7 @@ public void changeColorOfAllShapes_AND_buildStringShowingAllTheOldColors() { // Test helpers private static Matcher hasColor(Color color) { - return new FeatureMatcher(Matchers.is(color), "has color", "color") { - @Override protected Color featureValueOf(Shape shape) { - return shape.getColor(); - } - }; + return FeatureMatchers.from(Matchers.is(color), "has color", "color", Shape::getColor); } } diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java index 8a12668..19e9594 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java @@ -6,9 +6,8 @@ import org.adoptopenjdk.lambda.tutorial.exercise2.Person; import org.adoptopenjdk.lambda.tutorial.exercise2.RegisteredVoter; import org.adoptopenjdk.lambda.tutorial.exercise2.VotingRules; -import org.hamcrest.FeatureMatcher; +import org.adoptopenjdk.lambda.tutorial.util.FeatureMatchers; import org.hamcrest.Matcher; -import org.hamcrest.Matchers; import org.junit.Test; import java.util.ArrayList; @@ -17,7 +16,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collector; import java.util.stream.Collectors; @@ -26,7 +24,6 @@ import static java.util.Arrays.asList; import static java.util.Arrays.binarySearch; import static org.adoptopenjdk.lambda.tutorial.exercise2.ElectoralDistrict.HACKNEY; -import static org.adoptopenjdk.lambda.tutorial.exercise2.ElectoralDistrict.votersIn; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.equalTo; @@ -221,26 +218,15 @@ public void getAllPersonsEligibleToVote() { // Test helpers private static Matcher aPersonNamed(String name) { - return featureMatcher(is(name), "a person", "name", p -> p.name); + return FeatureMatchers.from(is(name), "a person", "name", p -> p.name); } private static Matcher aVoterWithId(String name) { - return featureMatcher(is(name), "a voter", "electorId", v -> v.electorId); + return FeatureMatchers.from(is(name), "a voter", "electorId", v -> v.electorId); } private static Matcher spoiled() { - return featureMatcher(equalTo(true), "a spoiled ballot", "isSpoiled", b -> b.isSpoiled); + return FeatureMatchers.from(equalTo(true), "a spoiled ballot", "isSpoiled", b -> b.isSpoiled); } - private static Matcher featureMatcher(Matcher featureMatcher, - String description, - String name, - Function extractor) { - return new FeatureMatcher(featureMatcher, description, name) { - @Override protected FEATURE featureValueOf(FROM t) { - return extractor.apply(t); - } - }; - } - -} +} \ No newline at end of file diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/util/FeatureMatchers.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/util/FeatureMatchers.java new file mode 100644 index 0000000..9148d90 --- /dev/null +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/util/FeatureMatchers.java @@ -0,0 +1,23 @@ +package org.adoptopenjdk.lambda.tutorial.util; + +import org.hamcrest.FeatureMatcher; +import org.hamcrest.Matcher; + +import java.util.function.Function; + +public final class FeatureMatchers { + + private FeatureMatchers() {} + + public static Matcher from(Matcher featureMatcher, + String description, + String name, + Function extractor) { + return new FeatureMatcher(featureMatcher, description, name) { + @Override protected FEATURE featureValueOf(FROM t) { + return extractor.apply(t); + } + }; + } + +} From 2a0d0e863abad103caeb048c77e1f48bb60d83f2 Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Tue, 7 May 2013 19:20:33 +0100 Subject: [PATCH 19/61] Replace all anonymous implementations of FeatureMatcher with call to factory method given an extractor function. --- .../lambda/tutorial/Exercise_1_Test.java | 8 ++----- .../lambda/tutorial/Exercise_2_Test.java | 24 ++++--------------- .../lambda/tutorial/util/FeatureMatchers.java | 23 ++++++++++++++++++ 3 files changed, 30 insertions(+), 25 deletions(-) create mode 100644 src/test/java/org/adoptopenjdk/lambda/tutorial/util/FeatureMatchers.java diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_1_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_1_Test.java index a556cad..8330b2b 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_1_Test.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_1_Test.java @@ -4,7 +4,7 @@ import org.adoptopenjdk.lambda.tutorial.exercise1.Color; import org.adoptopenjdk.lambda.tutorial.exercise1.Shape; import org.adoptopenjdk.lambda.tutorial.exercise1.Shapes; -import org.hamcrest.FeatureMatcher; +import org.adoptopenjdk.lambda.tutorial.util.FeatureMatchers; import org.hamcrest.Matcher; import org.hamcrest.Matchers; import org.junit.Test; @@ -139,11 +139,7 @@ public void changeColorOfAllShapes_AND_buildStringShowingAllTheOldColors() { // Test helpers private static Matcher hasColor(Color color) { - return new FeatureMatcher(Matchers.is(color), "has color", "color") { - @Override protected Color featureValueOf(Shape shape) { - return shape.getColor(); - } - }; + return FeatureMatchers.from(Matchers.is(color), "has color", "color", Shape::getColor); } } diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java index 8a12668..19e9594 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java @@ -6,9 +6,8 @@ import org.adoptopenjdk.lambda.tutorial.exercise2.Person; import org.adoptopenjdk.lambda.tutorial.exercise2.RegisteredVoter; import org.adoptopenjdk.lambda.tutorial.exercise2.VotingRules; -import org.hamcrest.FeatureMatcher; +import org.adoptopenjdk.lambda.tutorial.util.FeatureMatchers; import org.hamcrest.Matcher; -import org.hamcrest.Matchers; import org.junit.Test; import java.util.ArrayList; @@ -17,7 +16,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collector; import java.util.stream.Collectors; @@ -26,7 +24,6 @@ import static java.util.Arrays.asList; import static java.util.Arrays.binarySearch; import static org.adoptopenjdk.lambda.tutorial.exercise2.ElectoralDistrict.HACKNEY; -import static org.adoptopenjdk.lambda.tutorial.exercise2.ElectoralDistrict.votersIn; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.equalTo; @@ -221,26 +218,15 @@ public void getAllPersonsEligibleToVote() { // Test helpers private static Matcher aPersonNamed(String name) { - return featureMatcher(is(name), "a person", "name", p -> p.name); + return FeatureMatchers.from(is(name), "a person", "name", p -> p.name); } private static Matcher aVoterWithId(String name) { - return featureMatcher(is(name), "a voter", "electorId", v -> v.electorId); + return FeatureMatchers.from(is(name), "a voter", "electorId", v -> v.electorId); } private static Matcher spoiled() { - return featureMatcher(equalTo(true), "a spoiled ballot", "isSpoiled", b -> b.isSpoiled); + return FeatureMatchers.from(equalTo(true), "a spoiled ballot", "isSpoiled", b -> b.isSpoiled); } - private static Matcher featureMatcher(Matcher featureMatcher, - String description, - String name, - Function extractor) { - return new FeatureMatcher(featureMatcher, description, name) { - @Override protected FEATURE featureValueOf(FROM t) { - return extractor.apply(t); - } - }; - } - -} +} \ No newline at end of file diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/util/FeatureMatchers.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/util/FeatureMatchers.java new file mode 100644 index 0000000..9148d90 --- /dev/null +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/util/FeatureMatchers.java @@ -0,0 +1,23 @@ +package org.adoptopenjdk.lambda.tutorial.util; + +import org.hamcrest.FeatureMatcher; +import org.hamcrest.Matcher; + +import java.util.function.Function; + +public final class FeatureMatchers { + + private FeatureMatchers() {} + + public static Matcher from(Matcher featureMatcher, + String description, + String name, + Function extractor) { + return new FeatureMatcher(featureMatcher, description, name) { + @Override protected FEATURE featureValueOf(FROM t) { + return extractor.apply(t); + } + }; + } + +} From 6e138441cd0a52d5f3f48ee09f691909076a33ec Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Tue, 7 May 2013 21:45:58 +0100 Subject: [PATCH 20/61] Add a third exercise: mapping. --- .../lambda/tutorial/Exercise_3_Test.java | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_3_Test.java diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_3_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_3_Test.java new file mode 100644 index 0000000..2f5ea96 --- /dev/null +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_3_Test.java @@ -0,0 +1,81 @@ +package org.adoptopenjdk.lambda.tutorial; + +import org.junit.Test; + +import java.util.Collection; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Exercise 3 - Mapping + * + * Along with filter, map is one of the most common operations to perform. + * + * Consider this idiomatic Java code used to convert a list of strings to uppercase: + * + * List upperCaseStrings = new ArrayList<>(); + * for (String s: mixedCaseStrings) { + * upperCaseStrings.add(s.toUpperCase()); + * } + * return upperCaseStrings; + * + * As with the filter operation, map removes a lot of the boilerplate of this example by pushing the common code into + * the library, rather than having it repeated in your code. In this case, we are again: + * - constructing a new, empty, destination collection + * - iterating over the source collection + * - "doing something" to each element + * - adding the result to a new collection + * + * However, unlike filter, we are not just adding elements that pass a test, we are taking the element and creating some + * new value before adding it. The new value in the example above is taking the result of toUpperCase(). The result is a + * new list, of the same length, where each element is the result of mapping the source element. In post-JDK 8, you + * can express this operation like so: + * + * return mixedCaseStrings.stream().map(s -> s.toUpperCase()).collect(Collectors.toList()); + * + * As with the last exercise, we "open up" the Streams API by calling .stream() on a collection. This allows us to + * access the map() method. + * + * The map() method takes a function (java.util.function.Function) as an argument, and applies it to each element in a + * list. Since Function is an interface with a single abstract method, JDK 8 allows us to express it as a lambda. The + * single method on the Function type takes a single parameter of a certain type and returns a single result + * of another type. In this example, the input type is String, and the return type also happens to be String. It + * could be return any type, the resultant collection will be a collection of that type. + * + * Again we're transferring into a destination list by means of the Collectors.toList() method. + * + * So what is the result of both of implementations: + * + * If the variable 'mixedCaseStrings' in the example above looked like this: + * + * ["I", "am", "really", "enjoying", "lambda-tutorial"] + * + * The resultant variable 'upperCaseStrings', would like like this: + * + * ["I", "AM", "REALLY", "ENJOYING", "LAMBDA-TUTORIAL"] + * + * An important point to consider is that the map() method does not modify the original list, mixedCaseStrings still + * exists, as it was before. This makes it much easier to prevent bugs where mixedCaseStrings could be used elsewhere, + * perhaps later in the execution, or even concurrently in a different thread. + * + * + * The map operation is also known in other languages/libraries as: transform; collect. + * + * @see Collection#stream() + * @see Stream#map(Function) + * @see Function + * @see Collector + * @see Collectors + * @see Collectors#toList() + * + */ +public class Exercise_3_Test { + + @Test public void tbd() { + + + } + +} From 76936fa3f418d5013f8dbbd9d871c70737eb6a7a Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Tue, 7 May 2013 21:45:58 +0100 Subject: [PATCH 21/61] Add a third exercise: mapping. --- .../lambda/tutorial/Exercise_3_Test.java | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_3_Test.java diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_3_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_3_Test.java new file mode 100644 index 0000000..2f5ea96 --- /dev/null +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_3_Test.java @@ -0,0 +1,81 @@ +package org.adoptopenjdk.lambda.tutorial; + +import org.junit.Test; + +import java.util.Collection; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Exercise 3 - Mapping + * + * Along with filter, map is one of the most common operations to perform. + * + * Consider this idiomatic Java code used to convert a list of strings to uppercase: + * + * List upperCaseStrings = new ArrayList<>(); + * for (String s: mixedCaseStrings) { + * upperCaseStrings.add(s.toUpperCase()); + * } + * return upperCaseStrings; + * + * As with the filter operation, map removes a lot of the boilerplate of this example by pushing the common code into + * the library, rather than having it repeated in your code. In this case, we are again: + * - constructing a new, empty, destination collection + * - iterating over the source collection + * - "doing something" to each element + * - adding the result to a new collection + * + * However, unlike filter, we are not just adding elements that pass a test, we are taking the element and creating some + * new value before adding it. The new value in the example above is taking the result of toUpperCase(). The result is a + * new list, of the same length, where each element is the result of mapping the source element. In post-JDK 8, you + * can express this operation like so: + * + * return mixedCaseStrings.stream().map(s -> s.toUpperCase()).collect(Collectors.toList()); + * + * As with the last exercise, we "open up" the Streams API by calling .stream() on a collection. This allows us to + * access the map() method. + * + * The map() method takes a function (java.util.function.Function) as an argument, and applies it to each element in a + * list. Since Function is an interface with a single abstract method, JDK 8 allows us to express it as a lambda. The + * single method on the Function type takes a single parameter of a certain type and returns a single result + * of another type. In this example, the input type is String, and the return type also happens to be String. It + * could be return any type, the resultant collection will be a collection of that type. + * + * Again we're transferring into a destination list by means of the Collectors.toList() method. + * + * So what is the result of both of implementations: + * + * If the variable 'mixedCaseStrings' in the example above looked like this: + * + * ["I", "am", "really", "enjoying", "lambda-tutorial"] + * + * The resultant variable 'upperCaseStrings', would like like this: + * + * ["I", "AM", "REALLY", "ENJOYING", "LAMBDA-TUTORIAL"] + * + * An important point to consider is that the map() method does not modify the original list, mixedCaseStrings still + * exists, as it was before. This makes it much easier to prevent bugs where mixedCaseStrings could be used elsewhere, + * perhaps later in the execution, or even concurrently in a different thread. + * + * + * The map operation is also known in other languages/libraries as: transform; collect. + * + * @see Collection#stream() + * @see Stream#map(Function) + * @see Function + * @see Collector + * @see Collectors + * @see Collectors#toList() + * + */ +public class Exercise_3_Test { + + @Test public void tbd() { + + + } + +} From 373bb80aa1412dc2b6db6370395ff717d2fa44ee Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Tue, 7 May 2013 22:31:09 +0100 Subject: [PATCH 22/61] Add three tests for mapping behaviour. --- .../lambda/tutorial/exercise3/Author.java | 35 +++++++++++ .../lambda/tutorial/exercise3/Book.java | 35 +++++++++++ .../lambda/tutorial/exercise3/Books.java | 25 ++++++++ .../lambda/tutorial/exercise3/Publisher.java | 26 ++++++++ .../lambda/tutorial/Exercise_3_Test.java | 63 ++++++++++++++++++- 5 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Author.java create mode 100644 src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Book.java create mode 100644 src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Books.java create mode 100644 src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Publisher.java diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Author.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Author.java new file mode 100644 index 0000000..7e5ba57 --- /dev/null +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Author.java @@ -0,0 +1,35 @@ +package org.adoptopenjdk.lambda.tutorial.exercise3; + +public final class Author { + public final String firstName; + public final String lastName; + + public Author(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + public String fullName() { + return firstName + " " + lastName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Author author = (Author) o; + + if (!firstName.equals(author.firstName)) return false; + if (!lastName.equals(author.lastName)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = firstName.hashCode(); + result = 31 * result + lastName.hashCode(); + return result; + } +} diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Book.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Book.java new file mode 100644 index 0000000..280e4f3 --- /dev/null +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Book.java @@ -0,0 +1,35 @@ +package org.adoptopenjdk.lambda.tutorial.exercise3; + +public final class Book { + public final String title; + public final Author author; + public final Publisher publisher; + + public Book(String title, Author author, Publisher publisher) { + this.title = title; + this.author = author; + this.publisher = publisher; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Book book = (Book) o; + + if (!author.equals(book.author)) return false; + if (!publisher.equals(book.publisher)) return false; + if (!title.equals(book.title)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = title.hashCode(); + result = 31 * result + author.hashCode(); + result = 31 * result + publisher.hashCode(); + return result; + } +} diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Books.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Books.java new file mode 100644 index 0000000..b30f6af --- /dev/null +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Books.java @@ -0,0 +1,25 @@ +package org.adoptopenjdk.lambda.tutorial.exercise3; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public class Books { + public static List titlesOf(List books) { + // [your code here] + + return Collections.emptyList(); + } + + public static List namesOfAuthorsOf(List books) { + // [your code here] + + return Collections.emptyList(); + } + + public static Collection publishersRepresentedBy(List books) { + // [your code here] + + return Collections.emptySet(); + } +} diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Publisher.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Publisher.java new file mode 100644 index 0000000..1fabbc2 --- /dev/null +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Publisher.java @@ -0,0 +1,26 @@ +package org.adoptopenjdk.lambda.tutorial.exercise3; + +public final class Publisher { + public final String name; + + public Publisher(String name) { + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Publisher publisher = (Publisher) o; + + if (!name.equals(publisher.name)) return false; + + return true; + } + + @Override + public int hashCode() { + return name.hashCode(); + } +} diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_3_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_3_Test.java index 2f5ea96..41986cb 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_3_Test.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_3_Test.java @@ -1,13 +1,22 @@ package org.adoptopenjdk.lambda.tutorial; +import org.adoptopenjdk.lambda.tutorial.exercise3.Author; +import org.adoptopenjdk.lambda.tutorial.exercise3.Book; +import org.adoptopenjdk.lambda.tutorial.exercise3.Books; +import org.adoptopenjdk.lambda.tutorial.exercise3.Publisher; import org.junit.Test; +import java.util.Arrays; import java.util.Collection; +import java.util.List; import java.util.function.Function; import java.util.stream.Collector; import java.util.stream.Collectors; import java.util.stream.Stream; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; + /** * Exercise 3 - Mapping * @@ -63,6 +72,9 @@ * * The map operation is also known in other languages/libraries as: transform; collect. * + * The below tests can be made to pass using Stream's map method. Try to make them pass without using a loop, or adding + * to a new collection manually. + * * @see Collection#stream() * @see Stream#map(Function) * @see Function @@ -73,9 +85,58 @@ */ public class Exercise_3_Test { - @Test public void tbd() { + private final Author joshuaBloch = new Author("Joshua", "Bloch"); + private final Author brianGoetz = new Author("Brian", "Goetz"); + private final Author barryBurd = new Author("Barry", "Burd"); + + private final Publisher addisonWesley = new Publisher("Addison-Wesley"); + private final Publisher johnWileyAndSons = new Publisher("John Wiley & Sons"); + + private final Book effectiveJava = new Book("Effective Java", joshuaBloch, addisonWesley); + private final Book javaConcurrencyInPractice = new Book("Java Concurrency In Practice", brianGoetz, addisonWesley); + private final Book javaForDummies = new Book("Java For Dummies", barryBurd, johnWileyAndSons); + private final List books = Arrays.asList(effectiveJava, javaConcurrencyInPractice, javaForDummies); + + /** + * Use Stream.map() to convert a collection of books into a collection of their titles. + */ + @Test + public void getAllBookTitles() { + assertThat(Books.titlesOf(books), + contains("Effective Java", "Java Concurrency In Practice", "Java For Dummies")); + } + + /** + * Use Stream.map() to convert a collection of books into a collection of the author's full names. + * + * Note that it is possible to chain calls to map(). E.g. myCollection.map(...).map(...).collect(). That may come + * in useful when generating an author's full name. + * + * @see Author#fullName() + */ + @Test + public void getNamesOfAuthorsOfBooks() { + assertThat(Books.namesOfAuthorsOf(books), + contains("Joshua Bloch", "Brian Goetz", "Barry Burd")); + } + /** + * Use Stream.map() to convert a collection of books into a collection of the distinct publishers represented within + * the given list of books. For example, given books A published by X, B published by Y, and C published by Y, + * return a collection consisting of X and Y. + * + * This can be done with a single stream().map(...).collect(...). Remember you can collect into collections other + * than a List. + * + * @see Publisher#hashCode() + * @see Publisher#equals(Object) + * @see Collectors#toSet() + */ + @Test + public void getPublishersRepresentedByBooks() { + assertThat(Books.publishersRepresentedBy(books), + contains(addisonWesley, johnWileyAndSons)); } } From 54e1ebd9b28dc539b62f144e4416d219f5a5f60c Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Tue, 7 May 2013 22:31:09 +0100 Subject: [PATCH 23/61] Add three tests for mapping behaviour. --- .../lambda/tutorial/exercise3/Author.java | 35 +++++++++++ .../lambda/tutorial/exercise3/Book.java | 35 +++++++++++ .../lambda/tutorial/exercise3/Books.java | 25 ++++++++ .../lambda/tutorial/exercise3/Publisher.java | 26 ++++++++ .../lambda/tutorial/Exercise_3_Test.java | 63 ++++++++++++++++++- 5 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Author.java create mode 100644 src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Book.java create mode 100644 src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Books.java create mode 100644 src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Publisher.java diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Author.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Author.java new file mode 100644 index 0000000..7e5ba57 --- /dev/null +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Author.java @@ -0,0 +1,35 @@ +package org.adoptopenjdk.lambda.tutorial.exercise3; + +public final class Author { + public final String firstName; + public final String lastName; + + public Author(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + public String fullName() { + return firstName + " " + lastName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Author author = (Author) o; + + if (!firstName.equals(author.firstName)) return false; + if (!lastName.equals(author.lastName)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = firstName.hashCode(); + result = 31 * result + lastName.hashCode(); + return result; + } +} diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Book.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Book.java new file mode 100644 index 0000000..280e4f3 --- /dev/null +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Book.java @@ -0,0 +1,35 @@ +package org.adoptopenjdk.lambda.tutorial.exercise3; + +public final class Book { + public final String title; + public final Author author; + public final Publisher publisher; + + public Book(String title, Author author, Publisher publisher) { + this.title = title; + this.author = author; + this.publisher = publisher; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Book book = (Book) o; + + if (!author.equals(book.author)) return false; + if (!publisher.equals(book.publisher)) return false; + if (!title.equals(book.title)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = title.hashCode(); + result = 31 * result + author.hashCode(); + result = 31 * result + publisher.hashCode(); + return result; + } +} diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Books.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Books.java new file mode 100644 index 0000000..b30f6af --- /dev/null +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Books.java @@ -0,0 +1,25 @@ +package org.adoptopenjdk.lambda.tutorial.exercise3; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public class Books { + public static List titlesOf(List books) { + // [your code here] + + return Collections.emptyList(); + } + + public static List namesOfAuthorsOf(List books) { + // [your code here] + + return Collections.emptyList(); + } + + public static Collection publishersRepresentedBy(List books) { + // [your code here] + + return Collections.emptySet(); + } +} diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Publisher.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Publisher.java new file mode 100644 index 0000000..1fabbc2 --- /dev/null +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Publisher.java @@ -0,0 +1,26 @@ +package org.adoptopenjdk.lambda.tutorial.exercise3; + +public final class Publisher { + public final String name; + + public Publisher(String name) { + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Publisher publisher = (Publisher) o; + + if (!name.equals(publisher.name)) return false; + + return true; + } + + @Override + public int hashCode() { + return name.hashCode(); + } +} diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_3_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_3_Test.java index 2f5ea96..41986cb 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_3_Test.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_3_Test.java @@ -1,13 +1,22 @@ package org.adoptopenjdk.lambda.tutorial; +import org.adoptopenjdk.lambda.tutorial.exercise3.Author; +import org.adoptopenjdk.lambda.tutorial.exercise3.Book; +import org.adoptopenjdk.lambda.tutorial.exercise3.Books; +import org.adoptopenjdk.lambda.tutorial.exercise3.Publisher; import org.junit.Test; +import java.util.Arrays; import java.util.Collection; +import java.util.List; import java.util.function.Function; import java.util.stream.Collector; import java.util.stream.Collectors; import java.util.stream.Stream; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; + /** * Exercise 3 - Mapping * @@ -63,6 +72,9 @@ * * The map operation is also known in other languages/libraries as: transform; collect. * + * The below tests can be made to pass using Stream's map method. Try to make them pass without using a loop, or adding + * to a new collection manually. + * * @see Collection#stream() * @see Stream#map(Function) * @see Function @@ -73,9 +85,58 @@ */ public class Exercise_3_Test { - @Test public void tbd() { + private final Author joshuaBloch = new Author("Joshua", "Bloch"); + private final Author brianGoetz = new Author("Brian", "Goetz"); + private final Author barryBurd = new Author("Barry", "Burd"); + + private final Publisher addisonWesley = new Publisher("Addison-Wesley"); + private final Publisher johnWileyAndSons = new Publisher("John Wiley & Sons"); + + private final Book effectiveJava = new Book("Effective Java", joshuaBloch, addisonWesley); + private final Book javaConcurrencyInPractice = new Book("Java Concurrency In Practice", brianGoetz, addisonWesley); + private final Book javaForDummies = new Book("Java For Dummies", barryBurd, johnWileyAndSons); + private final List books = Arrays.asList(effectiveJava, javaConcurrencyInPractice, javaForDummies); + + /** + * Use Stream.map() to convert a collection of books into a collection of their titles. + */ + @Test + public void getAllBookTitles() { + assertThat(Books.titlesOf(books), + contains("Effective Java", "Java Concurrency In Practice", "Java For Dummies")); + } + + /** + * Use Stream.map() to convert a collection of books into a collection of the author's full names. + * + * Note that it is possible to chain calls to map(). E.g. myCollection.map(...).map(...).collect(). That may come + * in useful when generating an author's full name. + * + * @see Author#fullName() + */ + @Test + public void getNamesOfAuthorsOfBooks() { + assertThat(Books.namesOfAuthorsOf(books), + contains("Joshua Bloch", "Brian Goetz", "Barry Burd")); + } + /** + * Use Stream.map() to convert a collection of books into a collection of the distinct publishers represented within + * the given list of books. For example, given books A published by X, B published by Y, and C published by Y, + * return a collection consisting of X and Y. + * + * This can be done with a single stream().map(...).collect(...). Remember you can collect into collections other + * than a List. + * + * @see Publisher#hashCode() + * @see Publisher#equals(Object) + * @see Collectors#toSet() + */ + @Test + public void getPublishersRepresentedByBooks() { + assertThat(Books.publishersRepresentedBy(books), + contains(addisonWesley, johnWileyAndSons)); } } From 6d5644b0b1bd9d98da4657ce0085f446e2e707e8 Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Tue, 7 May 2013 22:39:20 +0100 Subject: [PATCH 24/61] Make it clear method returns a set. Also use a feature for a better test failure message. --- .../lambda/tutorial/exercise3/Books.java | 4 ++-- .../lambda/tutorial/Exercise_3_Test.java | 19 +++++++++++++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Books.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Books.java index b30f6af..0f428df 100644 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Books.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Books.java @@ -1,8 +1,8 @@ package org.adoptopenjdk.lambda.tutorial.exercise3; -import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Set; public class Books { public static List titlesOf(List books) { @@ -17,7 +17,7 @@ public static List namesOfAuthorsOf(List books) { return Collections.emptyList(); } - public static Collection publishersRepresentedBy(List books) { + public static Set publishersRepresentedBy(List books) { // [your code here] return Collections.emptySet(); diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_3_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_3_Test.java index 41986cb..2447243 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_3_Test.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_3_Test.java @@ -4,6 +4,8 @@ import org.adoptopenjdk.lambda.tutorial.exercise3.Book; import org.adoptopenjdk.lambda.tutorial.exercise3.Books; import org.adoptopenjdk.lambda.tutorial.exercise3.Publisher; +import org.adoptopenjdk.lambda.tutorial.util.FeatureMatchers; +import org.hamcrest.Matcher; import org.junit.Test; import java.util.Arrays; @@ -15,7 +17,8 @@ import java.util.stream.Stream; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.equalTo; /** * Exercise 3 - Mapping @@ -83,6 +86,7 @@ * @see Collectors#toList() * */ +@SuppressWarnings("unchecked") public class Exercise_3_Test { private final Author joshuaBloch = new Author("Joshua", "Bloch"); @@ -104,7 +108,7 @@ public class Exercise_3_Test { @Test public void getAllBookTitles() { assertThat(Books.titlesOf(books), - contains("Effective Java", "Java Concurrency In Practice", "Java For Dummies")); + containsInAnyOrder("Effective Java", "Java Concurrency In Practice", "Java For Dummies")); } /** @@ -118,7 +122,7 @@ public void getAllBookTitles() { @Test public void getNamesOfAuthorsOfBooks() { assertThat(Books.namesOfAuthorsOf(books), - contains("Joshua Bloch", "Brian Goetz", "Barry Burd")); + containsInAnyOrder("Joshua Bloch", "Brian Goetz", "Barry Burd")); } /** @@ -136,7 +140,14 @@ public void getNamesOfAuthorsOfBooks() { @Test public void getPublishersRepresentedByBooks() { assertThat(Books.publishersRepresentedBy(books), - contains(addisonWesley, johnWileyAndSons)); + containsInAnyOrder(publisherNamed("Addison-Wesley"), publisherNamed("John Wiley & Sons"))); + } + + + // Test helpers + + private static Matcher publisherNamed(String name) { + return FeatureMatchers.from(equalTo(name), "is named", "name", p -> p.name); } } From 22f7675b4687c79a178d845dc15582f53c13a2fd Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Tue, 7 May 2013 22:39:20 +0100 Subject: [PATCH 25/61] Make it clear method returns a set. Also use a feature for a better test failure message. --- .../lambda/tutorial/exercise3/Books.java | 4 ++-- .../lambda/tutorial/Exercise_3_Test.java | 19 +++++++++++++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Books.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Books.java index b30f6af..0f428df 100644 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Books.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Books.java @@ -1,8 +1,8 @@ package org.adoptopenjdk.lambda.tutorial.exercise3; -import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Set; public class Books { public static List titlesOf(List books) { @@ -17,7 +17,7 @@ public static List namesOfAuthorsOf(List books) { return Collections.emptyList(); } - public static Collection publishersRepresentedBy(List books) { + public static Set publishersRepresentedBy(List books) { // [your code here] return Collections.emptySet(); diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_3_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_3_Test.java index 41986cb..2447243 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_3_Test.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_3_Test.java @@ -4,6 +4,8 @@ import org.adoptopenjdk.lambda.tutorial.exercise3.Book; import org.adoptopenjdk.lambda.tutorial.exercise3.Books; import org.adoptopenjdk.lambda.tutorial.exercise3.Publisher; +import org.adoptopenjdk.lambda.tutorial.util.FeatureMatchers; +import org.hamcrest.Matcher; import org.junit.Test; import java.util.Arrays; @@ -15,7 +17,8 @@ import java.util.stream.Stream; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.equalTo; /** * Exercise 3 - Mapping @@ -83,6 +86,7 @@ * @see Collectors#toList() * */ +@SuppressWarnings("unchecked") public class Exercise_3_Test { private final Author joshuaBloch = new Author("Joshua", "Bloch"); @@ -104,7 +108,7 @@ public class Exercise_3_Test { @Test public void getAllBookTitles() { assertThat(Books.titlesOf(books), - contains("Effective Java", "Java Concurrency In Practice", "Java For Dummies")); + containsInAnyOrder("Effective Java", "Java Concurrency In Practice", "Java For Dummies")); } /** @@ -118,7 +122,7 @@ public void getAllBookTitles() { @Test public void getNamesOfAuthorsOfBooks() { assertThat(Books.namesOfAuthorsOf(books), - contains("Joshua Bloch", "Brian Goetz", "Barry Burd")); + containsInAnyOrder("Joshua Bloch", "Brian Goetz", "Barry Burd")); } /** @@ -136,7 +140,14 @@ public void getNamesOfAuthorsOfBooks() { @Test public void getPublishersRepresentedByBooks() { assertThat(Books.publishersRepresentedBy(books), - contains(addisonWesley, johnWileyAndSons)); + containsInAnyOrder(publisherNamed("Addison-Wesley"), publisherNamed("John Wiley & Sons"))); + } + + + // Test helpers + + private static Matcher publisherNamed(String name) { + return FeatureMatchers.from(equalTo(name), "is named", "name", p -> p.name); } } From 46b3eee2f99b6923f6a865e23701c90899a33662 Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Tue, 7 May 2013 22:40:57 +0100 Subject: [PATCH 26/61] Make tests pass for JDK 8. --- .../lambda/tutorial/exercise3/Books.java | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Books.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Books.java index 0f428df..5a20349 100644 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Books.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Books.java @@ -3,23 +3,19 @@ import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; public class Books { - public static List titlesOf(List books) { - // [your code here] - return Collections.emptyList(); + public static List titlesOf(List books) { + return books.stream().map(b -> b.title).collect(Collectors.toList()); } public static List namesOfAuthorsOf(List books) { - // [your code here] - - return Collections.emptyList(); + return books.stream().map(b -> b.author).map(Author::fullName).collect(Collectors.toList()); } public static Set publishersRepresentedBy(List books) { - // [your code here] - - return Collections.emptySet(); + return books.stream().map(b -> b.publisher).collect(Collectors.toSet()); } } From 3fe33e1dba35b84e84722d6f728c01891501b2eb Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Tue, 7 May 2013 22:46:39 +0100 Subject: [PATCH 27/61] Make tests pass for pre-JDK 8. --- .../lambda/tutorial/exercise3/Books.java | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Books.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Books.java index 0f428df..04170dd 100644 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Books.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Books.java @@ -1,25 +1,35 @@ package org.adoptopenjdk.lambda.tutorial.exercise3; -import java.util.Collections; +import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Set; public class Books { public static List titlesOf(List books) { - // [your code here] + List titles = new ArrayList<>(); + for (Book b: books) { + titles.add(b.title); + } - return Collections.emptyList(); + return titles; } public static List namesOfAuthorsOf(List books) { - // [your code here] + List fullNames = new ArrayList<>(); + for (Book b: books) { + fullNames.add(b.author.fullName()); + } - return Collections.emptyList(); + return fullNames; } public static Set publishersRepresentedBy(List books) { - // [your code here] + Set publishers = new HashSet<>(); + for (Book b: books) { + publishers.add(b.publisher); + } - return Collections.emptySet(); + return publishers; } } From 2318613ba31115f692d59a8ecc2457edaa1acd98 Mon Sep 17 00:00:00 2001 From: Grundlefleck Date: Sat, 11 May 2013 11:42:37 +0100 Subject: [PATCH 28/61] Add javadoc to each of the methods under test to repeat what is expected of them. --- .../lambda/tutorial/exercise1/Shapes.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise1/Shapes.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise1/Shapes.java index 1839713..24732e5 100644 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise1/Shapes.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise1/Shapes.java @@ -12,14 +12,54 @@ */ public class Shapes { + /** + * Changes the color of all the given shapes, setting to newColor. + * + * Example: + * given a list containing [BLUE shape, GREEN shape, BLACK shape] + * when this method is called with that list and the color RED + * then the list will contain [RED shape, RED shape, RED shape] + * + * @see Shape#setColor(Color) + */ public static void colorAll(List shapes, Color newColor) { shapes.forEach(s -> s.setColor(newColor)); } + /** + * Creates a String representation of all the given shapes, appending to the given + * stringBuilder. + * + * Uses Shape#toString to create the String representation of each shape. + * + * Example: + * given a list containing [BLUE shape, GREEN shape, BLACK shape] + * when this method is called with that list and an empty StringBuilder + * then the StringBuilder's toString method will return "[BLUE][GREEN][BLACK]" + * + * @see Shape#toString() + */ public static void makeStringOfAllColors(List shapes, StringBuilder stringBuilder) { shapes.forEach(s -> stringBuilder.append(s)); } + + /** + * Changes the color of each given shape to newColor, appending a String representation of the color of all the + * shapes, as they were before they were changed. + * + * + * Example: + * given a list containing [BLUE shape, GREEN shape, BLACK shape] + * when this method is called with that list, the color RED, and an empty StringBuilder + * then the list will contain [RED shape, RED shape, RED shape] + * and the StringBuilder's toString method will return "[BLUE][GREEN][BLACK]" + * + * This operation is performed in one pass over the shapes List. + * + * @see Shape#setColor(Color) + * @see Shape#toString() + */ public static void changeColorAndMakeStringOfOldColors(List shapes, Color newColor, StringBuilder stringBuilder) { shapes.forEach(s -> { stringBuilder.append("[" + s.getColor() + "]"); s.setColor(newColor); }); From 7f0affcab27afbd44ded4f572f603bd6dcbaeafe Mon Sep 17 00:00:00 2001 From: Grundlefleck Date: Sat, 11 May 2013 11:42:37 +0100 Subject: [PATCH 29/61] Add javadoc to each of the methods under test to repeat what is expected of them. --- .../lambda/tutorial/exercise1/Shapes.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise1/Shapes.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise1/Shapes.java index f49873b..f8f0cf0 100644 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise1/Shapes.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise1/Shapes.java @@ -12,18 +12,58 @@ */ public class Shapes { + /** + * Changes the color of all the given shapes, setting to newColor. + * + * Example: + * given a list containing [BLUE shape, GREEN shape, BLACK shape] + * when this method is called with that list and the color RED + * then the list will contain [RED shape, RED shape, RED shape] + * + * @see Shape#setColor(Color) + */ public static void colorAll(List shapes, Color newColor) { for (Shape s: shapes) { s.setColor(newColor); } } + /** + * Creates a String representation of all the given shapes, appending to the given + * stringBuilder. + * + * Uses Shape#toString to create the String representation of each shape. + * + * Example: + * given a list containing [BLUE shape, GREEN shape, BLACK shape] + * when this method is called with that list and an empty StringBuilder + * then the StringBuilder's toString method will return "[BLUE][GREEN][BLACK]" + * + * @see Shape#toString() + */ public static void makeStringOfAllColors(List shapes, StringBuilder stringBuilder) { for (Shape s: shapes) { stringBuilder.append(s); } } + + /** + * Changes the color of each given shape to newColor, appending a String representation of the color of all the + * shapes, as they were before they were changed. + * + * + * Example: + * given a list containing [BLUE shape, GREEN shape, BLACK shape] + * when this method is called with that list, the color RED, and an empty StringBuilder + * then the list will contain [RED shape, RED shape, RED shape] + * and the StringBuilder's toString method will return "[BLUE][GREEN][BLACK]" + * + * This operation is performed in one pass over the shapes List. + * + * @see Shape#setColor(Color) + * @see Shape#toString() + */ public static void changeColorAndMakeStringOfOldColors(List shapes, Color newColor, StringBuilder stringBuilder) { for (Shape s: shapes) { stringBuilder.append("[" + s.getColor() + "]"); From de271c2e313d778b682e48d26ae0a6087f7a1f1e Mon Sep 17 00:00:00 2001 From: Grundlefleck Date: Sun, 12 May 2013 22:32:07 +0100 Subject: [PATCH 30/61] Merge in changes from karianna's fixes on master. --- .../lambda/tutorial/exercise1/Shapes.java | 14 ++++++++--- .../tutorial/exercise2/ElectoralDistrict.java | 25 +++++++++++++------ .../tutorial/exercise2/VotingRules.java | 11 +++++--- .../lambda/tutorial/exercise3/Books.java | 23 ++++++++++++++--- 4 files changed, 55 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise1/Shapes.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise1/Shapes.java index df4f1ed..4bc9bac 100644 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise1/Shapes.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise1/Shapes.java @@ -23,7 +23,9 @@ public class Shapes { * @see Shape#setColor(Color) */ public static void colorAll(List shapes, Color newColor) { - shapes.forEach(s -> s.setColor(newColor)); + for (Shape s: shapes) { + s.setColor(newColor); + } } /** @@ -40,7 +42,9 @@ public static void colorAll(List shapes, Color newColor) { * @see Shape#toString() */ public static void makeStringOfAllColors(List shapes, StringBuilder stringBuilder) { - shapes.forEach(s -> stringBuilder.append(s)); + for (Shape s: shapes) { + stringBuilder.append(s); + } } /** @@ -61,7 +65,9 @@ public static void makeStringOfAllColors(List shapes, StringBuilder strin * @see Shape#toString() */ public static void changeColorAndMakeStringOfOldColors(List shapes, Color newColor, StringBuilder stringBuilder) { - shapes.forEach(s -> { stringBuilder.append(s.toString()); s.setColor(newColor); }); - + for (Shape s: shapes) { + stringBuilder.append(s.toString()); + s.setColor(newColor); + } } } diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java index cfbda10..eb407a7 100644 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java @@ -2,6 +2,7 @@ import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; @@ -27,17 +28,25 @@ public enum ElectoralDistrict { } public static Set votersIn(ElectoralDistrict district, Collection voters) { - Set fromDistrict = voters.stream() - .filter(v -> v.getElectorId().startsWith(district.prefix)) - .collect(Collectors.toSet()); - - return Collections.unmodifiableSet(fromDistrict); + Set votersInDistrict = new HashSet<>(); + for (RegisteredVoter v: voters) { + if (v.getElectorId().startsWith(district.prefix)) { + votersInDistrict.add(v); + } + } + + return Collections.unmodifiableSet(votersInDistrict); } public static Set unspoiledBallots(Set votes) { - return votes.stream() - .filter(v -> !v.isSpoiled()) - .collect(Collectors.toSet()); + Set unspoiledBallots = new HashSet<>(); + for (Ballot v: votes) { + if (!v.isSpoiled()) { + unspoiledBallots.add(v); + } + } + + return unspoiledBallots; } } diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/VotingRules.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/VotingRules.java index 9c18ca1..a3a38e8 100644 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/VotingRules.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/VotingRules.java @@ -1,11 +1,16 @@ package org.adoptopenjdk.lambda.tutorial.exercise2; +import java.util.ArrayList; import java.util.List; public class VotingRules { public static List eligibleVoters(List potentialVoters, int legalAgeOfVoting) { - return potentialVoters.stream() - .filter(p -> p.getAge() >= legalAgeOfVoting) - .collect(Collectors.toList()); + List eligible = new ArrayList<>(); + for (Person p: potentialVoters) { + if (p.getAge() >= legalAgeOfVoting) { + eligible.add(p); + } + } + return eligible; } } diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Books.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Books.java index f5a835a..812750c 100644 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Books.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Books.java @@ -1,5 +1,7 @@ package org.adoptopenjdk.lambda.tutorial.exercise3; +import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -7,14 +9,29 @@ public class Books { public static List titlesOf(List books) { - return books.stream().map(b -> b.title).collect(Collectors.toList()); + List titles = new ArrayList<>(); + for (Book b: books) { + titles.add(b.title); + } + + return titles; } public static List namesOfAuthorsOf(List books) { - return books.stream().map(b -> b.author).map(Author::fullName).collect(Collectors.toList()); + List fullNames = new ArrayList<>(); + for (Book b: books) { + fullNames.add(b.author.fullName()); + } + + return fullNames; } public static Set publishersRepresentedBy(List books) { - return books.stream().map(b -> b.publisher).collect(Collectors.toSet()); + Set publishers = new HashSet<>(); + for (Book b: books) { + publishers.add(b.publisher); + } + + return publishers; } } From 522e1a6e6b73f8da4ba8e792f02c528f0b885f92 Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Mon, 2 Sep 2013 23:06:32 +0100 Subject: [PATCH 31/61] Update build information used in readme. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d59ff77..a70b596 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,6 @@ The current tutorial is known to work with the following JDK build: |JDK Build Number|Released On | |:---------------|:---------- | -|b88 |May 09, 2013| +|b105 |Aug 26, 2013| lambda-tutorial will try to track against the newest version available. If you find that you are working with a newer version of the Lambda JDK and the tutorial does not compile or run, please file an issue. From 35a0655e18c880808be0d668c9f5c2aa78a57836 Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Mon, 2 Sep 2013 23:34:33 +0100 Subject: [PATCH 32/61] Now that there's a hook to wrap a computed stream, remove the comment about there being no way to do that. --- .../org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java index 267cd7b..4803a9e 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java @@ -38,6 +38,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collector; import java.util.stream.Collectors; @@ -242,13 +243,10 @@ public void getAllPersonsEligibleToVote() { * Ensure that the Set returned cannot be modified by callers by wrapping the result * in Collections.unmodifiableSet(). *

- *

- * The Streams API does not provide a way to wrap the final result, as one of its operations. So just wrap the - * result in an unmodifiableSet yourself. Sometimes it's just as important to know what an API _doesn't_ do. - *

* @throws ClassNotFoundException If the lambdas binary build no longer contains the class * @see Stream#collect(java.util.stream.Collector) - * @see java.util.stream.Collectors#toSet() + * @see Collectors#collectingAndThen(Collector, Function) + * @see Collectors#toSet() * @see Collections#unmodifiableSet(java.util.Set) */ @Test public void setOfVotersInDistrictInUnmodifiableSet() throws ClassNotFoundException { From de8ab52744d4f389f473c1b4290701e676b86b3f Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Thu, 3 Oct 2013 20:27:47 +0100 Subject: [PATCH 33/61] GrahamA: tested with most recent jdk ea build. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a70b596..c8123ae 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,6 @@ The current tutorial is known to work with the following JDK build: |JDK Build Number|Released On | |:---------------|:---------- | -|b105 |Aug 26, 2013| +|ea b109 |Sep 26, 2013| lambda-tutorial will try to track against the newest version available. If you find that you are working with a newer version of the Lambda JDK and the tutorial does not compile or run, please file an issue. From 58f254b969dadd26177fd077a26e322bd101a7d4 Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Thu, 3 Oct 2013 21:41:54 +0100 Subject: [PATCH 34/61] Describe each of the remaining kinds of method references available. Conflicts: src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java --- .../lambda/tutorial/exercise4/Document.java | 29 ++++ .../lambda/tutorial/exercise4/Printers.java | 38 +++++ .../lambda/tutorial/Exercise_4_Test.java | 138 ++++++++++++++++++ 3 files changed, 205 insertions(+) create mode 100644 src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java create mode 100644 src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Printers.java create mode 100644 src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java new file mode 100644 index 0000000..f63a873 --- /dev/null +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java @@ -0,0 +1,29 @@ +package org.adoptopenjdk.lambda.tutorial.exercise4; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public final class Document { + private final List pages; + + public Document(List pages) { + this.pages = Collections.unmodifiableList(new ArrayList<>(pages)); + } + + public String getPageContent(Integer pageNumber) { + return this.pages.get(pageNumber).getContent(); + } + + public static final class Page { + private final String content; + + public Page(String content) { + this.content = content; + } + + public String getContent() { + return this.content; + } + } +} diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Printers.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Printers.java new file mode 100644 index 0000000..4bd6d76 --- /dev/null +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Printers.java @@ -0,0 +1,38 @@ +package org.adoptopenjdk.lambda.tutorial.exercise4; + +import java.util.Arrays; +import java.util.List; +import static java.util.stream.Collectors.toList; +import java.util.stream.Stream; + +import org.adoptopenjdk.lambda.tutorial.exercise4.Document.Page; + +public class Printers { + public static void print(String s) { + System.out.println(s); + } + + public static void printPages(Document doc, Integer... pageNumbers) { + Arrays.stream(pageNumbers).map(doc::getPageContent).forEach(Printers::print); + } + + public static Stream createPagesFrom(Stream contents) { + return contents.map(Page::new); + } + + + public static void main(String... args) { + Page p1 = new Page("this is the first page"); + Page p2 = new Page("this is the second page"); + + Document myDocument = new Document(Arrays.asList(p1, p2)); + + Printers.printPages(myDocument, 0, 1); + + List pages = Arrays.asList(p1, p2); + pages.stream().map(Page::getContent).forEach(Printers::print); + + Stream pagesFromContent = createPagesFrom(Arrays.asList("a", "b").stream()); + System.out.println(pagesFromContent.collect(toList())); + } +} diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java new file mode 100644 index 0000000..e25c601 --- /dev/null +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java @@ -0,0 +1,138 @@ +package org.adoptopenjdk.lambda.tutorial; + +/* + * #%L + * lambda-tutorial + * %% + * Copyright (C) 2013 Adopt OpenJDK + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +import java.util.function.Consumer; + +/** + * Exercise 4 - Method References + *

+ * Method references are another syntactic addition to JDK 8. They are intended to be used in lambda expressions, + * preventing unnecessary boilerplate. Consider the following lambda from an earlier test: + * books.stream().map(b -> b.getTitle()). Here the lambda does nothing more than invoke a method on each + * element of the stream. This can be written alternatively as: books.stream().map(Book::getTitle). Both + * forms are equivalent. + *

+ *

+ * The parameter list and return type when using a method reference must match the signature of the method. A mismatch + * between the two will result in a compile error, just as invoking a method with parameters or return type with an + * incorrect type will cause a compile error. + * For example, consider the following code: + *

+ * public class Printers {
+ *     public static void print(String s) {...}
+ * }
+ * 
+ * Note that the printNumber method takes a single String parameter, and has a void return + * type. Although declared using different type names, this is the same signature as {@link Consumer}, the functional + * interface that's passed to {@link Iterable#forEach(Consumer)}. Since they match, we can use a reference to the + * print method when invoking forEach on a list of Strings, like so: + *
+ *     Arrays.asList("a", "b", "c").forEach(Printers::print)
+ * 
+ * If we changed the signature of print to public static void print(String s, String t) + * the use of the method reference would no longer compile, with an error message pointing to the mismatch in argument + * lists. + *

+ *

+ * There are four different kinds of methods that can be used with the method reference syntax: + *

    + *
  1. Static method belonging to a particular class
  2. + *
  3. Instance method bound to a particular object instance
  4. + *
  5. Instance method bound to a particular class
  6. + *
  7. Constructor belonging to a particular class
  8. + *
+ *

+ * We'll discuss each in turn: + *

+ * Static method belonging to a particular class + *
+ * In the previous example, the Printers::print method reference is to a static method ('print') belonging + * to the Printers class. Here the argument list of the lambda must match the argument list of the method, the first + * argument to the lambda is the first argument passed into the method. + *

+ * + *

+ * Instance method bound to a particular object instance + *
+ * It's possible to use a method invoked an a specific instance of a class. Consider the following code: + *

+ * public class Document {
+ *     // field, constructor, etc
+ *
+ *     public String getPageContent(int pageNumber) {
+ *         return this.pages.get(pageNumber).getContent();
+ *     }
+ * }
+ *
+ * public static void printPages(Document doc, int[] pageNumbers) {
+ *     Arrays.stream(pageNumbers).map(doc::getPageContent).forEach(Printers::print);
+ * }
+ * 
+ * + * In this case, when the map operation runs, the method getPageContent will + * be invoked on the doc instance. Regardless of the current page number at that point + * of the stream, it will always be transformed by calling the equivalent of doc.getPageContent(i). + *

+ *

+ * Instance method belonging to a particular class + *
+ * When iterating over a stream of objects, you can invoke a method on each object by using an instance method + * reference. As in this code: + *

+ * public static void printDocuments(List<Page> pages) {
+ *     pages.stream().map(Page::getContent).forEach(Printers::print);
+ * }
+ * 
+ * In this case the method getContent will still be invoked on an instance of Page, however, + * it will be invoked on each Page instance that is mapped over. + *

+ *

+ * Constructor belonging to a particular class + * By now, we know how to use method references for static methods and instance methods, that leaves an odd case: + * constructors. + *

+ * While we don't invoke a constructor like a static method, it is useful to think of it that way. Currently we write: + * Page p = new Page("content"); but imagine we changed the syntax of the Java language to allow this: + * Page p = Page.new("content");. We can consider the Page.new method to have the exact + * semantics of a constructor, that is, use a reference to the class object to invoke the constructor method and return + * the newly created instance as the result of new. + *

+ * With that in mind, consider the following code: + *
+ * public static Stream<Page> createPagesFrom(Stream<String> contents) {
+ *     return contents.map(Page::new).
+ * }
+ * 
+ * The method will return a Stream of newly constructed Page objects. new is + * still a special keyword in Java, but can now be used in the method reference construct. Note that just like other + * method references, the method signature of the constructor must match the types fed by the map + * operation. + *

+ * + */ +@SuppressWarnings("unchecked") +public class Exercise_4_Test { + + +} From 71e69283f0b4403b94073a8c83f53335885c1efe Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Fri, 4 Oct 2013 00:51:02 +0100 Subject: [PATCH 35/61] Ignore more intellij files. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index fc2a2f9..7fe7beb 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *iml *ipr *iws +*eml # Eclipse .classpath From 90e8717525946314f0e57c407fccef0f3704578a Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Fri, 4 Oct 2013 00:51:26 +0100 Subject: [PATCH 36/61] Introduce asm to help verify that method references have been used. --- pom.xml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e13fb1f..688d5cc 100644 --- a/pom.xml +++ b/pom.xml @@ -87,6 +87,12 @@ 1.3 test + + org.ow2.asm + asm-debug-all + 5.0_ALPHA + test + @@ -103,8 +109,13 @@ org.hamcrest hamcrest-library + + org.ow2.asm + asm-debug-all + - + + From 0697cd1c13f607f10d3397eb319b052f06e0ee94 Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Fri, 4 Oct 2013 00:53:14 +0100 Subject: [PATCH 37/61] First example complete. A matcher that checks you're using method references by discovering the java source file from the class, scans the filesystem, and reads the source to check there's a :: before the method name. Phew. --- .../lambda/tutorial/exercise4/Document.java | 8 +- .../lambda/tutorial/Documents.java | 42 +++++ .../lambda/tutorial/Exercise_4_Test.java | 35 ++++ .../util/CodeUsesMethodReferencesMatcher.java | 169 ++++++++++++++++++ 4 files changed, 253 insertions(+), 1 deletion(-) create mode 100644 src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java create mode 100644 src/test/java/org/adoptopenjdk/lambda/tutorial/util/CodeUsesMethodReferencesMatcher.java diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java index f63a873..fda540f 100644 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java @@ -5,9 +5,11 @@ import java.util.List; public final class Document { + private final String title; private final List pages; - public Document(List pages) { + public Document(String title, List pages) { + this.title = title; this.pages = Collections.unmodifiableList(new ArrayList<>(pages)); } @@ -15,6 +17,10 @@ public String getPageContent(Integer pageNumber) { return this.pages.get(pageNumber).getContent(); } + public String getTitle() { + return this.title; + } + public static final class Page { private final String content; diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java new file mode 100644 index 0000000..af52656 --- /dev/null +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java @@ -0,0 +1,42 @@ +package org.adoptopenjdk.lambda.tutorial; + +/* + * #%L + * lambda-tutorial + * %% + * Copyright (C) 2013 Adopt OpenJDK + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +import org.adoptopenjdk.lambda.tutorial.exercise4.Document; + +import java.util.Arrays; +import java.util.List; + +import static java.util.stream.Collectors.toList; + +public class Documents { + + /** + * Return the titles from a list of documents. + */ + public static List titlesOf(Document... documents) { + return Arrays.stream(documents) + .map(d -> d.getTitle()) + .collect(toList()); + } +} diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java index e25c601..931bf67 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java @@ -22,8 +22,17 @@ * #L% */ +import org.adoptopenjdk.lambda.tutorial.exercise4.Document; +import org.adoptopenjdk.lambda.tutorial.exercise4.Document.Page; +import org.hamcrest.Matchers; +import org.junit.Test; + +import java.util.Arrays; import java.util.function.Consumer; +import static org.adoptopenjdk.lambda.tutorial.util.CodeUsesMethodReferencesMatcher.usesMethodReferences; +import static org.hamcrest.MatcherAssert.assertThat; + /** * Exercise 4 - Method References *

@@ -109,6 +118,7 @@ *

*

* Constructor belonging to a particular class + *
* By now, we know how to use method references for static methods and instance methods, that leaves an odd case: * constructors. *

@@ -134,5 +144,30 @@ @SuppressWarnings("unchecked") public class Exercise_4_Test { + /** + * The Documents class has a method which transforms a list of Document into a list of + * their titles. The implementation has already been filled out, but it uses a lambda, as in: + * .map(document -> document.getTitle()) + *
+ * Instead of using a lambda, use a method reference instead. + * + * @see Documents#titlesOf(Document...) + * @see Document#getTitle() + * + */ + @Test + public void getListOfDocumentTitlesUsingInstanceMethodReference() { + Document expenses = new Document("My Expenses", + Arrays.asList(new Page("LJC Open Conference ticket: £25"), new Page("Beer stipend: £100"))); + Document toDoList = new Document("My ToDo List", + Arrays.asList(new Page("Build a todo app"), new Page("Pick up dry cleaning"))); + Document certificates = new Document("My Certificates", + Arrays.asList(new Page("Oracle Certified Professional"), new Page("Swimming 10m"))); + + assertThat(Documents.titlesOf(expenses, toDoList, certificates), + Matchers.contains("My Expenses", "My ToDo List", "My Certificates")); + assertThat(Documents.class, usesMethodReferences("getTitle")); + + } } diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/util/CodeUsesMethodReferencesMatcher.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/util/CodeUsesMethodReferencesMatcher.java new file mode 100644 index 0000000..70a9ba2 --- /dev/null +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/util/CodeUsesMethodReferencesMatcher.java @@ -0,0 +1,169 @@ +package org.adoptopenjdk.lambda.tutorial.util; + +/* + * #%L + * lambda-tutorial + * %% + * Copyright (C) 2013 Adopt OpenJDK + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Opcodes; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Optional; + +import static java.util.stream.Collectors.toList; + +public final class CodeUsesMethodReferencesMatcher extends TypeSafeDiagnosingMatcher> { + + private final String methodName; + + private CodeUsesMethodReferencesMatcher(String methodName) { + this.methodName = methodName; + } + + public static CodeUsesMethodReferencesMatcher usesMethodReferences(String methodName) { + return new CodeUsesMethodReferencesMatcher(methodName); + } + + @Override + public void describeTo(Description description) { + description.appendText("a source file using a method reference to invoke ").appendValue(methodName); + } + + + @Override + protected boolean matchesSafely(Class clazz, Description mismatchDescription) { + try { + Optional sourceFileContent = getSourceContent(clazz); + return sourceFileContent.map(c -> usesMethodReference(c, mismatchDescription)).orElseGet(() -> { + mismatchDescription.appendText("could not read source file to discover if you used method references."); + return false; + }); + } catch (IOException e) { + mismatchDescription.appendText("could not read source file to discover if you used method references."); + mismatchDescription.appendValue(e); + return false; + } + } + + private boolean usesMethodReference(String sourceCode, Description mismatchDescription) { + if (sourceCode.contains("::"+methodName)) { + return true; + } else { + mismatchDescription.appendText("source code did not use a method reference to invoke " + methodName + ". "); + context(sourceCode, methodName, mismatchDescription); + return false; + } + } + + private void context(String sourceCode, String methodName, Description mismatchDescription) { + if (!sourceCode.contains(methodName)) { + mismatchDescription.appendText("You did not appear to invoke the method at all."); + } else { + String[] lines = sourceCode.split("\\n"); + mismatchDescription.appendText("Actual invocations: "); + mismatchDescription.appendValueList("[", ",", "]", + Arrays.stream(lines).filter(l -> l.contains(methodName)).map(String::trim).collect(toList())); + } + } + + private Optional getSourceContent(Class clazz) throws IOException { + String sourceFileName = getSourceFileName(clazz); + Optional sourceFile = findPathTo(sourceFileName); + + return sourceFile.map(this::toContent); + } + + private Optional findPathTo(String sourceFileName) throws IOException { + File cwd = new File("."); + File rootOfProject = findRootOfProject(cwd); + return findSourceFile(rootOfProject, sourceFileName); + } + + private String toContent(File file) { + try { + byte[] encoded = Files.readAllBytes(Paths.get(file.toURI())); + return StandardCharsets.UTF_8.decode(ByteBuffer.wrap(encoded)).toString(); + } catch (IOException e) { + throw new RuntimeException("Could not read Java source file.", e); + } + } + + private Optional findSourceFile(File rootOfProject, String sourceFileName) throws IOException { + Path startingDir = Paths.get(rootOfProject.toURI()); + return Files.find(startingDir, 15, (path, attrs) -> path.endsWith(sourceFileName)) + .map(p -> new File(p.toUri())) + .findFirst(); + } + + private File findRootOfProject(File cwd) { + File[] pomFiles = cwd.listFiles((file, name) -> { return name.equals("pom.xml"); }); + if (pomFiles != null && pomFiles.length == 1) { + return cwd; + } else if (cwd.getParentFile() == null) { + throw new RuntimeException("Couldn't find directory containing pom.xml. Last looked in: " + cwd.getAbsolutePath()); + } else { + return findRootOfProject(cwd.getParentFile()); + } + } + + private String getSourceFileName(Class clazz) throws IOException { + String resourceName = clazz.getName().replace(".", "/").concat(".class"); + ClassReader reader = new ClassReader(clazz.getClassLoader().getResourceAsStream(resourceName)); + SourceFileNameVisitor sourceFileNameVisitor = new SourceFileNameVisitor(); + reader.accept(sourceFileNameVisitor, 0); + + return sourceFileNameVisitor.getSourceFile(); + } + + + private static final class SourceFileNameVisitor extends ClassVisitor { + + private String sourceFile = null; + private boolean visitedYet = false; + + public SourceFileNameVisitor() { + super(Opcodes.ASM5); + } + + @Override + public void visitSource(String source, String debug) { + this.visitedYet = true; + this.sourceFile = source; + super.visitSource(source, debug); + } + + public String getSourceFile() { + if (!visitedYet) throw new IllegalStateException("Must visit a class before asking for source file"); + return this.sourceFile; + } + } + +} From 016a43670ca98fdf853b055343de60edff9ed0fb Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Fri, 4 Oct 2013 01:00:49 +0100 Subject: [PATCH 38/61] Add license headers. --- .../lambda/tutorial/exercise3/Books.java | 8 ++-- .../lambda/tutorial/exercise4/Document.java | 22 +++++++++++ .../lambda/tutorial/exercise4/Printers.java | 38 +++++++++++-------- .../lambda/tutorial/Documents.java | 10 +++-- 4 files changed, 55 insertions(+), 23 deletions(-) diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Books.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Books.java index 4d344d3..4a83ba6 100644 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Books.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise3/Books.java @@ -13,15 +13,15 @@ * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 2 of the + * published by the Free Software Foundation, either version 2 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public + * + * You should have received a copy of the GNU General Public * License along with this program. If not, see * . * #L% diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java index fda540f..a79d97b 100644 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java @@ -1,5 +1,27 @@ package org.adoptopenjdk.lambda.tutorial.exercise4; +/* + * #%L + * lambda-tutorial + * %% + * Copyright (C) 2013 Adopt OpenJDK + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + import java.util.ArrayList; import java.util.Collections; import java.util.List; diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Printers.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Printers.java index 4bd6d76..1f394ad 100644 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Printers.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Printers.java @@ -1,5 +1,27 @@ package org.adoptopenjdk.lambda.tutorial.exercise4; +/* + * #%L + * lambda-tutorial + * %% + * Copyright (C) 2013 Adopt OpenJDK + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + import java.util.Arrays; import java.util.List; import static java.util.stream.Collectors.toList; @@ -19,20 +41,4 @@ public static void printPages(Document doc, Integer... pageNumbers) { public static Stream createPagesFrom(Stream contents) { return contents.map(Page::new); } - - - public static void main(String... args) { - Page p1 = new Page("this is the first page"); - Page p2 = new Page("this is the second page"); - - Document myDocument = new Document(Arrays.asList(p1, p2)); - - Printers.printPages(myDocument, 0, 1); - - List pages = Arrays.asList(p1, p2); - pages.stream().map(Page::getContent).forEach(Printers::print); - - Stream pagesFromContent = createPagesFrom(Arrays.asList("a", "b").stream()); - System.out.println(pagesFromContent.collect(toList())); - } } diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java index af52656..48792dc 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java @@ -24,6 +24,7 @@ import org.adoptopenjdk.lambda.tutorial.exercise4.Document; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -35,8 +36,11 @@ public class Documents { * Return the titles from a list of documents. */ public static List titlesOf(Document... documents) { - return Arrays.stream(documents) - .map(d -> d.getTitle()) - .collect(toList()); + // No equivalent in pre-Java 8 + List titles = new ArrayList<>(); + for (Document doc: documents) { + titles.add(doc.getTitle()); // Document::getTitle + } + return titles; } } From 36e3816820115a8087882e5dcc0b908e5b6b2b77 Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Fri, 4 Oct 2013 18:24:04 +0100 Subject: [PATCH 39/61] Make the name of test consistent with the category of method reference as described in introduction. --- .../java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java index 931bf67..06ad9ee 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java @@ -156,7 +156,7 @@ public class Exercise_4_Test { * */ @Test - public void getListOfDocumentTitlesUsingInstanceMethodReference() { + public void getListOfDocumentTitlesUsingReferenceOfInstanceMethodBelongingToAClass() { Document expenses = new Document("My Expenses", Arrays.asList(new Page("LJC Open Conference ticket: £25"), new Page("Beer stipend: £100"))); Document toDoList = new Document("My ToDo List", From 09fec08c8e5033d76494b3fd4770273efe88745c Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Fri, 4 Oct 2013 18:48:16 +0100 Subject: [PATCH 40/61] Add an example for a reference to a static method. Conflicts: src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Printers.java --- .../lambda/tutorial/exercise4/Document.java | 4 +- .../lambda/tutorial/exercise4/Printers.java | 44 ------------------- .../lambda/tutorial/Documents.java | 9 ++++ .../lambda/tutorial/Exercise_4_Test.java | 25 ++++++++++- 4 files changed, 34 insertions(+), 48 deletions(-) delete mode 100644 src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Printers.java diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java index a79d97b..f6c4b9f 100644 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java @@ -35,8 +35,8 @@ public Document(String title, List pages) { this.pages = Collections.unmodifiableList(new ArrayList<>(pages)); } - public String getPageContent(Integer pageNumber) { - return this.pages.get(pageNumber).getContent(); + public List getPages() { + return this.pages; } public String getTitle() { diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Printers.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Printers.java deleted file mode 100644 index 1f394ad..0000000 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Printers.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.adoptopenjdk.lambda.tutorial.exercise4; - -/* - * #%L - * lambda-tutorial - * %% - * Copyright (C) 2013 Adopt OpenJDK - * %% - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program. If not, see - * . - * #L% - */ - -import java.util.Arrays; -import java.util.List; -import static java.util.stream.Collectors.toList; -import java.util.stream.Stream; - -import org.adoptopenjdk.lambda.tutorial.exercise4.Document.Page; - -public class Printers { - public static void print(String s) { - System.out.println(s); - } - - public static void printPages(Document doc, Integer... pageNumbers) { - Arrays.stream(pageNumbers).map(doc::getPageContent).forEach(Printers::print); - } - - public static Stream createPagesFrom(Stream contents) { - return contents.map(Page::new); - } -} diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java index 48792dc..f1963a0 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java @@ -23,6 +23,7 @@ */ import org.adoptopenjdk.lambda.tutorial.exercise4.Document; +import org.adoptopenjdk.lambda.tutorial.exercise4.Document.Page; import java.util.ArrayList; import java.util.Arrays; @@ -43,4 +44,12 @@ public static List titlesOf(Document... documents) { } return titles; } + + public static Integer characterCount(Page page) { + return page.getContent().length(); + } + + public static List pageCharacterCounts(Document document) { + return document.getPages().stream().map(doc -> Documents.characterCount(doc)).collect(toList()); + } } diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java index 06ad9ee..b96eb7e 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java @@ -24,7 +24,7 @@ import org.adoptopenjdk.lambda.tutorial.exercise4.Document; import org.adoptopenjdk.lambda.tutorial.exercise4.Document.Page; -import org.hamcrest.Matchers; +import static org.hamcrest.Matchers.*; import org.junit.Test; import java.util.Arrays; @@ -165,9 +165,30 @@ public void getListOfDocumentTitlesUsingReferenceOfInstanceMethodBelongingToACla Arrays.asList(new Page("Oracle Certified Professional"), new Page("Swimming 10m"))); assertThat(Documents.titlesOf(expenses, toDoList, certificates), - Matchers.contains("My Expenses", "My ToDo List", "My Certificates")); + contains("My Expenses", "My ToDo List", "My Certificates")); assertThat(Documents.class, usesMethodReferences("getTitle")); } + /** + * The Documents class has a method which calculates a list of the character counts of Pages in a + * Document. The method characterCount can be applied to each Page to calculate the number of + * characters in that page. Currently it is invoked using a lambda. + *
+ * Change to use a method reference which uses the static characterCount method. + * + * @see Documents#pageCharacterCounts(Document) + * @see Documents#characterCount(Page) + */ + @Test + public void getListOfPageCharacterCountsFromDocumentUsingReferenceOfStaticMethodBelongingToAClass() { + Document diary = new Document("My Diary", Arrays.asList( + new Page("Today I went shopping"), + new Page("Today I did maths"), + new Page("Today I wrote in my diary"))); + + assertThat(Documents.pageCharacterCounts(diary), contains(21, 17, 25)); + assertThat(Documents.class, usesMethodReferences("characterCount")); + } + } From 039bf71841e07f3971736dad353f32711225a9ec Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Fri, 4 Oct 2013 18:53:07 +0100 Subject: [PATCH 41/61] Show pre-java 8 solution. --- .../java/org/adoptopenjdk/lambda/tutorial/Documents.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java index f1963a0..7984ac1 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java @@ -50,6 +50,11 @@ public static Integer characterCount(Page page) { } public static List pageCharacterCounts(Document document) { - return document.getPages().stream().map(doc -> Documents.characterCount(doc)).collect(toList()); + // No equivalent in pre-Java 8 + List characterCounts = new ArrayList<>(); + for (Page page: document.getPages()) { + characterCounts.add(Documents.characterCount(page)); // Documents::characterCount + } + return characterCounts; } } From 67b75399b6a7e818a1a67197fa2020e908be5c5e Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Fri, 4 Oct 2013 19:34:20 +0100 Subject: [PATCH 42/61] Add test case for using method references to invoke methods on a particular instance. --- .../tutorial/exercise4/PagePrinter.java | 48 ++++++++++++++++ .../lambda/tutorial/Documents.java | 13 +++++ .../lambda/tutorial/Exercise_4_Test.java | 37 +++++++++++++ .../util/StringWithComparisonMatcher.java | 55 +++++++++++++++++++ 4 files changed, 153 insertions(+) create mode 100644 src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/PagePrinter.java create mode 100644 src/test/java/org/adoptopenjdk/lambda/tutorial/util/StringWithComparisonMatcher.java diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/PagePrinter.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/PagePrinter.java new file mode 100644 index 0000000..503692a --- /dev/null +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/PagePrinter.java @@ -0,0 +1,48 @@ +package org.adoptopenjdk.lambda.tutorial.exercise4; + +/* + * #%L + * lambda-tutorial + * %% + * Copyright (C) 2013 Adopt OpenJDK + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +import org.adoptopenjdk.lambda.tutorial.exercise4.Document.Page; + +import static java.lang.String.format; + +public final class PagePrinter { + + private final String pageBreak; + + public PagePrinter(String pageBreak) { + this.pageBreak = pageBreak; + } + + public String printTitlePage(Document document) { + return format( + "%s%n" + + "%s%n", document.getTitle(), pageBreak); + } + + public String printPage(Page page) { + return format( + "%s%n" + + "%s%n", page.getContent(), pageBreak); + } +} diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java index 7984ac1..7d7d5a2 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java @@ -24,11 +24,13 @@ import org.adoptopenjdk.lambda.tutorial.exercise4.Document; import org.adoptopenjdk.lambda.tutorial.exercise4.Document.Page; +import org.adoptopenjdk.lambda.tutorial.exercise4.PagePrinter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import static java.lang.String.format; import static java.util.stream.Collectors.toList; public class Documents { @@ -57,4 +59,15 @@ public static List pageCharacterCounts(Document document) { } return characterCounts; } + + public static String print(Document document, PagePrinter pagePrinter) { + StringBuilder output = new StringBuilder(); + + output.append(pagePrinter.printTitlePage(document)); + document.getPages().stream() + .map(p -> pagePrinter.printPage(p)) + .forEach(s -> output.append(s)); + + return output.toString(); + } } diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java index b96eb7e..58de542 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java @@ -24,7 +24,11 @@ import org.adoptopenjdk.lambda.tutorial.exercise4.Document; import org.adoptopenjdk.lambda.tutorial.exercise4.Document.Page; + +import static java.lang.String.format; import static org.hamcrest.Matchers.*; + +import org.adoptopenjdk.lambda.tutorial.exercise4.PagePrinter; import org.junit.Test; import java.util.Arrays; @@ -32,6 +36,7 @@ import static org.adoptopenjdk.lambda.tutorial.util.CodeUsesMethodReferencesMatcher.usesMethodReferences; import static org.hamcrest.MatcherAssert.assertThat; +import static org.adoptopenjdk.lambda.tutorial.util.StringWithComparisonMatcher.*; /** * Exercise 4 - Method References @@ -191,4 +196,36 @@ public void getListOfPageCharacterCountsFromDocumentUsingReferenceOfStaticMethod assertThat(Documents.class, usesMethodReferences("characterCount")); } + /** + * The Documents class has a method which takes a PagePrinter and renders a + * Document to text. The method uses two lambda expressions where method references can be used. In + * this case the method references are to methods belonging to a particular instance object. + *
+ * Change {@link Documents#print(Document, PagePrinter)} to use method references to invoke instance methods of + * particular objects. + * + * @see Documents#print(Document, PagePrinter) + * @see StringBuilder#append + * @see PagePrinter#printPage(Page) + * + */ + @Test + public void printContentsOfDocumentUsingReferenceOfInstanceMethodBeloningToAnObject() { + Document diary = new Document("My Diary", Arrays.asList( + new Page("Today I went shopping"), + new Page("Today I did maths"), + new Page("Today I wrote in my diary"))); + + assertThat(Documents.print(diary, new PagePrinter("----")), + isString(format("My Diary%n" + + "----%n" + + "Today I went shopping%n" + + "----%n" + + "Today I did maths%n" + + "----%n" + + "Today I wrote in my diary%n" + + "----%n"))); + assertThat(Documents.class, allOf(usesMethodReferences("printPage"), usesMethodReferences("append"))); + } + } diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/util/StringWithComparisonMatcher.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/util/StringWithComparisonMatcher.java new file mode 100644 index 0000000..dc661d0 --- /dev/null +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/util/StringWithComparisonMatcher.java @@ -0,0 +1,55 @@ +package org.adoptopenjdk.lambda.tutorial.util; + +/* + * #%L + * lambda-tutorial + * %% + * Copyright (C) 2013 Adopt OpenJDK + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; +import org.junit.ComparisonFailure; + +public class StringWithComparisonMatcher extends TypeSafeDiagnosingMatcher { + private final String expected; + + private StringWithComparisonMatcher(String expected) { + this.expected = expected; + } + + public static StringWithComparisonMatcher isString(String expected) { + return new StringWithComparisonMatcher(expected); + } + + @Override + public void describeTo(Description description) { + description.appendText(expected); + } + + @Override + protected boolean matchesSafely(String actual, Description mismatchDescription) { + if (!expected.equals(actual)) { + String compactedMismatch = new ComparisonFailure("did not match:", expected, actual).getMessage(); + mismatchDescription.appendText(compactedMismatch); + return false; + } + return true; + } + +} \ No newline at end of file From c470f56d0c48beb1d18af6f5a6c1757d5085dc1a Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Fri, 4 Oct 2013 19:46:35 +0100 Subject: [PATCH 43/61] Add passing test case for pre-java 8. --- .../java/org/adoptopenjdk/lambda/tutorial/Documents.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java index 7d7d5a2..ee0b25c 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java @@ -61,12 +61,15 @@ public static List pageCharacterCounts(Document document) { } public static String print(Document document, PagePrinter pagePrinter) { + // No equivalent in pre-Java 8 StringBuilder output = new StringBuilder(); output.append(pagePrinter.printTitlePage(document)); - document.getPages().stream() - .map(p -> pagePrinter.printPage(p)) - .forEach(s -> output.append(s)); + + for (Page page: document.getPages()) { + String content = pagePrinter.printPage(page); // PagePrinter::printPage + output.append(content); // StringBuilder::append + } return output.toString(); } From 3ac42ed546067662f0e97bc4072e7c4e8043f7b4 Mon Sep 17 00:00:00 2001 From: Grundlefleck Date: Sun, 6 Oct 2013 21:48:23 +0100 Subject: [PATCH 44/61] Add test case to show how to use method reference using 'this'. --- .../lambda/tutorial/exercise4/Document.java | 19 ++++++++++++ .../lambda/tutorial/Exercise_4_Test.java | 31 +++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java index f6c4b9f..56976b5 100644 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java @@ -25,6 +25,10 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; + +import static java.lang.String.format; +import static java.util.stream.Collectors.collectingAndThen; public final class Document { private final String title; @@ -43,6 +47,21 @@ public String getTitle() { return this.title; } + private Page appendFooter(Page original) { + String footer = "Document: " + getTitle(); + return new Page(format("%s%n%s", original.getContent(), footer)); + } + + private Document copyWithPages(List newPages) { + return new Document(title, newPages); + } + + public Document copyWithFooter() { + return getPages().stream() + .map(page -> appendFooter(page)) + .collect(collectingAndThen(Collectors.toList(), pages -> copyWithPages(pages))); + } + public static final class Page { private final String content; diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java index 58de542..2a1bc97 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java @@ -29,6 +29,8 @@ import static org.hamcrest.Matchers.*; import org.adoptopenjdk.lambda.tutorial.exercise4.PagePrinter; +import org.adoptopenjdk.lambda.tutorial.util.FeatureMatchers; +import org.hamcrest.Matcher; import org.junit.Test; import java.util.Arrays; @@ -228,4 +230,33 @@ public void printContentsOfDocumentUsingReferenceOfInstanceMethodBeloningToAnObj assertThat(Documents.class, allOf(usesMethodReferences("printPage"), usesMethodReferences("append"))); } + + /** + * The Document class has a method which can create a new Document where all the pages have had a + * footer appended to it of the format "Document: {title}". The method uses two lambda expressions where method + * references can be used. In this case the method references are to methods belonging to this object + * instance. That is, the methods to be invoked should be invoked on this. + *
+ * Change {@link Document#copyWithFooter()} to use method references to invoke instance methods on this + * instance. + */ + @Test + public void transformPagesToHaveFooterUsingReferenceOfInstanceMethodBelonginToThisObject() { + Page blankPage = new Page(""); + Document diary = new Document("My Diary", Arrays.asList( + new Page("Today I went shopping"), + blankPage, + new Page("Today I did maths"), + blankPage, + new Page("Today I wrote in my diary"))); + + Document diaryWithFooters = diary.copyWithFooter(); + + assertThat(diaryWithFooters.getPages(), everyItem(pageEndingWith("Document: My Diary"))); + assertThat(Document.class, allOf(usesMethodReferences("appendFooter"), usesMethodReferences("copyWithPages"))); + } + + private static Matcher pageEndingWith(String ending) { + return FeatureMatchers.from(endsWith(ending), "page containing", "contents", Page::getContent); + } } From f593568c040119d6590c4ac4b0081f47a74a3828 Mon Sep 17 00:00:00 2001 From: Grundlefleck Date: Sun, 6 Oct 2013 21:59:58 +0100 Subject: [PATCH 45/61] Add (non) equivalent in pre java 8. --- .../adoptopenjdk/lambda/tutorial/exercise4/Document.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java index 56976b5..ba1464d 100644 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java @@ -57,9 +57,12 @@ private Document copyWithPages(List newPages) { } public Document copyWithFooter() { - return getPages().stream() - .map(page -> appendFooter(page)) - .collect(collectingAndThen(Collectors.toList(), pages -> copyWithPages(pages))); + // No equivalent in pre-Java 8 + List pagesWithFooter = new ArrayList<>(); + for (Page page: getPages()) { + pagesWithFooter.add(appendFooter(page)); // this::appendFooter + } + return copyWithPages(pagesWithFooter); // this::copyWithPages } public static final class Page { From 806c523a7d7f2851e30fbcc190c834295c4672b9 Mon Sep 17 00:00:00 2001 From: Grundlefleck Date: Sun, 6 Oct 2013 22:04:11 +0100 Subject: [PATCH 46/61] Whoops, forgot to remove blankPage variable from when figuring out what this test should do. --- .../java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java index 2a1bc97..3a6906b 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java @@ -242,12 +242,9 @@ public void printContentsOfDocumentUsingReferenceOfInstanceMethodBeloningToAnObj */ @Test public void transformPagesToHaveFooterUsingReferenceOfInstanceMethodBelonginToThisObject() { - Page blankPage = new Page(""); Document diary = new Document("My Diary", Arrays.asList( new Page("Today I went shopping"), - blankPage, new Page("Today I did maths"), - blankPage, new Page("Today I wrote in my diary"))); Document diaryWithFooters = diary.copyWithFooter(); From f6cf03b593f35803404843b2c9aef566ca55e717 Mon Sep 17 00:00:00 2001 From: Grundlefleck Date: Mon, 7 Oct 2013 21:13:41 +0100 Subject: [PATCH 47/61] Add correctly failing test for using constructor method reference. --- .../lambda/tutorial/exercise4/Translator.java | 19 ++++++++++++++++ .../lambda/tutorial/Documents.java | 10 +++++++++ .../lambda/tutorial/Exercise_4_Test.java | 22 +++++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Translator.java diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Translator.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Translator.java new file mode 100644 index 0000000..61ef0fd --- /dev/null +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Translator.java @@ -0,0 +1,19 @@ +package org.adoptopenjdk.lambda.tutorial.exercise4; + +public interface Translator { + + String translate(String input); + + enum Languages implements Translator { + REVERSISH { + @Override + public String translate(String input) { + return new StringBuilder(input).reverse().toString(); + } + } + + // TODO: implement other, real languages. + } + +} + diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java index ee0b25c..4a279f5 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java @@ -25,12 +25,14 @@ import org.adoptopenjdk.lambda.tutorial.exercise4.Document; import org.adoptopenjdk.lambda.tutorial.exercise4.Document.Page; import org.adoptopenjdk.lambda.tutorial.exercise4.PagePrinter; +import org.adoptopenjdk.lambda.tutorial.exercise4.Translator; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import static java.lang.String.format; +import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.toList; public class Documents { @@ -73,4 +75,12 @@ public static String print(Document document, PagePrinter pagePrinter) { return output.toString(); } + + public static Document translate(Document document, Translator translator) { + return document.getPages().stream() + .map(page -> page.getContent()) + .map(content -> translator.translate(content)) + .map(translated -> new Page(translated)) + .collect(collectingAndThen(toList(), pages -> new Document(document.getTitle(), pages))); + } } diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java index 3a6906b..c8f649c 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java @@ -29,6 +29,7 @@ import static org.hamcrest.Matchers.*; import org.adoptopenjdk.lambda.tutorial.exercise4.PagePrinter; +import org.adoptopenjdk.lambda.tutorial.exercise4.Translator.Languages; import org.adoptopenjdk.lambda.tutorial.util.FeatureMatchers; import org.hamcrest.Matcher; import org.junit.Test; @@ -253,7 +254,28 @@ public void transformPagesToHaveFooterUsingReferenceOfInstanceMethodBelonginToTh assertThat(Document.class, allOf(usesMethodReferences("appendFooter"), usesMethodReferences("copyWithPages"))); } + + @Test + public void createNewDocumentWithTranslatedPagesUsingReferenceOfConstructorMethod() { + Document diary = new Document("My Diary", Arrays.asList( + new Page("Today I went shopping"), + new Page("Today I did maths"), + new Page("Today I wrote in my diary"))); + + Document translated = Documents.translate(diary, Languages.REVERSISH); + + assertThat(translated.getPages(), + contains(pageContaining("gnippohs tnew I yadoT"), + pageContaining("shtam did I yadoT"), + pageContaining("yraid ym ni etorw I yadoT"))); + assertThat(Documents.class, usesMethodReferences("new")); + } + private static Matcher pageEndingWith(String ending) { return FeatureMatchers.from(endsWith(ending), "page containing", "contents", Page::getContent); } + + private static Matcher pageContaining(String content) { + return FeatureMatchers.from(isString(content), "page containing", "contents", Page::getContent); + } } From 28ac65c10f0a02d14ec0a8245b6a7c59fa38b4d9 Mon Sep 17 00:00:00 2001 From: Grundlefleck Date: Mon, 7 Oct 2013 22:33:04 +0100 Subject: [PATCH 48/61] Finish javadoc for final test of method references chapter. --- .../lambda/tutorial/Documents.java | 3 ++- .../lambda/tutorial/Exercise_4_Test.java | 23 +++++++++++++++---- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java index 4a279f5..118dad3 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java @@ -81,6 +81,7 @@ public static Document translate(Document document, Translator translator) { .map(page -> page.getContent()) .map(content -> translator.translate(content)) .map(translated -> new Page(translated)) - .collect(collectingAndThen(toList(), pages -> new Document(document.getTitle(), pages))); + .collect(collectingAndThen(toList(), + pages -> new Document(translator.translate(document.getTitle()), pages))); } } diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java index c8f649c..85ff9f6 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java @@ -24,11 +24,8 @@ import org.adoptopenjdk.lambda.tutorial.exercise4.Document; import org.adoptopenjdk.lambda.tutorial.exercise4.Document.Page; - -import static java.lang.String.format; -import static org.hamcrest.Matchers.*; - import org.adoptopenjdk.lambda.tutorial.exercise4.PagePrinter; +import org.adoptopenjdk.lambda.tutorial.exercise4.Translator; import org.adoptopenjdk.lambda.tutorial.exercise4.Translator.Languages; import org.adoptopenjdk.lambda.tutorial.util.FeatureMatchers; import org.hamcrest.Matcher; @@ -37,9 +34,14 @@ import java.util.Arrays; import java.util.function.Consumer; +import static java.lang.String.format; import static org.adoptopenjdk.lambda.tutorial.util.CodeUsesMethodReferencesMatcher.usesMethodReferences; +import static org.adoptopenjdk.lambda.tutorial.util.StringWithComparisonMatcher.isString; import static org.hamcrest.MatcherAssert.assertThat; -import static org.adoptopenjdk.lambda.tutorial.util.StringWithComparisonMatcher.*; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.everyItem; /** * Exercise 4 - Method References @@ -255,6 +257,17 @@ public void transformPagesToHaveFooterUsingReferenceOfInstanceMethodBelonginToTh } + /** + * The Documents class has a method which can translate a document into another language. The method + * uses a lambda expression to construct each translated Page, where it could use a method reference + * to Page's constructor. + *
+ * Change {@link Documents#translate} to use a method reference to construct each translated Page. + * + * @see Documents#translate(Document, Translator) + * @see Translator.Languages + * @see Page + */ @Test public void createNewDocumentWithTranslatedPagesUsingReferenceOfConstructorMethod() { Document diary = new Document("My Diary", Arrays.asList( From 680efa27895405911c4469347101afc33b33430c Mon Sep 17 00:00:00 2001 From: Grundlefleck Date: Mon, 7 Oct 2013 22:39:20 +0100 Subject: [PATCH 49/61] Show (non) equivalent of constructor method refs. --- .../adoptopenjdk/lambda/tutorial/Documents.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java index 118dad3..71b8890 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java @@ -77,11 +77,16 @@ public static String print(Document document, PagePrinter pagePrinter) { } public static Document translate(Document document, Translator translator) { - return document.getPages().stream() - .map(page -> page.getContent()) - .map(content -> translator.translate(content)) - .map(translated -> new Page(translated)) - .collect(collectingAndThen(toList(), - pages -> new Document(translator.translate(document.getTitle()), pages))); + // No equivalent in pre-Java 8 + + List translatedPages = new ArrayList<>(); + for (Page page: document.getPages()) { + String content = page.getContent(); + String translatedContent = translator.translate(content); + Page translatedPage = new Page(translatedContent); // Page::new + translatedPages.add(translatedPage); + } + + return new Document(translator.translate(document.getTitle()), translatedPages); } } From 935247b3708531c34aa8000a73ffe36f52138432 Mon Sep 17 00:00:00 2001 From: Grundlefleck Date: Mon, 7 Oct 2013 22:42:13 +0100 Subject: [PATCH 50/61] Merge README from master. --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index c8123ae..6742287 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ To follow the exercises: 1. Internal vs External Iteration (the forEach method) 2. Filtering and Collecting 3. Mapping + 4. (In Progress) Method References [More to come] @@ -34,3 +35,11 @@ The current tutorial is known to work with the following JDK build: |ea b109 |Sep 26, 2013| lambda-tutorial will try to track against the newest version available. If you find that you are working with a newer version of the Lambda JDK and the tutorial does not compile or run, please file an issue. + +### IDE Setup +- [IntelliJ IDEA on Ubuntu](https://github.com/AdoptOpenJDK/lambda-tutorial/wiki/IntelliJ-IDEA-on-Ubuntu-%5BLinux%5D) +- [IntelliJ IDEA on MacOS](https://github.com/AdoptOpenJDK/lambda-tutorial/wiki/IntelliJ-IDEA-on-MacOS) +- [IntelliJ IDEA deutsche Anleitung (u.a. Windows)](https://github.com/AdoptOpenJDK/lambda-tutorial/wiki/IntelliJ-IDEA-Einrichtung) +- [Eclipse Kepler 4.3 on Windows](https://github.com/AdoptOpenJDK/lambda-tutorial/wiki/Eclipse-Lambda-EA-Setup) + +Note: we are hoping the instructions are not too sensitive to the OSes on which they have been performed. From e2f7d54b01a7411202a4174afafe4774184a7a77 Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Sun, 13 Oct 2013 21:04:38 +0100 Subject: [PATCH 51/61] Move Documents class into the appropriate package and source folder. --- .../lambda/tutorial/exercise4}/Documents.java | 8 +------ .../lambda/tutorial/exercise4/Translator.java | 22 +++++++++++++++++++ .../lambda/tutorial/Exercise_4_Test.java | 1 + 3 files changed, 24 insertions(+), 7 deletions(-) rename src/{test/java/org/adoptopenjdk/lambda/tutorial => main/java/org/adoptopenjdk/lambda/tutorial/exercise4}/Documents.java (88%) diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Documents.java similarity index 88% rename from src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java rename to src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Documents.java index 71b8890..b4fa38a 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Documents.java @@ -1,4 +1,4 @@ -package org.adoptopenjdk.lambda.tutorial; +package org.adoptopenjdk.lambda.tutorial.exercise4; /* * #%L @@ -22,18 +22,12 @@ * #L% */ -import org.adoptopenjdk.lambda.tutorial.exercise4.Document; import org.adoptopenjdk.lambda.tutorial.exercise4.Document.Page; -import org.adoptopenjdk.lambda.tutorial.exercise4.PagePrinter; -import org.adoptopenjdk.lambda.tutorial.exercise4.Translator; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import static java.lang.String.format; -import static java.util.stream.Collectors.collectingAndThen; -import static java.util.stream.Collectors.toList; public class Documents { diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Translator.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Translator.java index 61ef0fd..7d3a700 100644 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Translator.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Translator.java @@ -1,5 +1,27 @@ package org.adoptopenjdk.lambda.tutorial.exercise4; +/* + * #%L + * lambda-tutorial + * %% + * Copyright (C) 2013 Adopt OpenJDK + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + public interface Translator { String translate(String input); diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java index 85ff9f6..1bb2ffc 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java @@ -24,6 +24,7 @@ import org.adoptopenjdk.lambda.tutorial.exercise4.Document; import org.adoptopenjdk.lambda.tutorial.exercise4.Document.Page; +import org.adoptopenjdk.lambda.tutorial.exercise4.Documents; import org.adoptopenjdk.lambda.tutorial.exercise4.PagePrinter; import org.adoptopenjdk.lambda.tutorial.exercise4.Translator; import org.adoptopenjdk.lambda.tutorial.exercise4.Translator.Languages; From 8efe2c66ec761e1955299fd204d009e74398ee9d Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Sat, 19 Oct 2013 12:44:12 +0100 Subject: [PATCH 52/61] Method References chapter is now considered complete. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6742287..48fd16a 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ To follow the exercises: 1. Internal vs External Iteration (the forEach method) 2. Filtering and Collecting 3. Mapping - 4. (In Progress) Method References + 4. Method References [More to come] From 19ea12fbcf79fbf08d1004fef8a08de6f230f5eb Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Sun, 16 Feb 2014 12:49:08 +0000 Subject: [PATCH 53/61] Add Travis CI builds to pre-jdk8 tests. --- .travis.yml | 6 ++++++ README.md | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..6c23cde --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ +language: java +jdk: + - oraclejdk7 + - openjdk7 +script: "mvn clean test" + diff --git a/README.md b/README.md index 48fd16a..d1425a6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -## Lambda Tutorial +## Lambda Tutorial +[![Build Status](https://api.travis-ci.org/AdoptOpenJDK/lambda-tutorial.png)](https://travis-ci.org/AdoptOpenJDK/lambda-tutorial) A set of exercises to teach use of Java 8 lambda syntax, and the new Streams API. @@ -43,3 +44,4 @@ lambda-tutorial will try to track against the newest version available. If you f - [Eclipse Kepler 4.3 on Windows](https://github.com/AdoptOpenJDK/lambda-tutorial/wiki/Eclipse-Lambda-EA-Setup) Note: we are hoping the instructions are not too sensitive to the OSes on which they have been performed. + From 33737e4e65d9b7b17b4b82a84ed35e5d12ca3803 Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Sun, 16 Feb 2014 13:01:00 +0000 Subject: [PATCH 54/61] Retrieve build status icon for the correct branch. --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index d1425a6..00397b6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -## Lambda Tutorial -[![Build Status](https://api.travis-ci.org/AdoptOpenJDK/lambda-tutorial.png)](https://travis-ci.org/AdoptOpenJDK/lambda-tutorial) +## Lambda Tutorial [![Build Status](https://api.travis-ci.org/AdoptOpenJDK/lambda-tutorial.png?branch=solutions-prejava8)](https://travis-ci.org/AdoptOpenJDK/lambda-tutorial) A set of exercises to teach use of Java 8 lambda syntax, and the new Streams API. From 568921d3b4f3fe4beaaf68ebbbf7920f6cf6b844 Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Sun, 16 Feb 2014 13:10:11 +0000 Subject: [PATCH 55/61] Correct jdk version used. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 688d5cc..046741e 100644 --- a/pom.xml +++ b/pom.xml @@ -64,7 +64,7 @@ UTF-8 - 1.8 + 1.7 From 837a8dcc09192495e3aa97878de9bf0a00caa0fa Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Sun, 16 Feb 2014 14:04:55 +0000 Subject: [PATCH 56/61] Allow tests to pass against JDK 7. Involves removing all references to JDK 8 lib classes and syntax. --- .../ConfigureYourLambdaBuildOfJdk.java | 4 +- .../tutorial/exercise2/ElectoralDistrict.java | 1 - .../lambda/tutorial/exercise4/Document.java | 2 - .../lambda/tutorial/Exercise_1_Test.java | 6 +- .../lambda/tutorial/Exercise_2_Test.java | 23 ++- .../lambda/tutorial/Exercise_3_Test.java | 10 +- .../lambda/tutorial/Exercise_4_Test.java | 20 ++- .../util/CodeUsesMethodReferencesMatcher.java | 169 ------------------ .../lambda/tutorial/util/FeatureMatchers.java | 13 +- 9 files changed, 47 insertions(+), 201 deletions(-) delete mode 100644 src/test/java/org/adoptopenjdk/lambda/tutorial/util/CodeUsesMethodReferencesMatcher.java diff --git a/src/main/java/org/adoptopenjdk/lambda/setupcheck/ConfigureYourLambdaBuildOfJdk.java b/src/main/java/org/adoptopenjdk/lambda/setupcheck/ConfigureYourLambdaBuildOfJdk.java index 8c87c4a..2385664 100644 --- a/src/main/java/org/adoptopenjdk/lambda/setupcheck/ConfigureYourLambdaBuildOfJdk.java +++ b/src/main/java/org/adoptopenjdk/lambda/setupcheck/ConfigureYourLambdaBuildOfJdk.java @@ -51,6 +51,8 @@ public static void main(String... args) { "", "Until this source file compiles, you will be unable to make progress in the tutorial."); - messages.forEach(System.out::println); + // This is not expected to compile with JDK 7. + // (for pre-JDK8 solutions examples only) + // messages.forEach(System.out::println); } } diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java index 9628499..769b08c 100644 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise2/ElectoralDistrict.java @@ -26,7 +26,6 @@ import java.util.Collections; import java.util.HashSet; import java.util.Set; -import java.util.stream.Collectors; /** * Some (inaccurate) London electrical districts diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java index ba1464d..82c8986 100644 --- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java +++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java @@ -25,10 +25,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; import static java.lang.String.format; -import static java.util.stream.Collectors.collectingAndThen; public final class Document { private final String title; diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_1_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_1_Test.java index 4a4a0b9..00ea7f4 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_1_Test.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_1_Test.java @@ -165,6 +165,10 @@ public void changeColorOfAllShapes_AND_buildStringShowingAllTheOldColors() { // ----- Test helpers ----- private static Matcher hasColor(Color color) { - return FeatureMatchers.from(Matchers.is(color), "has color", "color", Shape::getColor); + return FeatureMatchers.from(Matchers.is(color), "has color", "color", new FeatureMatchers.Extractor() { + @Override public Color get(Shape shape) { + return shape.getColor(); + } + }); } } diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java index 4803a9e..eee3c02 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_2_Test.java @@ -38,11 +38,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.stream.Collector; -import java.util.stream.Collectors; -import java.util.stream.Stream; import static java.util.Arrays.asList; import static java.util.Arrays.binarySearch; @@ -268,15 +263,27 @@ public void getAllPersonsEligibleToVote() { // Test helpers private static Matcher aPersonNamed(String name) { - return FeatureMatchers.from(is(name), "a person", "name", person -> person.getName()); + return FeatureMatchers.from(is(name), "a person", "name", new FeatureMatchers.Extractor() { + @Override public String get(Person person) { + return person.getName(); + } + }); } private static Matcher aVoterWithId(String name) { - return FeatureMatchers.from(is(name), "a voter", "electorId", voter -> voter.getElectorId()); + return FeatureMatchers.from(is(name), "a voter", "electorId", new FeatureMatchers.Extractor() { + @Override public String get(RegisteredVoter registeredVoter) { + return registeredVoter.getElectorId(); + } + }); } private static Matcher spoiled() { - return FeatureMatchers.from(equalTo(Boolean.TRUE), "a spoiled ballot", "isSpoiled", ballot -> ballot.isSpoiled()); + return FeatureMatchers.from(equalTo(Boolean.TRUE), "a spoiled ballot", "isSpoiled", new FeatureMatchers.Extractor() { + @Override public Boolean get(Ballot ballot) { + return ballot.isSpoiled(); + } + }); } } \ No newline at end of file diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_3_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_3_Test.java index df14c3a..8ebaff3 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_3_Test.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_3_Test.java @@ -33,10 +33,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; -import java.util.function.Function; -import java.util.stream.Collector; -import java.util.stream.Collectors; -import java.util.stream.Stream; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; @@ -185,7 +181,11 @@ public void getPublishersRepresentedByBooks() { // Test helpers private static Matcher publisherNamed(String name) { - return FeatureMatchers.from(equalTo(name), "is named", "name", publisher -> publisher.getName()); + return FeatureMatchers.from(equalTo(name), "is named", "name", new FeatureMatchers.Extractor() { + @Override public String get(Publisher publisher) { + return publisher.getName(); + } + }); } } diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java index 1bb2ffc..9f2abe5 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java @@ -33,10 +33,8 @@ import org.junit.Test; import java.util.Arrays; -import java.util.function.Consumer; import static java.lang.String.format; -import static org.adoptopenjdk.lambda.tutorial.util.CodeUsesMethodReferencesMatcher.usesMethodReferences; import static org.adoptopenjdk.lambda.tutorial.util.StringWithComparisonMatcher.isString; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.allOf; @@ -177,7 +175,6 @@ public void getListOfDocumentTitlesUsingReferenceOfInstanceMethodBelongingToACla assertThat(Documents.titlesOf(expenses, toDoList, certificates), contains("My Expenses", "My ToDo List", "My Certificates")); - assertThat(Documents.class, usesMethodReferences("getTitle")); } @@ -199,7 +196,6 @@ public void getListOfPageCharacterCountsFromDocumentUsingReferenceOfStaticMethod new Page("Today I wrote in my diary"))); assertThat(Documents.pageCharacterCounts(diary), contains(21, 17, 25)); - assertThat(Documents.class, usesMethodReferences("characterCount")); } /** @@ -231,7 +227,6 @@ public void printContentsOfDocumentUsingReferenceOfInstanceMethodBeloningToAnObj "----%n" + "Today I wrote in my diary%n" + "----%n"))); - assertThat(Documents.class, allOf(usesMethodReferences("printPage"), usesMethodReferences("append"))); } @@ -254,7 +249,6 @@ public void transformPagesToHaveFooterUsingReferenceOfInstanceMethodBelonginToTh Document diaryWithFooters = diary.copyWithFooter(); assertThat(diaryWithFooters.getPages(), everyItem(pageEndingWith("Document: My Diary"))); - assertThat(Document.class, allOf(usesMethodReferences("appendFooter"), usesMethodReferences("copyWithPages"))); } @@ -282,14 +276,22 @@ public void createNewDocumentWithTranslatedPagesUsingReferenceOfConstructorMetho contains(pageContaining("gnippohs tnew I yadoT"), pageContaining("shtam did I yadoT"), pageContaining("yraid ym ni etorw I yadoT"))); - assertThat(Documents.class, usesMethodReferences("new")); } private static Matcher pageEndingWith(String ending) { - return FeatureMatchers.from(endsWith(ending), "page containing", "contents", Page::getContent); + return FeatureMatchers.from(endsWith(ending), "page containing", "contents", new FeatureMatchers.Extractor() { + @Override public String get(Page page) { + return page.getContent(); + } + }); } private static Matcher pageContaining(String content) { - return FeatureMatchers.from(isString(content), "page containing", "contents", Page::getContent); + return FeatureMatchers.from(isString(content), "page containing", "contents", new FeatureMatchers.Extractor() { + @Override + public String get(Page page) { + return page.getContent(); + } + }); } } diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/util/CodeUsesMethodReferencesMatcher.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/util/CodeUsesMethodReferencesMatcher.java deleted file mode 100644 index 70a9ba2..0000000 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/util/CodeUsesMethodReferencesMatcher.java +++ /dev/null @@ -1,169 +0,0 @@ -package org.adoptopenjdk.lambda.tutorial.util; - -/* - * #%L - * lambda-tutorial - * %% - * Copyright (C) 2013 Adopt OpenJDK - * %% - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program. If not, see - * . - * #L% - */ - -import org.hamcrest.Description; -import org.hamcrest.TypeSafeDiagnosingMatcher; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.Opcodes; - -import java.io.File; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Arrays; -import java.util.Optional; - -import static java.util.stream.Collectors.toList; - -public final class CodeUsesMethodReferencesMatcher extends TypeSafeDiagnosingMatcher> { - - private final String methodName; - - private CodeUsesMethodReferencesMatcher(String methodName) { - this.methodName = methodName; - } - - public static CodeUsesMethodReferencesMatcher usesMethodReferences(String methodName) { - return new CodeUsesMethodReferencesMatcher(methodName); - } - - @Override - public void describeTo(Description description) { - description.appendText("a source file using a method reference to invoke ").appendValue(methodName); - } - - - @Override - protected boolean matchesSafely(Class clazz, Description mismatchDescription) { - try { - Optional sourceFileContent = getSourceContent(clazz); - return sourceFileContent.map(c -> usesMethodReference(c, mismatchDescription)).orElseGet(() -> { - mismatchDescription.appendText("could not read source file to discover if you used method references."); - return false; - }); - } catch (IOException e) { - mismatchDescription.appendText("could not read source file to discover if you used method references."); - mismatchDescription.appendValue(e); - return false; - } - } - - private boolean usesMethodReference(String sourceCode, Description mismatchDescription) { - if (sourceCode.contains("::"+methodName)) { - return true; - } else { - mismatchDescription.appendText("source code did not use a method reference to invoke " + methodName + ". "); - context(sourceCode, methodName, mismatchDescription); - return false; - } - } - - private void context(String sourceCode, String methodName, Description mismatchDescription) { - if (!sourceCode.contains(methodName)) { - mismatchDescription.appendText("You did not appear to invoke the method at all."); - } else { - String[] lines = sourceCode.split("\\n"); - mismatchDescription.appendText("Actual invocations: "); - mismatchDescription.appendValueList("[", ",", "]", - Arrays.stream(lines).filter(l -> l.contains(methodName)).map(String::trim).collect(toList())); - } - } - - private Optional getSourceContent(Class clazz) throws IOException { - String sourceFileName = getSourceFileName(clazz); - Optional sourceFile = findPathTo(sourceFileName); - - return sourceFile.map(this::toContent); - } - - private Optional findPathTo(String sourceFileName) throws IOException { - File cwd = new File("."); - File rootOfProject = findRootOfProject(cwd); - return findSourceFile(rootOfProject, sourceFileName); - } - - private String toContent(File file) { - try { - byte[] encoded = Files.readAllBytes(Paths.get(file.toURI())); - return StandardCharsets.UTF_8.decode(ByteBuffer.wrap(encoded)).toString(); - } catch (IOException e) { - throw new RuntimeException("Could not read Java source file.", e); - } - } - - private Optional findSourceFile(File rootOfProject, String sourceFileName) throws IOException { - Path startingDir = Paths.get(rootOfProject.toURI()); - return Files.find(startingDir, 15, (path, attrs) -> path.endsWith(sourceFileName)) - .map(p -> new File(p.toUri())) - .findFirst(); - } - - private File findRootOfProject(File cwd) { - File[] pomFiles = cwd.listFiles((file, name) -> { return name.equals("pom.xml"); }); - if (pomFiles != null && pomFiles.length == 1) { - return cwd; - } else if (cwd.getParentFile() == null) { - throw new RuntimeException("Couldn't find directory containing pom.xml. Last looked in: " + cwd.getAbsolutePath()); - } else { - return findRootOfProject(cwd.getParentFile()); - } - } - - private String getSourceFileName(Class clazz) throws IOException { - String resourceName = clazz.getName().replace(".", "/").concat(".class"); - ClassReader reader = new ClassReader(clazz.getClassLoader().getResourceAsStream(resourceName)); - SourceFileNameVisitor sourceFileNameVisitor = new SourceFileNameVisitor(); - reader.accept(sourceFileNameVisitor, 0); - - return sourceFileNameVisitor.getSourceFile(); - } - - - private static final class SourceFileNameVisitor extends ClassVisitor { - - private String sourceFile = null; - private boolean visitedYet = false; - - public SourceFileNameVisitor() { - super(Opcodes.ASM5); - } - - @Override - public void visitSource(String source, String debug) { - this.visitedYet = true; - this.sourceFile = source; - super.visitSource(source, debug); - } - - public String getSourceFile() { - if (!visitedYet) throw new IllegalStateException("Must visit a class before asking for source file"); - return this.sourceFile; - } - } - -} diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/util/FeatureMatchers.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/util/FeatureMatchers.java index 7010d57..005c066 100644 --- a/src/test/java/org/adoptopenjdk/lambda/tutorial/util/FeatureMatchers.java +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/util/FeatureMatchers.java @@ -25,7 +25,6 @@ import org.hamcrest.FeatureMatcher; import org.hamcrest.Matcher; -import java.util.function.Function; /** * Helper utility for matching on features @@ -46,14 +45,18 @@ private FeatureMatchers() {} * @return A Matcher */ public static Matcher from(Matcher featureMatcher, - String description, - String name, - Function extractor) { + String description, + String name, + final Extractor extractor) { return new FeatureMatcher(featureMatcher, description, name) { @Override protected FEATURE featureValueOf(FROM t) { - return extractor.apply(t); + return extractor.get(t); } }; } + public static interface Extractor { + TO get(FROM from); + } + } From b05a4b959a0b4a6690cc55dc38e5e6575b8c2879 Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Wed, 8 Apr 2020 22:20:46 +0100 Subject: [PATCH 57/61] Remove outdated instructions and documentation. In 2020, there's no need to go into detail about getting lambda enabled JDK builds, or how to get support in IDEs. --- README.md | 32 +++---------------- .../ConfigureYourLambdaBuildOfJdk.java | 10 +++--- 2 files changed, 8 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 00397b6..f0ec9af 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ ## Lambda Tutorial [![Build Status](https://api.travis-ci.org/AdoptOpenJDK/lambda-tutorial.png?branch=solutions-prejava8)](https://travis-ci.org/AdoptOpenJDK/lambda-tutorial) -A set of exercises to teach use of Java 8 lambda syntax, and the new Streams API. +A set of exercises to teach use of Java 8 lambda syntax, and the Streams API. To follow the exercises: - fork and clone the repository - - ensure you have a correctly configured, [lambda-enabled Java build](#getting-lambda-jdk) - - Maven can help generate configuration for your favourite IDE, though you will likely have to set the JDK manually + - ensure you have a correctly configured, JDK8+ build + - Maven can help generate configuration for your favourite IDE - ensure your cloned project, particularly the class `ConfigureYourLambdaBuildOfJdk` compiles and runs correctly - navigate to the first exercise, `Exercise_1_Test` (tests are in `src/test/java`, in the package `org.adoptopenjdk.lambda.tutorial`) - read background information in the JavaDoc, and follow instructions, making the test pass @@ -19,28 +19,4 @@ To follow the exercises: 2. Filtering and Collecting 3. Mapping 4. Method References - -[More to come] - - -### Getting Lambda JDK -Early access builds of JDK 8 are available [here](https://jdk8.java.net/lambda/). - - -#### Lamba JDK Build Compatibility -The current tutorial is known to work with the following JDK build: - -|JDK Build Number|Released On | -|:---------------|:---------- | -|ea b109 |Sep 26, 2013| - -lambda-tutorial will try to track against the newest version available. If you find that you are working with a newer version of the Lambda JDK and the tutorial does not compile or run, please file an issue. - -### IDE Setup -- [IntelliJ IDEA on Ubuntu](https://github.com/AdoptOpenJDK/lambda-tutorial/wiki/IntelliJ-IDEA-on-Ubuntu-%5BLinux%5D) -- [IntelliJ IDEA on MacOS](https://github.com/AdoptOpenJDK/lambda-tutorial/wiki/IntelliJ-IDEA-on-MacOS) -- [IntelliJ IDEA deutsche Anleitung (u.a. Windows)](https://github.com/AdoptOpenJDK/lambda-tutorial/wiki/IntelliJ-IDEA-Einrichtung) -- [Eclipse Kepler 4.3 on Windows](https://github.com/AdoptOpenJDK/lambda-tutorial/wiki/Eclipse-Lambda-EA-Setup) - -Note: we are hoping the instructions are not too sensitive to the OSes on which they have been performed. - + 5. Default methods on interfaces diff --git a/src/main/java/org/adoptopenjdk/lambda/setupcheck/ConfigureYourLambdaBuildOfJdk.java b/src/main/java/org/adoptopenjdk/lambda/setupcheck/ConfigureYourLambdaBuildOfJdk.java index 2385664..ea3b5cb 100644 --- a/src/main/java/org/adoptopenjdk/lambda/setupcheck/ConfigureYourLambdaBuildOfJdk.java +++ b/src/main/java/org/adoptopenjdk/lambda/setupcheck/ConfigureYourLambdaBuildOfJdk.java @@ -39,15 +39,13 @@ public class ConfigureYourLambdaBuildOfJdk { public static void main(String... args) { List messages = asList( "If this source file does not compile, you have not configured your development setup correctly.", - "It uses both a new JDK 8 syntax (method references with '::') and a new JDK 8 library method (Iterable#forEach)", + "It uses both a JDK 8+ syntax (method references with '::') and a JDK 8+ library method (Iterable#forEach)", "You should also be able to execute this main method, and see this message printed to the console.", "", "To configure your development environment, you need:", - " - a lambda build of JDK 8, available at: http://jdk8.java.net/lambda/", - " - a lambda-aware IDE.", - " IntelliJ and NetBeans support lambdas in early access versions, available at: http://openjdk.java.net/projects/lambda/ \n" + - " Eclipse support is more sketchy, the method described here just about works: http://tuhrig.de/?p=921", - " Maven will compile your code and run your tests, just, add JDK 8 javac and java executables to your system path and use 'mvn test'", + " - an install of JDK 8 or higher", + " - an IDE that supports JDK8+. All mainstream IDEs (Eclipse/IntelliJ IDEA/NetBeans) support JDK8+ ", + " - and/or Maven, to compile your code and run your tests, using 'mvn test'", "", "Until this source file compiles, you will be unable to make progress in the tutorial."); From d24ad067f7a211c44c8adfb4544420501f3c7c20 Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Thu, 9 Apr 2020 13:29:40 +0100 Subject: [PATCH 58/61] Remove test cases from prejava8 branch. --- .../lambda/tutorial/Exercise_5_Test.java | 166 ++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_5_Test.java diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_5_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_5_Test.java new file mode 100644 index 0000000..bedf2c3 --- /dev/null +++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_5_Test.java @@ -0,0 +1,166 @@ +package org.adoptopenjdk.lambda.tutorial; + +/* + * #%L + * lambda-tutorial + * %% + * Copyright (C) 2013 Adopt OpenJDK + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + + +import java.util.function.Consumer; + +/** + * Exercise 5 - Default Methods + *

+ * The introduction of Default Methods is intended to allow for libraries to evolve more easily - with the + * JDK Collections library as their first main user. They permit adding concrete methods, with an implementation, + * to Java interfaces. Prior to JDK 8, every method on an interface had to be abstract, with implementations provided + * by classes. With JDK 8, it's possible to declare the following interface: + *

+ * + *

+ * // (stripped down version of the real Iterable.java)
+ * public interface Iterable {
+ *
+ *     Iterator iterator(); // As in prior versions
+ *
+ *     // example of a new JDK 8 default method
+ *     default void forEach(Consumer action) {
+ *         Objects.requireNonNull(action);
+ *         for (T t : this) {
+ *             action.accept(t);
+ *         }
+ *     }
+ * }
+ * 
+ * + * In this case the method forEach is a default method with a concrete implementation. With this + * declaration forEach() can be invoked on any implementation of Iterable. This is very + * similar to adding a concrete method to an abstract class -- if no implementation overrides it, the code in the method + * on the interface is executed. Crucially, the new method can be added without causing compiler errors in the client + * code. Consider if a normal forEach method was added to Iterable: every class that + * implemented the Iterable interface would now fail to compile. Using default methods, the interface can + * be evolved without breaking client code. + *

+ * Allowing evolution of a library is the primary use case of default methods. + *

+ *

+ *

Rules of Method Lookup

+ *
+ * Inheritance of method implementations is not new to Java. When a subclass method is invoked, the runtime finds the + * nearest implementation available. This could be declared on the subclass itself, or on any of it's superclasses, or + * even all the way up in java.lang.Object. This last case is what occurs if your class does not override + * toString. However, if anywhere in the inheritance hierarchy from your class to Object, toString is + * implemented, that would be executed instead of Object's toString. This behaviour is still in + * place in JDK 8, but has been augmented to cope with default methods. + *

+ *

+ * The main difference between superclass methods and default methods in JDK 8 is that there is only single + * inheritance of classes, while there is multiple inheritance of interfaces. As such, there needs to be + * some extra rules around method lookup. + *
+ * Method lookup has all the following characteristics: + *
+ *

For a default method to be invoked, there must be no declaration anywhere in the class hierarchy.

+ *
+ * This can + * be thought of like Miranda rights, as in, "You have + * the right to a method implementation. If your class hierarchy cannot afford an implementation, one will be + * provided for you". A method implementation anywhere in the class hierarchy will take priority over a default + * method. This also applies to default methods which match the signature from java.lang.Object; the default method + * can never be invoked. As such, declaring a default method which matches a method from Object is a compiler error. + *
+ *

The closest default method wins.

+ *
+ * As with superclasses, when searching for a concrete method the nearest + * superclass wins. For example, given "class A", "class B extends A", and "class C + * extends B", if a method is invoked on C, the Java runtime will first look for the implementation in C, + * then B, then A, then finally java.lang.Object, invoking the first method it finds. This is also the case with + * super-interfaces. Indeed, given "interface X", "interface Y extends X" and " + * interface Z extends Y", and declaring "class C extends B, implements Z", the lookup for a + * default method would traverse the hierarchy in the following order: C -> B -> A -> Object -> Z -> Y -> X. + *
+ *

Ambiguous inheritance of a default method must be resolved in the implementing class, at compile time.

+ *
+ * If a class inherits the same default method from more than one source, a compiler error will be emitted. This happens + * when unrelated types declare the same method signature, and a class becomes a subtype of more than one of them. + * Consider the following example: + *
+ * interface A {
+ *     default void speak() { System.out.println("A says hi!"; }
+ * }
+ * interface B {
+ *     default void speak() { System.out.println("Regards from B!"; }
+ * }
+ * class C implements A, B { } // compiler error: inherits unrelated default methods from A and B
+ * 
+ * This principle applies regardless of how deep the interface inheritance hierarchy is, and also applies to + * sub-interfaces as well as classes, i.e. "interface Y extends A, B" results in the same compile error. + * This error can be resolved by removing ambiguity in the subtype, by overriding the default method. This can be any + * compliant implementation, including directly invoking a specific inherited default method. A new syntax in JDK 8 is + * available to allow choosing an inherited default method, like so: + *
+ * class C implements A, B {
+ *     public void speak() { A.super.speak(); } // prints "A says hi!"
+ * }
+ * 
+ * In this case, when instanceOfC.speak(); is executed, the default speak from + * interface A is invoked. This syntax is also available within default method bodies, allowing an interface + * to choose an implementation from one of its super-interfaces. Note that this syntax is not entirely unfamiliar: it + * can be considered just like invoking super.someMethod();, except that with the single inheritance of + * classes, the name of the super class is can be nothing other than the single superclass, so it can remain implicit. + *
+ * It should be noted that ambiguities are never resolved by the order in which interface implementations are + * declared (such as with Scala's traits). They must always be resolved explicitly in the subtype. + *

+ *

Do default methods mean Java supports multiple inheritance?

+ *

+ * In a way. Java has always supported multiple inheritance of interfaces, previously this did not include any of the + * implementation, just the contract. Inheritance can be subdivided again, into inheritance of state, and + * inheritance of behaviour. Default methods introduce the latter, multiple inheritance of behaviour. + *

What about the diamond problem?

+ *

There are two aspects to the diamond problem: a) disambiguating implementations and b) handling state. We have seen + * how ambiguities are resolved in JDK 8, requiring that users specify the implementation at compile time. Default + * methods do not introduce a problem in that aspect of the diamond problem. The second problem, state, is where trickier + * issues of the diamond problem live. It can be too easy to accidentally introduce bugs into an implementing class + * because it must adhere to the contract defined in the interface and/or superclass, which can include maintaining + * state. Any methods which manipulate that state must also be invoked in the implementing class, and accidentally + * "losing" that invocation is an easy way to introduce a subtle bug. Also, there is the problem between the ordering of + * conflicting methods, that cannot be resolved by disambiguating. Default methods in Java avoid this issue, due to an + * existing virtue of interfaces, that they do not contain state. Because an interface cannot be constructed + * with fields, there is no state available to encounter this issue. Java still has single inheritance of state, + * through superclasses. If the contract of an interface requires state (like, e.g. Iterator) it will be + * provided through the single inheritance chain. + *

+ *

+ * Note that there are still ways to "simulate" state in interfaces with default methods, since there is no restriction + * on accessing static fields defined in other classes. However, that should be avoided for all the reasons that both + * multiple inheritance of state, and global mutable state should be avoided. + *

+ * + * @see Iterable#forEach(Consumer) + */ +@SuppressWarnings("unchecked") +public class Exercise_5_Test { + + /* + Exercise on default methods in interfaces does not apply pre-java8. + */ + +} From fb483da15d5b3ea4034ecc3a27e8e9744900d950 Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Sat, 11 Apr 2020 11:59:36 +0100 Subject: [PATCH 59/61] Update TravisCI config --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6c23cde..a94fd57 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ +dist: precise language: java jdk: - - oraclejdk7 - openjdk7 script: "mvn clean test" From 781c90f93043196f7db31073e514b49df133e39f Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Sat, 11 Apr 2020 12:04:42 +0100 Subject: [PATCH 60/61] Try to get running on Travis. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a94fd57..d0dd95b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ dist: precise language: java jdk: - - openjdk7 + - oraclejdk7 script: "mvn clean test" From bd653213d92251088dd20748ce6b67b367faeeed Mon Sep 17 00:00:00 2001 From: Graham Allan Date: Sat, 11 Apr 2020 12:07:23 +0100 Subject: [PATCH 61/61] Still trying to get travis working. --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index d0dd95b..f331bf5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ -dist: precise language: java jdk: - - oraclejdk7 + - openjdk8 script: "mvn clean test"