Stream - HolmesJJ/OOP-FP GitHub Wiki

Definition

Exectue very complex operations such as finding, filtering, and mapping data on the collection. In short, the Stream API provides an efficient and easy-to-use way to process data.

Features:

  • It is not a data structure and will not store data.
  • The original data source will not be modified, it will save the data to another object after the operation.
  • Lazy evaluation. In the intermediate processing process, the stream only records the operation, and will not execute it immediately. The actual evaluation will not be exectued until the terminal operation is executed.

Detail Introdction

Classification

Operations Categories Methods
Intermediate operations Stateless unordered(), filter(), map(), mapToInt(), mapToLong(), mapToDouble(), flatMap(), flatMapToInt(), flatMapToLong(), flatMapToDouble(), peek()
Intermediate operations Stateful distinct(), sorted(), sorted(), limit(), skip()
Terminal operations Not short-circuiting forEach(), forEachOrdered(), toArray(), reduce(), collect(), max(), min(), count()
Terminal operations Short-circuiting anyMatch(), allMatch(), noneMatch(), findFirst(), findAny()
  • Stateless: The processing of the current element is not affected by the previous element
  • Stateful: The operation can only continue after all elements are obtained
  • Not short-circuiting: All elements must be processed to get the final result
  • Short-circuiting: The final result can be obtained after the certain elements meet the conditions

Create Stream

1. Create an empty Stream object

Stream stream = Stream.empty();

2. Create Stream by of()

  • Usage: Easy to test the code
Stream<String> stream = Stream.of("A", "B", "C", "D");
/** 
 * forEach() method is equivalent to inner loop
 * Can pass in a method reference that conforms to the 
 * void accept(T t) of the Consumer interface:
 */
stream.forEach(System.out::println);

/* Output */
A
B
C
D

3. Create Stream based on array or collection

  • For array, Stream can be obtained by directly calling the Arrays.stream() method.
  • For Collection (List, Set, Queue, etc.), Stream can be obtained by directly calling the stream() method.
// Get the serial Stream object
Stream<String> arrayStream = Arrays.stream(new String[] { "A", "B", "C", "D" });
// Get the serial Stream object
Stream<String> listStream = Arrays.asList("a", "b", "c", "d").stream();
// Get the parallel Stream object
Stream<String> parallelListStream = Arrays.asList("w", "x", "y", "z").parallelStream();

arrayStream.forEach(System.out::println);
listStream.forEach(System.out::println);
parallelListStream.forEach(System.out::println);

/* Output */
A
B
C
D

a
b
c
d

y
z
x
w

Creating a Stream by method 2 and 3 are all by converting an existing sequence to a Stream, so its elements are fixed.

4. Create Stream by generate() based on Supplier()

  • The Stream created based on Supplier will continuously call the Supplier.get() method to generate the element. This kind of Stream does not save any elements, but algorithms, which can be used to represent infinite sequences. Therefore, Stream almost not takes up space, each element is calculated in real time when it is used.
  • For an infinite sequence, if directly call forEach() or count(), the system will enter an infinite loop since this sequence can never be calculated. Therefore, the correct way is to convert the infinite sequence to a finite sequence first, for example, use the limit() method can intercept the first few elements of the infinite sequence so that it is converted to a finite sequence, then call forEach() or count() on this finite sequence.
/* Randomly generate integers in the interval [0,10) and print */
/**
 * 
 * Stream<Integer> randomStream = Stream.generate(new Supplier<Integer>() {
 *     @Override
 *     public Integer get() {
 *         return new Random().nextInt(10);
 *     }
 * });
 */
// Further simplify
Stream<Integer> randomStream = Stream.generate(() -> new Random().nextInt(10));
// Note: The infinite sequence must be converted to a finite sequence before printing:
randomStream.limit(5).forEach(System.out::println);

/* Output */
1
3
7
8
7

5. Primitive Types

  • Generics in Java does not support primitive types, so cannot write like Stream<int>, compilation errors will occur.
  • In order to convert to int Stream, only Stream<Integer> can be used, but this will cause frequent autoboxing and unboxing operations.
  • In order to improve operating efficiency, Java library provides IntStream, LongStream and DoubleStream, which are similar to Stream.
