From 9038a9a5280a4f098ba60507996b2aeb2d1e6ed1 Mon Sep 17 00:00:00 2001
From: Martin Goik <goik@hdm-stuttgart.de>
Date: Sat, 28 Nov 2015 23:51:30 +0100
Subject: [PATCH] Extended functional programming

---
 .../mi/javastreams/CsvCollector.java          | 72 ++++++++++++++++
 .../hdm_stuttgart/mi/javastreams/Student.java | 31 +++++--
 .../mi/javastreams/Java8FunctionalTest.java   | 80 +++++++++++++++---
 .../mi/javastreams/RosterTest.java            | 83 ++++++++++++++-----
 .../hdm_stuttgart/mi/javastreams/Student.java | 36 ++++++--
 .../mi/javastreams/Java8FunctionalTest.java   | 43 ++++++++--
 6 files changed, 290 insertions(+), 55 deletions(-)
 create mode 100644 P/Sda1/Streams/Solution/src/main/java/de/hdm_stuttgart/mi/javastreams/CsvCollector.java

diff --git a/P/Sda1/Streams/Solution/src/main/java/de/hdm_stuttgart/mi/javastreams/CsvCollector.java b/P/Sda1/Streams/Solution/src/main/java/de/hdm_stuttgart/mi/javastreams/CsvCollector.java
new file mode 100644
index 000000000..252404812
--- /dev/null
+++ b/P/Sda1/Streams/Solution/src/main/java/de/hdm_stuttgart/mi/javastreams/CsvCollector.java
@@ -0,0 +1,72 @@
+package de.hdm_stuttgart.mi.javastreams;
+
+import java.util.EnumSet;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.BinaryOperator;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collector;
+
+/**
+ * Turning a list of strings into a character separated list of words. Example:
+ * 
+ * List {"Fred", "Eve", "Petra" } and separator "+" results
+ * in the string "Fred+Eve+Petra".
+ *
+ */
+public class CsvCollector implements Collector<String, StringBuffer, String> {
+   
+   final String separator;
+   
+   /**
+    * Providing the list's desired separator string.
+    * @param separator String filling the gap between two adjacent strings.
+    */
+   public CsvCollector(final String separator) {
+      this.separator = separator;
+   }
+   
+   @Override
+   public Supplier<StringBuffer> supplier() {
+      return () -> new StringBuffer();
+   }
+
+   @Override
+   public BiConsumer<StringBuffer, String> accumulator() {
+      return (csv, s) -> {
+         if (0 != csv.length()) {   // Buffer non-empty?
+            csv.append(separator);  // ==> Append separator ...
+         }
+         csv.append(s);             // and string.
+      };
+   }
+
+   @Override
+   public BinaryOperator<StringBuffer> combiner() {
+      return (csv1, csv2) -> {
+         if (0 == csv1.length()) {        // Left list empty?
+            return csv2;                  // ==> return right list.
+            
+         } else if (0 == csv2.length()) { // Right List empty?
+            return csv1;                  // ==> return left list.
+            
+         } else {                         // Both lists non-empty?
+            return csv1.                  // ==> Concatenate both lists.
+                  append(separator).
+                  append(csv2);  
+         }
+      };
+   }
+
+   @Override
+   public Function<StringBuffer, String> finisher() {
+      return csv -> csv.toString();
+   }
+
+   @Override
+   public Set<java.util.stream.Collector.Characteristics> characteristics() {
+      return EnumSet.of(Characteristics.UNORDERED);
+   }
+
+}
diff --git a/P/Sda1/Streams/Solution/src/main/java/de/hdm_stuttgart/mi/javastreams/Student.java b/P/Sda1/Streams/Solution/src/main/java/de/hdm_stuttgart/mi/javastreams/Student.java
index 8e3a64d9f..0b506d7c8 100644
--- a/P/Sda1/Streams/Solution/src/main/java/de/hdm_stuttgart/mi/javastreams/Student.java
+++ b/P/Sda1/Streams/Solution/src/main/java/de/hdm_stuttgart/mi/javastreams/Student.java
@@ -34,8 +34,10 @@ package de.hdm_stuttgart.mi.javastreams;
 import java.util.Arrays;
 import java.util.List;
 