// Convert int[] array to IntStream:
IntStream is = Arrays.stream(new int[] { 1, 2, 3 });
// Convert double[] array to DoubleStream:
DoubleStream ds = Arrays.stream(new double[] { 1, 2, 3 });
// Convert long[] array to LongStream:
LongStream ls = Arrays.stream(new long[] { 1, 2, 3 });

is.forEach(System.out::println);
ds.forEach(System.out::println);
ls.forEach(System.out::println);

/* Output */
1
2
3
4.0
5.0
6.0
7
8
9

6. Create Stream by iterate()

/**
 * Create a infinite sequence from 1, the first is 1; the second is 1 + 1 which is 2; 
 * the third is 2 + 1, which is 3; ...;
 * 
 * 1st parameter: First element, which is 1
 * 2rd parameter: Iteration condition, wihch is i + 1
 * 
 * Note: This method is generally used together with limit() to get the first few elements.
 */
/**
 * Stream.iterate(1, 
 *         new UnaryOperator<Integer>() {
 *             @Override
 *             public Integer apply(Integer i) {
 *                 return i + 1;
 *             }
 *         }).forEach(System.out::println);
 */
// Further simplify
Stream.iterate(1, n -> n + 1).limit(5).forEach(System.out::println);

/* Output */
1
2
3
4
5
/**
 * Create a finite sequence from 1, the first is 1; the second is 1 + 1 which is 2; 
 * the third is 2 + 1, which is 3; ...; and it stops at 5;
 * 
 * 1st parameter: First element, which is 1
 * 2nd parameter: Termination condition, which is i <= 5
 * 3rd parameter: Iteration condition, wihch is i + 1
 */
/**
 * Stream.iterate(1, 
 *         new Predicate<Integer>() {
 *             @Override
 *             public boolean test(Integer i) {
 *                 return i <= 5;
 *             }
 *         },
 *         new UnaryOperator<Integer>() {
 *             @Override
 *             public Integer apply(Integer i) {
 *                 return i + 1;
 *             }
 *         }).forEach(System.out::println);
 */
// Further simplify
Stream.iterate(1, n -> n <= 5, n -> n + 1).forEach(System.out::println);

/* Output */
1
2
3
4
5

Usage of methods in the Stream

foreach() & findFirst() & findAny() & match()

Traverse and find the matching elements

public static void main(String[] args) {

    List<Integer> list = Arrays.asList(7, 6, 9, 3, 8, 2, 1);

    // Traverse and print all eligible elements
    list.stream().filter(x -> x > 6).forEach(System.out::println);
    
    // Find the first eligible element
    Optional<Integer> findFirst = list.stream().filter(x -> x > 6).findFirst();
    // Find any one eligible element (Suitable for parallel stream)
    Optional<Integer> findAny = list.parallelStream().filter(x -> x > 6).findAny();
    // Whether include elements that meet the specific conditions
    boolean anyMatch = list.stream().anyMatch(x -> x < 6);
    System.out.println("findFirst:" + findFirst.get());
    System.out.println("findAny:" + findAny.get());
    System.out.println("anyMatch:" + anyMatch);
}

/* Output */
7
9
8
findFirst:7
findAny:8
anyMatch:true

map()

  • One-to-one mapping of Stream elements.
  • Convert a Stream to another Stream via mapping.
  • Map an operation (function) to each element of a sequence. For example, when using the function f(x) = x * x to calculate the square of x, this function will be mapped to a sequence 1, 2, 3, 4, 5, and get another sequence 1, 4, 9, 16, 25.
  • The object received by the map() method is the Function interface object, which accepts an input parameter and returns a result.
lower_bounded_wildcard
/* map() source code */
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> stream2 = stream.map(n -> n * n);
stream2.forEach(System.out::println);

/* Output */
1
4
9
16
25

Example: Batch operations on String List

public static void mapExample() {
    List.of("  Apple ", " pear ", " ORANGE", " BaNaNa ")
            .stream()
            .map(String::trim)             // Remove spaces
            .map(String::toLowerCase)      // Convert to lowercase
            .forEach(System.out::println); // Print
}

/* Output */
apple
pear
orange
banana

flatMap()

  • Convert each element in the stream to another stream, and then connect all these streams to one stream.
  • In simple terms, convert several small lists to one big list.
lower_bounded_wildcard
/* flatMap() source code */
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

Example: List of all players in world cup

public static void main(String[] args) {

    List<String> teamIndia = Arrays.asList("Virat", "Dhoni", "Jadeja");
    List<String> teamAustralia = Arrays.asList("Warner", "Watson", "Smith");
    List<String> teamEngland = Arrays.asList("Alex", "Bell", "Broad");
    List<String> teamNewZeland = Arrays.asList("Kane", "Nathan", "Vettori");
    List<String> teamSouthAfrica = Arrays.asList("AB", "Amla", "Faf");
    List<String> teamWestIndies = Arrays.asList("Sammy", "Gayle", "Narine");
    List<String> teamSriLanka = Arrays.asList("Mahela", "Sanga", "Dilshan");
    List<String> teamPakistan = Arrays.asList("Misbah", "Afridi", "Shehzad");

    List<List<String>> playersInWorldCup = new ArrayList<>();
    playersInWorldCup.add(teamIndia);
    playersInWorldCup.add(teamAustralia);
    playersInWorldCup.add(teamEngland);
    playersInWorldCup.add(teamNewZeland);
    playersInWorldCup.add(teamSouthAfrica);
    playersInWorldCup.add(teamWestIndies);
    playersInWorldCup.add(teamSriLanka);
    playersInWorldCup.add(teamPakistan);

    // Before Java 8
    List<String> listOfAllPlayers = new ArrayList<>();
    for(List<String> team : playersInWorldCup){
        listOfAllPlayers.addAll(team);
    }
    
    // After Java 8
    List<String> flatMapList = playersInWorldCup.stream()
            .flatMap(Collection::stream)
            .collect(Collectors.toList());

    System.out.println("Players playing in world cup 2016");
    System.out.println(listOfAllPlayers);
    System.out.println(flatMapList);
}

/* Output */
Players playing in world cup 2016
[Virat, Dhoni, Jadeja, Warner, Watson, Smith, Alex, Bell, Broad, Kane, Nathan, Vettori, 
AB, Amla, Faf, Sammy, Gayle, Narine, Mahela, Sanga, Dilshan, Misbah, Afridi, Shehzad]
[Virat, Dhoni, Jadeja, Warner, Watson, Smith, Alex, Bell, Broad, Kane, Nathan, Vettori, 
AB, Amla, Faf, Sammy, Gayle, Narine, Mahela, Sanga, Dilshan, Misbah, Afridi, Shehzad]

filter()

  • Filter the elements in the Stream and return a filtered Stream. For example, using the function f(x) = x % 2 != 0 to determine whether the element is odd to filter out all the even elements to get another sequence 1, 3, 5.
  • The stream generated after filter() may have fewer elements than the original Stream.
  • The object received by the filter() method is the Predicate interface object to determine whether the element meets the conditions.
lower_bounded_wildcard
/* filter() source code */
Stream<T> filter(Predicate<? super T> predicate);
IntStream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
        .filter(n -> n % 2 != 0)
        .forEach(System.out::println);

/* Output */
1
3
5
7
9

Example: Filter out passing students and print:

public class Person {

    private String name;
    private int score;

    Person(String name, int score) {
        this.name = name;
        this.score = score;
    }

    public String getName() {
        return name;
    }

    public int getScore() {
        return score;
    }
}

public static void filterExample() {
    List<Person> persons = List.of(new Person("Oliver", 88), new Person("George", 52), new Person("Harry", 35),
            new Person("Jack", 78), new Person("Jacob", 99), new Person("Noah", 48));
    /**
     * persons.stream().filter(new Predicate<Person>() {
     *     @Override
     *     public boolean test(Person person) {
     *         return person.getScore() >= 50;
     *     }
     * }).forEach(new Consumer<Person>() {
     *     @Override
     *     public void accept(Person person) {
     *        System.out.println(person.getName() + ": " + person.getScore());
     *    }
     * });
     */
    // Further simplify
    persons.stream().filter(person -> person.getScore() >= 50)
            .forEach(person -> System.out.println(person.getName() + ": " + person.getScore()));
}