+/** Representing students among with examination marks. */
 public class Student {
 
+   /** @return Test sample of students. */
    public static List<Student> createRoster() { // Create test data records
 
       final Student[] roster = new Student[] {
@@ -49,12 +51,18 @@ public class Student {
       return Arrays.asList(roster);
    }
 
+   /** Male or female */
    public enum Sex {
-      MALE("male"), FEMALE("female");
+      /** */
+      MALE("male"), 
+      /** */
+      FEMALE("female");
 
       final String extern;
 
       private Sex(final String extern) {this.extern = extern;}
+
+      @Override
       public String toString(){return extern;}
    }
 
@@ -71,22 +79,34 @@ public class Student {
       this.emailAddress = email;
    }  
 
+   /** * @return An examination's result */
    public int getMark() {
       return mark;
    }
-   public Sex getGender() {
+
+   /** @return A student's sex. */
+   public Sex getSex() {
       return gender;
    }
 
+   /** @return A student's name. */
    public String getName() {
       return name;
    }
 
+   /** @return A student's e-mail. */
    public String getEmailAddress() {
       return emailAddress;
    }
 
-   public static int compareByMark(Student a, Student b) {
+   /**
+    * Compare two students by their marks
+    * @param a first student
+    * @param b second student
+    * @return positive if student b's mark is better than student a's,
+    *         zero if marks are equal, negative if ...
+    */
+   public static int compareByMark(final Student a, final Student b) {
       return a.mark - b.mark;
    }
 
@@ -94,9 +114,4 @@ public class Student {
    public String toString() {
       return getName() + "(" + gender + ", " + getEmailAddress() + ", mark=" + getMark() + ")";
    }
-
-   public void print() {
-      System.out.println(this);
-   }
-
 }
\ No newline at end of file
diff --git a/P/Sda1/Streams/Solution/src/test/java/de/hdm_stuttgart/mi/javastreams/Java8FunctionalTest.java b/P/Sda1/Streams/Solution/src/test/java/de/hdm_stuttgart/mi/javastreams/Java8FunctionalTest.java
index f6b78ef79..39a44d258 100644
--- a/P/Sda1/Streams/Solution/src/test/java/de/hdm_stuttgart/mi/javastreams/Java8FunctionalTest.java
+++ b/P/Sda1/Streams/Solution/src/test/java/de/hdm_stuttgart/mi/javastreams/Java8FunctionalTest.java
@@ -2,9 +2,11 @@ package de.hdm_stuttgart.mi.javastreams;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.OptionalDouble;
+import java.util.stream.Collector;
 import java.util.stream.Collectors;
 
 import org.hamcrest.Matchers;
@@ -40,8 +42,7 @@ public class Java8FunctionalTest {
    public void allMaleDistinctNameOrderedByEmail() {
 
       final List<String> emails =
-            roster.
-            stream().
+            roster.parallelStream().
             filter(s -> s.gender == Sex.MALE).
             sorted((s1, s2) -> s1.getEmailAddress().compareTo(s2.getEmailAddress())).
             map(Student::getName).
@@ -67,13 +68,62 @@ public class Java8FunctionalTest {
    public void markSumAllStudentsMarksHavingEmail_Dot_de() {
       Assert.assertEquals(
             3,                //Expected marks sum 3 == 1 + 2   
-            roster.stream()
+            roster.parallelStream()
             .filter(p -> p.emailAddress.endsWith(".de")) // Jane and Kim
             .mapToInt(Student::getMark)
             .reduce(0, (a, b) -> a + b)
             );
    }
 
+   /**
+    * A comma separated string containing all students' alphabetically ordered
+    * email addresses:
+    * 
+    * "bob@uk.edu, fred@example.com, george@math.edu, ..."
+    *  
+    */
+   @Test
+   public void orderedCharacterSeparatedEmailList() {
+
+      Assert.assertEquals(
+            "bob@uk.edu, fred@example.com, george@math.edu, jane@kiv.de, wilde@serious.de", 
+
+            roster.parallelStream().
+            map(Student::getEmailAddress).
+            sorted().
+            collect(new CsvCollector(", ")) // Using ", " as separator string.
+            );
+
+   }
+
+   /**
+    * Achieves the same result as in {@link #orderedCharacterSeparatedEmailList()}
+    * internally using a standard collector with a modified
+    * {@link Collector#finisher()} method:
+    * 
+    * "bob@uk.edu, fred@example.com, george@math.edu, ..."
+    *  
+    */
+   @Test
+   public void orderedCharacterSeparatedEmailList2() {
+
+      Assert.assertEquals(
+            "bob@uk.edu, fred@example.com, george@math.edu, jane@kiv.de, wilde@serious.de", 
+
+            roster.parallelStream().
+            map(Student::getEmailAddress).
+            sorted().
+            collect
+            (Collectors.collectingAndThen
+                  (Collectors.toList(),
+                        l -> l.stream().   // A stream within a stream.
+                        reduce((s1, s2) -> s1 + ", " + s2).
+                        get()
+                        )
+                  )
+            );
+   }
+
    /**
     * Get the average mark of all female students as double value.
     * 
@@ -83,7 +133,8 @@ public class Java8FunctionalTest {
    @Test
    public void averageMarkFemaleStudents() {
 
-      final OptionalDouble femaleAverage = roster.stream().
+      final OptionalDouble femaleAverage =
+            roster.parallelStream().
             filter(s -> s.gender == Sex.FEMALE).
             mapToInt(Student::getMark).
             average();
@@ -111,10 +162,10 @@ public class Java8FunctionalTest {
 
       final Map<Student.Sex, List<String>> studentnamesBySex =
             roster
-            .stream()
+            .parallelStream()
             .collect(
                   Collectors.groupingBy(
-                        Student::getGender,
+                        Student::getSex,
                         Collectors.mapping(
                               Student::getName,
                               Collectors.toList())
@@ -144,7 +195,7 @@ public class Java8FunctionalTest {
    public void markingFrequencies() {
       final Map<Integer, Integer> frequencyByMark =
             roster
-            .stream()
+            .parallelStream()
             .collect(
                   Collectors.groupingBy(
                         Student::getMark,
@@ -175,17 +226,24 @@ public class Java8FunctionalTest {
     *
     */
    @Test
-   public void studentsByMark() {
+   public void studentnamessByMark() {
+
       final Map<Integer, List<String>> namesByMark =
             roster
-            .stream()
-            .sorted((s1,s2) -> s1.name.compareTo(s2.name))
+            .parallelStream()
             .collect(
                   Collectors.groupingBy(
                         Student::getMark,
                         Collectors.mapping(
                               Student::getName,
-                              Collectors.toList())
+                              Collectors.collectingAndThen(
+                                    Collectors.toList(),
+                                    l -> {
+                                       Collections.sort(l);
+                                       return l;
+                                    }
+                                    )
+                              )
                         )
                   );
 
diff --git a/P/Sda1/Streams/Template/src/main/java/de/hdm_stuttgart/mi/javastreams/RosterTest.java b/P/Sda1/Streams/Template/src/main/java/de/hdm_stuttgart/mi/javastreams/RosterTest.java
index f1b9293d9..4bee36942 100644
--- a/P/Sda1/Streams/Template/src/main/java/de/hdm_stuttgart/mi/javastreams/RosterTest.java
+++ b/P/Sda1/Streams/Template/src/main/java/de/hdm_stuttgart/mi/javastreams/RosterTest.java
@@ -14,7 +14,7 @@ import java.util.function.Predicate;
  */
 public class RosterTest {
    
-   static final List<Student> roster = Student.createRoster();
+   static final List<Student> rosterInstance = Student.createRoster();
    
    interface CheckStudent {
        boolean test(Student p);
@@ -23,16 +23,25 @@ public class RosterTest {
    // Approach 1: Create Methods that Search for Persons that Match One
    // Characteristic
 
+   /**
+    * @param roster
+    * @param mark
+    */
    public static void printPersonsOlderThan(List<Student> roster, int mark) {
        for (Student p : roster) {
            if (p.getMark()>= mark) {
-               p.print();;
+               p.print();
            }
        }
    }
 
    // Approach 2: Create More Generalized Search Methods
 
+   /**
+    * @param roster
+    * @param low
+    * @param high
+    */
    public static void printPersonsWithinMarkRange(
        List<Student> roster, int low, int high) {
        for (Student p : roster) {
@@ -46,6 +55,10 @@ public class RosterTest {
    // Approach 4: Specify Search Criteria Code in an Anonymous Class
    // Approach 5: Specify Search Criteria Code with a Lambda Expression
 
+   /**
+    * @param roster
+    * @param tester
+    */
    public static void printPersons(
        List<Student> roster, CheckStudent tester) {
        for (Student p : roster) {
@@ -57,6 +70,10 @@ public class RosterTest {
 
    // Approach 6: Use Standard Functional Interfaces with Lambda Expressions
 
+   /**
+    * @param roster
+    * @param tester
+    */
    public static void printPersonsWithPredicate(
        List<Student> roster, Predicate<Student> tester) {
        for (Student p : roster) {
@@ -68,6 +85,11 @@ public class RosterTest {
 
    // Approach 7: Use Lambda Expressions Throughout Your Application
 
+   /**
+    * @param roster
+    * @param tester
+    * @param block
+    */
    public static void processPersons(
        List<Student> roster,
        Predicate<Student> tester,
@@ -81,6 +103,12 @@ public class RosterTest {
 
    // Approach 7, second example
 
+   /**
+    * @param roster
+    * @param tester
+    * @param mapper
+    * @param block
+    */
    public static void processPersonsWithFunction(
        List<Student> roster,
        Predicate<Student> tester,
@@ -96,6 +124,12 @@ public class RosterTest {
     
    // Approach 8: Use Generics More Extensively
 
+   /**
+    * @param source
+    * @param tester
+    * @param mapper
+    * @param block
+    */
    public static <X, Y> void processElements(
        Iterable<X> source,
        Predicate<X> tester,
@@ -109,9 +143,12 @@ public class RosterTest {
            }
    }
 
+   /**
+    * @param args unused
+    */
    public static void main(String... args) {
 
-       for (Student p : roster) {
+       for (Student p : rosterInstance) {
            p.print();
        }
 
@@ -119,13 +156,13 @@ public class RosterTest {
        // Characteristic
 
        System.out.println("Persons older than 20:");
-       printPersonsOlderThan(roster, 20);
+       printPersonsOlderThan(rosterInstance, 20);
        System.out.println();
 
        // Approach 2: Create More Generalized Search Methods
 
        System.out.println("Persons between the ages of 14 and 30:");
-       printPersonsWithinMarkRange(roster, 14, 30);
+       printPersonsWithinMarkRange(rosterInstance, 14, 30);
        System.out.println();
 
        // Approach 3: Specify Search Criteria Code in a Local Class
@@ -133,15 +170,16 @@ public class RosterTest {
        System.out.println("Persons who are eligible for Selective Service:");
 
        class CheckStudentEligibleForSelectiveService implements CheckStudent {
-          public boolean test(Student p) {
-               return p.getGender() == Student.Sex.MALE
+          @Override
+         public boolean test(Student p) {
+               return p.getSex() == Student.Sex.MALE
                    && p.getMark() >= 2
                    && p.getMark() <= 3;
            }
        }
 
        printPersons(
-           roster, new CheckStudentEligibleForSelectiveService());
+           rosterInstance, new CheckStudentEligibleForSelectiveService());
 
 
        System.out.println();
@@ -152,10 +190,11 @@ public class RosterTest {
            "(anonymous class):");
 
        printPersons(
-           roster,
+           rosterInstance,
            new CheckStudent() {
+               @Override
                public boolean test(Student p) {
-                   return p.getGender() == Student.Sex.MALE
+                   return p.getSex() == Student.Sex.MALE
                        && p.getMark() >= 2
                        && p.getMark() <= 3;
                }
@@ -170,8 +209,8 @@ public class RosterTest {
            "(lambda expression):");
 
        printPersons(
-           roster,
-           (Student p) -> p.getGender() == Student.Sex.MALE
+           rosterInstance,
+           (Student p) -> p.getSex() == Student.Sex.MALE
                && p.getMark() >= 2
                && p.getMark() <= 3
        );
@@ -185,8 +224,8 @@ public class RosterTest {
            "(with Predicate parameter):");
 
        printPersonsWithPredicate(
-           roster,
-           p -> p.getGender() == Student.Sex.MALE
+           rosterInstance,
+           p -> p.getSex() == Student.Sex.MALE
                && p.getMark() >= 18
                && p.getMark() <= 25
        );
@@ -199,8 +238,8 @@ public class RosterTest {
            "(with Predicate and Consumer parameters):");
 
        processPersons(
-           roster,
-           p -> p.getGender() == Student.Sex.MALE
+           rosterInstance,
+           p -> p.getSex() == Student.Sex.MALE
                && p.getMark() >= 2
                && p.getMark() <= 3,
            p -> p.print()
@@ -214,8 +253,8 @@ public class RosterTest {
            "(with Predicate, Function, and Consumer parameters):");
 
        processPersonsWithFunction(
-           roster,
-           p -> p.getGender() == Student.Sex.MALE
+           rosterInstance,
+           p -> p.getSex() == Student.Sex.MALE
                && p.getMark() >= 2
                && p.getMark() <= 3,
            p -> p.getEmailAddress(),
@@ -230,8 +269,8 @@ public class RosterTest {
            "(generic version):");
 
        processElements(
-           roster,
-           p -> p.getGender() == Student.Sex.MALE
+           rosterInstance,
+           p -> p.getSex() == Student.Sex.MALE
                && p.getMark() >= 2
                && p.getMark() <= 3,
            p -> p.getEmailAddress(),
@@ -246,10 +285,10 @@ public class RosterTest {
        System.out.println("Persons who are eligible for Selective Service " +
            "(with bulk data operations):");
 
-       roster
+       rosterInstance
            .stream()
            .filter(
-               p -> p.getGender() == Student.Sex.MALE
+               p -> p.getSex() == Student.Sex.MALE
                    && p.getMark() >= 2
                    && p.getMark() <= 3)
            .map(p -> p.getEmailAddress())
diff --git a/P/Sda1/Streams/Template/src/main/java/de/hdm_stuttgart/mi/javastreams/Student.java b/P/Sda1/Streams/Template/src/main/java/de/hdm_stuttgart/mi/javastreams/Student.java
index 8e3a64d9f..80c48c353 100644
--- a/P/Sda1/Streams/Template/src/main/java/de/hdm_stuttgart/mi/javastreams/Student.java
+++ b/P/Sda1/Streams/Template/src/main/java/de/hdm_stuttgart/mi/javastreams/Student.java
@@ -34,8 +34,10 @@ package de.hdm_stuttgart.mi.javastreams;
 import java.util.Arrays;
 import java.util.List;
 
+/** Representing students among with examination marks. */
 public class Student {
 
+   /** @return Test sample of students. */
    public static List<Student> createRoster() { // Create test data records
 
       final Student[] roster = new Student[] {
@@ -49,52 +51,72 @@ public class Student {
       return Arrays.asList(roster);
    }
 
+   /** Male or female */
    public enum Sex {
-      MALE("male"), FEMALE("female");
+      /** */
+      MALE("male"), 
+      /** */
+      FEMALE("female");
 
       final String extern;
 
       private Sex(final String extern) {this.extern = extern;}
+      @Override
       public String toString(){return extern;}
    }
 
    String name; 
    int mark;
-   Sex gender;
+   Sex sex;
    String emailAddress;
 
    Student(String name, int mark ,
-         Sex gender, String email) {
+         Sex sex, String email) {
       this.name = name;
       this.mark = mark;
-      this.gender = gender;
+      this.sex = sex;
       this.emailAddress = email;
    }  
 
+   /** * @return An examination's result */
    public int getMark() {
       return mark;
    }
-   public Sex getGender() {
-      return gender;
+
+   /** @return A student's sex. */
+   public Sex getSex() {
+      return sex;
    }
 
+   /** @return A student's name. */
    public String getName() {
       return name;
    }
 
+   /** @return A student's e-mail. */
    public String getEmailAddress() {
       return emailAddress;
    }
 
+   /**
+    * Compare two students by their marks
+    * @param a first student
+    * @param b second student
+    * @return positive if student b's mark is better than student a's,
+    *         zero if marks are equal, negative if ...
+    */
    public static int compareByMark(Student a, Student b) {
       return a.mark - b.mark;
    }
 
    @Override
    public String toString() {
-      return getName() + "(" + gender + ", " + getEmailAddress() + ", mark=" + getMark() + ")";
+      return getName() + "(" + sex + ", " + getEmailAddress() + ", mark=" + getMark() + ")";
    }
 
+   /**
+    * Printing all data to standard output.
+    */
    public void print() {
       System.out.println(this);
    }
diff --git a/P/Sda1/Streams/Template/src/test/java/de/hdm_stuttgart/mi/javastreams/Java8FunctionalTest.java b/P/Sda1/Streams/Template/src/test/java/de/hdm_stuttgart/mi/javastreams/Java8FunctionalTest.java
index f82115081..8edfdd587 100644
--- a/P/Sda1/Streams/Template/src/test/java/de/hdm_stuttgart/mi/javastreams/Java8FunctionalTest.java
+++ b/P/Sda1/Streams/Template/src/test/java/de/hdm_stuttgart/mi/javastreams/Java8FunctionalTest.java
@@ -4,6 +4,8 @@ import static org.hamcrest.MatcherAssert.assertThat;
 
 import java.util.List;
 import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collector;
 import java.util.stream.Collectors;
 
 import org.hamcrest.Matchers;
@@ -33,7 +35,6 @@ public class Java8FunctionalTest {
     *  "Kim", 2, Student.Sex.FEMALE, "wilde@serious.de"
     *  
     *  ==> {"Bob", "Fred", "George"}
-    *  
     */
    @Test
    public void allMaleDistinctNameOrderedByEmail() {
@@ -41,9 +42,9 @@ public class Java8FunctionalTest {
       final List<String> emails =
             roster.
             stream().
-            filter(s -> s.gender == Sex.MALE).
-            sorted((s1, s2) -> s1.getEmailAddress().compareTo(s2.getEmailAddress())).
+            filter(s -> s.sex == Sex.MALE).
             map(Student::getName).
+            sorted().
             distinct().
             collect(Collectors.toList());
 
@@ -66,20 +67,50 @@ public class Java8FunctionalTest {
     * Implementation hint: Map objects to int and provide a suitable reduce operation.
     * You may want to read
     * https://docs.oracle.com/javase/tutorial/collections/streams/reduction.html#reduce
-    * 
     */
    @Test
    public void markSumAllStudentsMarksHavingEmail_Dot_de() {
       Assert.fail("Implement me!");// TODO
    }
 
+   /**
+    * A comma separated string containing all students' alphabetically ordered
+    * email addresses:
+    * 
+    * "bob@uk.edu, fred@example.com, george@math.edu, ..."
+    * 
+    *  Hint: Read 
+    *    http://www.nurkiewicz.com/2014/07/introduction-to-writing-custom.html
+    *    and learn how to write custom collectors. Then implement a string collector
+    *    which uses a {@link StringBuffer} collecting the string elements. Its
+    *    finisher() method may then return the desired result string.
+    */
+   @Test
+   public void orderedCharacterSeparatedEmailList() {
+      Assert.fail("Implement me!");// TODO
+   }
+   
+   /**
+    * Achieves the same result as in {@link #orderedCharacterSeparatedEmailList()}
+    * internally using a standard collector with a modified
+    * {@link Collector#finisher()} method:
+    * 
+    * "bob@uk.edu, fred@example.com, george@math.edu, ..."
+    * 
+    * Hint: Consider {@link Collectors#collectingAndThen(Collector, Function)} and
+    * read http://www.adam-bien.com/roller/abien/entry/java_8_reducing_a_list.
+    */
+   @Test
+   public void orderedCharacterSeparatedEmailList2() {
+      Assert.fail("Implement me!");// TODO
+   }
+
    /**
     * Get the average mark of all female students as double value.
     * 
     * Implementation hint: Map objects to int and provide a suitable reduce operation.
     * You may want to read
     * https://docs.oracle.com/javase/8/docs/api/java/util/stream/IntStream.html#average--
-    * 
     */
    @Test
    public void averageMarkFemaleStudents() {
@@ -101,7 +132,6 @@ public class Java8FunctionalTest {
     * 
     * Implementation hint:
     * stackoverflow.com/questions/2509293/map-equality-using-hamcrest#answer-29680356
-    * 
     */
    @Test
    public void studentNamesBySex() {
@@ -141,7 +171,6 @@ public class Java8FunctionalTest {
     * Implementation hint: Sort the stream by student's names beforehand. Then
     * apply a suitable 
     * {@link Collectors#mapping(java.util.function.Function, java.util.stream.Collector)}
-    * 
     */
    @Test
    public void studentsByMark() {
-- 
GitLab