/* Output */
Oliver: 88
George: 52
Jack: 78
Jacob: 99

reduce() & Collectors.reducing()

  • Accumulate all elements of a Stream into one result according to the accumulator function. For example, (accumulate, n) -> accumulate + n is cumulative addition, (accumulate, n) -> accumulate * n is cumulative multiplication.
  • The object received by the reduce() method is the BinaryOperator interface object, which defines an apply() method to evaluate the sum of the last accumulation and the current element, and return the result.
  • In fact, the reduce() operation is a sum / product.
  • Note: When evaluate product, the initial value must not be 0.

reduce() with 1 parameters

  • 1st parameter: Accumulator function
/* reduce() source code */
Optional<T> reduce(BinaryOperator<T> accumulator);

lower_bounded_wildcard
// 0 (First element: 1) + 2 + 3 + 4 + 5
int sum = IntStream.rangeClosed(1, 5).reduce((accumulate, n) -> accumulate + n).getAsInt();
// 1 (First element: 1) * 2 * 3 * 4 * 5
int product = IntStream.rangeClosed(1, 5).reduce((accumulate, n) -> accumulate * n).getAsInt();
System.out.println(sum);
System.out.println(product);

/* Output */
15
120

reduce() with 2 parameters

  • 1st parameter: First element, initial value
  • 2nd parameter: Accumulator function
/* reduce() source code */
T reduce(T identity, BinaryOperator<T> accumulator);

lower_bounded_wildcard
// 0 (First element: start with 0) + 1 + 2 + 3 + 4 + 5
int sum = IntStream.rangeClosed(1, 5).reduce(0, (accumulate, n) -> accumulate + n);
// 1 (First element: start with 1) * 1 * 2 * 3 * 4 * 5
int product = IntStream.rangeClosed(1, 5).reduce(1, (accumulate, n) -> accumulate * n);
System.out.println(sum);
System.out.println(product);

/* Output */
18
360

From the above figure and the code, the evaluation process of the sum is (((((3 + 1) + 2) + 3) + 4) + 5) = 18

reduce() with 3 parameters

/* reduce() source code */
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);
  • reduce() with 3 parameters is similar to reduce() with 2 parameters
  • The third parameter is used to combine the calculation results of each thread under parallel evaluation
  • According to the Fork/Join Framework, when processing asynchronous multithreading, multiple threads will execute tasks at the same time, which will inevitably produce multiple results. Then need to merger them correctly with the third parameter
IntStream.rangeClosed(1, 5).boxed().parallel().reduce(3, (a, b) -> a + b, (a, b) -> a + b);
lower_bounded_wildcard
From the above figure, R (identity) is involved in the evaluation in each thread. And the evaluation process of the sum is (((3 + 1) + 2) + 3) + ((3 + 4) + 5) = 21
Therefore, this is the difference between sequential stream and parallel stream when using reduce()

How to use reduce() in parallel stream correctly

  • Function is associative, , for example:
    • (x ∗ y) ∗ z = x ∗ (y ∗ z)
    • (x + y) + z = x + (y + z)
  • Ensure that R (identity) will not be involved in the evaluation in each thread, for example:
    • When calculating product, the R (identity) value must be set to 1.
    • When calculating sum, the R (identity) value must be set to 0.

Example:

public static void reduceExample() {
    List<String> transcripts = List.of("Oliver,88", "George,52", "Harry,35", "Jack,78", "Jacob,99", "Noah,48");
    /**
     * Map<String, Integer> map = transcripts.stream()
     *         // Convert k, v to Map[k] = v:
     *         .map(new Function<String, Map<String, Integer>>() {
     *             @Override
     *             public Map<String, Integer> apply(String s) {
     *                 String[] ss = s.split(",", 2);
     *                 return Map.of(ss[0], Integer.valueOf(ss[1]));
     *             }
     *         })
     *         // Aggregate all Maps into one Map:
     *         .reduce(new HashMap<String, Integer>(), new BinaryOperator<Map<String, Integer>>() {
     *             @Override
     *             public Map<String, Integer> apply(Map<String, Integer> transcriptMap1, 
     *                                               Map<String, Integer> transcriptMap2) {
     *                 transcriptMap1.putAll(transcriptMap2);
     *                 return transcriptMap1;
     *             }
     *         });
     */
    // Further simplify
    Map<String, Integer> map = transcripts.stream()
            .map(s -> {
                String[] ss = s.split(",", 2);
                return Map.of(ss[0], Integer.valueOf(ss[1]));
            })
            .reduce(new HashMap<String, Integer>(), (transcriptMap1, transcriptMap2) -> {
                transcriptMap1.putAll(transcriptMap2);
                return transcriptMap1;
            });
    map.forEach((k, v) -> System.out.println(k + ": " + v));
}

/* Output */
George: 52
Harry: 35
Jack: 78
Oliver: 88
Noah: 48
Jacob: 99

peek()

Print the value being operated in the stream, which is convenient for our debugging

/* peek() source code */
Stream<T> peek(Consumer<? super T> action);
public static void main(String[] args) {

    List<Integer> list = IntStream.rangeClosed(1, 10).filter(e -> e > 5)
            .peek(e -> System.out.println("Filtered value: " + e))
            .map(x -> x * x)
            .peek(e -> System.out.println("Mapped value: " + e))
            .boxed()
            .collect(Collectors.toList());

    System.out.println(list);
}

/* Output */
Filtered value: 6
Mapped value: 36
Filtered value: 7
Mapped value: 49
Filtered value: 8
Mapped value: 64
Filtered value: 9
Mapped value: 81
Filtered value: 10
Mapped value: 100
[36, 49, 64, 81, 100]

sorted()

Sort the stream

Student.java

public class Student {

    private final String name;
    private final int score;

    Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    public String getName() {
        return name;
    }

    public int getScore() {
        return score;
    }
}

Main.java

public class Main {

    public static void main(String[] args) {

        // Stream<T> sorted();
        List<Integer> list = IntStream.of(7, 4, 5, 3, 8, 2, 9, 6, 10, 1)
                .boxed()
                .sorted()
                .collect(Collectors.toList());
        System.out.println(list);

        List<Integer> listReversed = IntStream.of(7, 4, 5, 3, 8, 2, 9, 6, 10, 1)
                .boxed()
                .sorted(Comparator.reverseOrder())
                .collect(Collectors.toList());
        System.out.println(listReversed);

        // Stream<T> sorted(Comparator<? super T> comparator);
        List<Student> students = new ArrayList<Student>();

        students.add(new Student("Sherry", 90));
        students.add(new Student("Tom", 89));
        students.add(new Student("Jack", 100));
        students.add(new Student("Lily", 75));
        students.add(new Student("Alisa", 68));

        List<String> nameList = students.stream()
                .sorted(Comparator.comparing(Student::getScore))
                .map(Student::getName)
                .collect(Collectors.toList());
        System.out.println(nameList);

        List<String> nameListReversed = students.stream()
                .sorted(Comparator.comparing(Student::getScore).reversed())
                .map(Student::getName)
                .collect(Collectors.toList());
        System.out.println(nameListReversed);
    }
}

/* Output */
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
[Alisa, Lily, Tom, Sherry, Jack]
[Jack, Sherry, Tom, Lily, Alisa]

concat()

Concatenate two streams
lower_bounded_wildcard

public static void main(String[] args) {

    // IntStream --- boxed() ---> Stream
    // Only stream has collect() method
    IntStream intStream1 = IntStream.rangeClosed(1, 5);
    IntStream intStream2 = IntStream.rangeClosed(6, 10);
    List<Integer> list = IntStream.concat(intStream1, intStream2).boxed().collect(Collectors.toList());
    System.out.println(list);
}

/* Output */
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

limit()

Get the first n data from the stream
lower_bounded_wildcard

public static void main(String[] args) {

    // IntStream --- boxed() ---> Stream
    // Only stream has collect() method
    List<Integer> list = IntStream.rangeClosed(1, 10).limit(5).boxed().collect(Collectors.toList());
    System.out.println(list);
}

/* Output */
[1, 2, 3, 4, 5]

skip()

Skip the first n data from the stream
lower_bounded_wildcard

public static void main(String[] args) {

    // IntStream --- boxed() ---> Stream
    // Only stream has collect() method
    List<Integer> list = IntStream.rangeClosed(1, 10).skip(5).boxed().collect(Collectors.toList());
    System.out.println(list);
}

/* Output */
[6, 7, 8, 9, 10]

distinct()

Remove duplicate data in the stream
lower_bounded_wildcard

public static void main(String[] args) {

    // IntStream --- boxed() ---> Stream
    // Only stream has collect() method
    List<Integer> list = IntStream.of(1, 1, 2, 2, 3, 3, 4, 4, 5, 5).distinct().boxed().collect(Collectors.toList());
    System.out.println(list);
}

/* Output */
[1, 2, 3, 4, 5]

Collectors.toList() & Collectors.toSet() & Collectors.toMap()

Because the stream does not store data, after the data in the stream is processed, the data in the stream needs to be collected to a new collection

Food.java

public class Food {

    private final String name;
    private final int stock;
    private final double price;
    private final String category;

    public Food(String name, int stock, double price, String category) {
        this.name = name;
        this.stock = stock;
        this.price = price;
        this.category = category;
    }

    public String getName() {
        return name;
    }

    public int getStock() {
        return stock;
    }

    public double getPrice() {
        return price;
    }

    public String getCategory() {
        return category;
    }

    @Override
    public String toString() {
        return "[" + name + ", " + stock + ", " + price + ", " + category + "]";
    }
}

Main.java

public class Main {

    public static void main(String[] args) {

        List<Food> foods = new ArrayList<Food>();
        foods.add(new Food("Coke", 10, 1.50, "Drink"));
        foods.add(new Food("Sprite", 20, 1.30, "Drink"));
        foods.add(new Food("Milo", 30, 1.80, "Drink"));
        foods.add(new Food("Fries", 10, 2.50, "Dish"));
        foods.add(new Food("Hamburger", 20, 3.50, "Dish"));
        foods.add(new Food("Corn", 30, 3.00, "Dish"));

        // IntStream --- boxed() ---> Stream
        // Only stream has collect() method
        List<Integer> list = IntStream.rangeClosed(1, 10).boxed().collect(Collectors.toList());
        Set<Integer> set = IntStream.rangeClosed(1, 10).boxed().collect(Collectors.toSet());
        Map<String, Food> map = foods.stream().collect(Collectors.toMap(Food::getName, p -> p));

        System.out.println("toList: " + list);
        System.out.println("toSet: " + set);
        System.out.println("toMap: " + map);
    }
}

/* Output */
toList: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
toSet: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
toMap: {Hamburger=[Hamburger, 20, 3.5, Dish], Sprite=[Sprite, 20, 1.3, Drink], Fries=[Fries, 10, 2.5, Dish], 
        Coke=[Coke, 10, 1.5, Drink], Corn=[Corn, 30, 3.0, Dish], Milo=[Milo, 30, 1.8, Drink]}

Counting

Usage Collectors Stream
Count Collectors.counting() count()
Average Collectors.averagingInt(), Collectors.averagingLong(), Collectors.averagingDouble() average()
Maximum & Minimum Collectors.maxBy(), Collectors.minBy() max(), min()
Sum Collectors.summingInt(), Collectors.summingLong(), Collectors.summingDouble() sum()
Summarize Collectors.summarizingInt(), Collectors.summarizingLong(), Collectors.summarizingDouble()
public static void main(String[] args) {
    List<Student> students = new ArrayList<Student>();

    students.add(new Student("Sherry", 90));
    students.add(new Student("Tom", 89));
    students.add(new Student("Jack", 100));
    students.add(new Student("Lily", 75));
    students.add(new Student("Alisa", 68));

    // Long count = students.stream().collect(Collectors.counting());
    long count = students.stream().count();

    // average() must be IntStream
    Double average = students.stream().collect(Collectors.averagingDouble(Student::getScore));

    // Optional<Integer> max = students.stream().map(Student::getScore).collect(Collectors.maxBy(Integer::compare));
    Optional<Integer> max = students.stream().map(Student::getScore).max(Integer::compare);

    // int sum = students.stream().collect(Collectors.summingInt(Student::getScore));
    int sum = students.stream().mapToInt(Student::getScore).sum();

    DoubleSummaryStatistics summarize = students.stream().collect(Collectors.summarizingDouble(Student::getScore));

    System.out.println("count:" + count);
    System.out.println("Average:" + average);
    System.out.println("Max:" + max);
    System.out.println("Sum:" + sum);
    System.out.println("Summarize:" + summarize);
}

/* Output */
count:5
Average:84.4
Max:Optional[100]
Sum:422
Summarize:DoubleSummaryStatistics{count=5, sum=422.000000, min=68.000000, average=84.400000, max=100.000000}

Collectors.partitioningBy() & Collectors.groupingBy()

For Collectors.partitioningBy(), from Map<Boolean, List<T>>, we can know that data can only be divided into two groups, qualified and non-qualified
lower_bounded_wildcard

/* partitioningBy() source code */
public static <T> Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate) {
    return partitioningBy(predicate, toList());
}

For Collectors.groupingBy(), from Map<K, List<T>>, we can know that data can only be divided into multiple groups
lower_bounded_wildcard

/* groupingBy() source code */
public static <T, K> Collector<T, ?, Map<K, List<T>>> groupingBy(Function<? super T, ? extends K> classifier) {
    return groupingBy(classifier, toList());
}

Food.java

public class Food {

    private final String name;
    private final int stock;
    private final double price;
    private final String category;

    public Food(String name, int stock, double price, String category) {
        this.name = name;
        this.stock = stock;
        this.price = price;
        this.category = category;
    }

    public String getName() {
        return name;
    }

    public int getStock() {
        return stock;
    }

    public double getPrice() {
        return price;
    }

    public String getCategory() {
        return category;
    }

    @Override
    public String toString() {
        return name;
    }
}

Main.java

public class Main {

    public static void main(String[] args) {

        List<Food> foods = new ArrayList<Food>();
        foods.add(new Food("Coke", 10, 1.50, "Drink"));
        foods.add(new Food("Sprite", 20, 1.30, "Drink"));
        foods.add(new Food("Milo", 30, 1.80, "Drink"));
        foods.add(new Food("Fries", 10, 2.50, "Dish"));
        foods.add(new Food("Hamburger", 20, 3.50, "Dish"));
        foods.add(new Food("Corn", 30, 3.00, "Dish"));

        Map<Boolean, List<Food>> part = foods.stream()
                .collect(Collectors.partitioningBy(x -> x.getPrice() <= 2.00));
        System.out.println("All the food with price <= 2.00:" + part);

        Map<String, List<Food>> group = foods.stream()
                .collect(Collectors.groupingBy(Food::getCategory));
        System.out.println("All the food group by category:" + group);
    }
}

/* Output */
All the food with price <= 2.00:{false=[Fries, Hamburger, Corn], true=[Coke, Sprite, Milo]}
All the food group by category:{Dish=[Fries, Hamburger, Corn], Drink=[Coke, Sprite, Milo]}

Collectors.joining()

Connect the elements in the stream to a string with a specific connector

public static void main(String[] args) {

    // IntStream --- boxed() ---> Stream
    // Only stream has collect() method
    String list = IntStream.rangeClosed(1, 10)
            .boxed()
            .map(Object::toString)
            .collect(Collectors.joining(","));
    System.out.println(list);
}

/* Output */
1,2,3,4,5,6,7,8,9,10
⚠️ **GitHub.com Fallback** ⚠️