[For beginners] How to operate Stream API after Java 8

How to interact with Stream API after Java 8

Introduction

Overview

It's been a long time since the function interface became available in Java. I thought it was quite recent, but it was five years ago. With such a function interface and Stream API, I think there is a considerable difference between those who say "easy to understand" and those who say "difficult to understand". I will write it as a reference for how to deal with Stream API in such a situation.

Premise

In short, it feels like people who are new to function interfaces, Streams, and Optional are developing with Java 8.

Conclusion

Anyway, the point is, let's stop stuffing everything into the method chain.

Factors that make code written with the Stream API difficult to understand

There are various ways to write lambda expressions, method references, etc.

There are several ways to create a functional object. Define a function object that takes a string and calls the StringUtils # isEmpty defined above to return the result.

Function<String, Boolean> emptyChecker1 = new Function<>{
  @Override
  public Boolean apply(String s) {
    return StringUtils.isEmpty(s);
  }
}

Function<String, Boolean> emptyChecker2 = s -> StringUtils.isEmpty(s);

Function<String, Boolean> emptyChecker3 = StringUtils::isEmpty;

It is especially difficult to refer to methods, and depending on whether you write the class name or variable name to the left of ::, which method to call will change, and in some cases it will be confused.

The argument function is too complicated to understand

I can't show this code when I have a child with 1 year of Java development experience (confession).

list.getValues().forEach(value -> {
  if (CollectionUtils.isEmpty(value.getPropertyList())) {
    return;
  }
  if (!CollectionUtils.isEmpty(value.getPropertyList())) {
    Property property = value.getPropertyList().stream()
        .findFirst()
        .orElseThrow(RuntimeException::new);
    service.insert(value, property);
    return;
  }

  switch (value.getType()) {
    case TYPE_1:
      value.getProperty1().setAmount(1000);
      break;
    case TYPE_2:
      value.getProperty2().setAmount(1000);
      break;
    case TYPE_3:
      value.getProperty3().setAmount(1000);
      break;
    }
    service.insert(value);
});

I try to read the source code with an awareness that ** there is a paragraph for each block of processing **, but if the argument of forEach is so long, I can't read it all at once. I will. I'm in the third year, so I'm sure the children in the first year ...

It is quite difficult to write the termination process

If you just want to convert to List, you can use Collectors.toList () to get rid of it in an instant, but I find it quite difficult to write a simple process to convert List to Map. What to do if there is duplication? For beginners, the hurdles are high and writing is troublesome.

Map<Key, List<Value>> map = values.stream()
      .collect(Collectors.groupingBy(Value::getKey));

Rules around Stream API (I wanted to operate in the future)

We have formulated some rules on the premise that many inexperienced members are enrolled.

Write intermediate operation arguments with method references

The purpose is to keep the arguments of the intermediate operations Stream # map and Stream # filter simple and improve readability. We believe that this rule has the following benefits:

The third one is a little difficult to understand, so I will explain it using an example of a program that calculates the average score in the class of the mid-term exam.

By the way, the mid-term exam assumes three subjects, Japanese, math and English, and considers the implementation of ʻExaminationScoreSummary # average`.

public class ExaminationScore {
  private final Integer japaneseScore;
  private final Integer mathScore;
  private final Integer englishScore;

  // constractor, getter
}

public class ExaminationScoreSummary() {
  private final List<ExaminationScore> values;
  // constractor, getter

  public Integer average() {
    // TODO
  }
}

When using a lambda expression

It can be implemented in any way. I write it as if I always write it.

public class ExaminationScoreSummary() {
  private final List<ExaminationScore> values;
  // constractor, getter

  public Integer average() {
    return values.stream()
        .mapToInt(score -> score.getJapaneseScore() + score.getMathScore() + score.getEnglishScore())
        .average();  
  }
}

No, this is fine, but when you ask for the total score, you should add the points at the caller every time.

When using method references

In the case of method reference, it is impossible for the caller to add, so for the time being, write the process of adding in the ʻExaminationScore` class.

public class ExaminationScore {
  private final Integer japaneseScore;
  private final Integer mathScore;
  private final Integer englishScore;

  // constractor, getter
  public Integer getTotalScore() {
    return japaneseScore + mathScore + englishScore;
  }
}

For experienced people, it may be natural to write "calculations between fields of the same class in the method in which the fields are defined to increase cohesiveness". But at my level, it's difficult. It is a story that if you rule the use of method references, you will also learn the basic idea of object-oriented programming.

The caller's implementation looks like this.

public class ExaminationScoreSummary() {
  private final List<ExaminationScore> values;
  // constractor, getter

  public Integer average() {
    return values.stream()
        .mapToInt(ExaminationScore::getTotalScore)
        .average();  
  }
}

Use java.util.Comparator to sort with multiple keys

Suppose you want to post in descending order of mid-term exam scores. No, there is a problem such as exposing the score ... Since there are 3 subjects, they are sorted in descending order of national language scores, and if the national language scores are the same, they are sorted in descending order of math scores.

Using Comparator # comparing and Comparator # thenComparing, it is possible to implement as follows.

List<ExaminationScore> values = new ArrayList<>();
values
    .stream()
    .sorted(Comparator.comparing(ExaminationScore::getJapaneseScore).thenComparing(ExaminationScore::getMathScore())
    .collect(Collectors.toList());

It's convenient to be able to sort things so easily. By the way, if the sort order becomes complicated, will I write everything here? No, no, it's pretty tough. In junior high school, if there are 5 subjects or if there are exams in practical subjects, the code will be insanely long.

Here are two ways to write the definition of "in what order" elsewhere.

1. Have the elements of the collection implement the Comparable interface.

It is a way to define how to sort in the type of collection element. First, define how to sort. There are only two steps below.

  1. Add ʻimplements Comparable ` to the element class declaration
  2. Implement public int compareTo (element class o) in the element class

This time, the scores are sorted in the order of national language scores, math scores, and English scores, so I implemented it as follows.

  1. If the national language scores are not equal, treat the national language score comparison result as the ʻExamination Score` comparison result.
  2. If the national language scores are equal and the math scores are not equal, the math score comparison result is treated as the ʻExamination Score` comparison result.
  3. If the scores of Japanese and math are equal and the scores of English are equal, the comparison result of English scores is treated as the comparison result of ʻExamination Score`.

// 1.In the class declaration of the element`implements Comparable<Element class>`Added
public class ExaminationScore implements Comparable<ExaminationScore> {
  private final Integer japaneseScore;
  private final Integer mathScore;
  private final Integer englishScore;

  // constractor, getter

  // 2.In the element class`public int compareTo(Element class o)`Implemented
  public int compareTo(ExaminationScore o) {
    if (japaneseScore.compareTo(o.japaneseScore) != 0) {
      return japaneseScore.compareTo(o.japaneseScore);
    }
    if (mathScore.compareTo(o.mathScore) != 0) {
      return mathScore.compareTo(o.mathScore);
    }
    return englishScore.compareTo(o.englishScore);
  }
}

I forget what to return with the return value of Comparable # compareTo, so I try to implement it as simple as possible by returning the comparison result of the variables that are used as the sort key.

Sorting is done as follows. We call Comparator # reverseOrder () because it sorts in descending order of score.

List<ExaminationScore> values = new ArrayList<>();
values
    .stream()
    .sorted(Comparator.reverseOrder())
    .collect(Collectors.toList());

If you implement the Comparable # compareTo method as" -1 if the score is high ", you can omit the argument of Stream # sorted, but I avoided it because it is difficult to understand.

2. Create a separate class that implements the Comparator interface

Unlike 1, it defines the sort order in another class. The procedure is as follows:

  1. Create a class that implements Comparator <element class>
  2. Implement public int compare (element class o1, element class o2)
class ExaminationScoreComparator implements Comparator<ExaminationScore> {
  @Override
  public int compare(ExaminationScore o1, ExaminationScore o2) {
    if (Integer.compare(o1.getJapaneseScore(), o2.getJapaneseScore()) != 0) {
      return Integer.compare(o1.getJapaneseScore(), o2.getJapaneseScore());
    }
    if (Integer.compare(o1.getMathScore(), o2.getMathScore()) != 0) {
      return Integer.compare(o1.getMathScore(), o2.getMathScore());
    }
    return Integer.compare(o1.getEnglishScore(), o2.getEnglishScore());
  }
}

Sorting is done as follows. Pass the instance of Comparator defined above to the argument of Stream # sorted.

List<ExaminationScore> values = new ArrayList<>();
values
    .stream()
    .sorted(new ExaminationScoreComparator().reverseOrder())
    .collect(Collectors.toList());

Comparable vs Comparator

After all, it's a matter of which one to use, but basically let's implement it using Comparator.

For natural ordering, consistency with equals is not required, but highly recommended. This is because using a sort set or sort map that does not specify an explicit comparator with elements or keys whose natural ordering is inconsistent with equals does not guarantee the behavior of the set and map. In particular, such sort sets or sort maps violate the general rules of sets or maps. This convention is defined using the terms of the equals method.

From Comparable (Java Platform SE 8)

As stated in the official documentation, if there is a contradiction between ʻequals and compareTo, operation with Mapetc. will not be guaranteed. If the class that implementsComparable is the key Map, then Map # get uses the result of compareTo instead of the key ʻequals, which is a strange bug.

The same thing was written in Which Java Comparable or Comparator to use.

Boilerplate-like processing of Stream # collect is cut out elsewhere

I think it's common to take a specific field in a collection element to create a new collection, or use a specific field in a collection as a key to create a Map. I think it's better to be able to use this kind of thing without touching the Stream API one by one.

/**
 *Create another instance from the list element and return the list of created instances.<br>
 *The instance creation logic follows the function object given in the second argument.<br>
 * @param list list
 * @param generator A function object that instantiates another type from an element in the list
 * @param <S>The type of the original list element.
 * @param <R>New list element type.
 * @return List of generated instances
 */
public static <S, R> List<Property> collect(List<S> list, Function<S, R> extractor){
  return list.stream()
      .map(extractor)
      .collect(Collectors.toList());
}

From PropertyCollector.java

/**
 *Group the list with a specific key and return a map with the key and list paired.<br>
 *The key generation logic follows the function object given in the second argument.<br>
 * @param list List to be grouped
 * @param keyExtractor A function object that gets the list key from a list element
 * @param <K>Key type. Must be a class that implements the Comparable interface
 * @param <V>List element type
 * @return grouping result
 */
public static <K, V> Map<K, List<V>> groupingBy(List<V> list, Function<V, K> keyExtractor) {
  return list.stream().collect(Collectors.groupingBy(keyExtractor));
}

From MapCollector.java

Finally

The function interface and Stream API are very useful, but for teams with many inexperienced members, the readability of the source code may be reduced and productivity may be reduced. In this article, I wrote a lot about the joke I thought about in the third year, but it seems that the team has also devised a way of writing and used new Java features to increase the productivity of the team. I would be happy if there was.

Recommended Posts

[For beginners] How to operate Stream API after Java 8
[Java] How to operate List using Stream API
[Must-see for apprentice java engineer] How to use Stream API
[Java] Introduction to Stream API
Java application for beginners: stream
Java8 / 9 Beginners: Stream API addiction points and how to deal with them
[java8] To understand the Stream API
[Introduction to Java] About Stream API
Java 8 ~ Stream API ~ to start now
For Java beginners: List, Map, Iterator / Array ... How to convert?
Java Stream API
[Ruby] How to use slice for beginners
[For beginners] How to debug in Eclipse
[Java] [For beginners] How to insert elements directly in a 2D array
[Java] How to test for null with JUnit
[For beginners] How to implement the delete function
How to use Java API with lambda expression
[Java] (for MacOS) How to set the classpath
[For beginners] About lambda expressions and Stream API
[For super beginners] How to use autofocus: true
[Introduction to Java] Basics of java arithmetic (for beginners)
[Java] How to make multiple for loops single
[Java] Stream API / map
Java8 Stream API practice
How to implement login request processing (Rails / for beginners)
[Spring Boot] How to create a project (for beginners)
[For beginners] Minimum sample to display RecyclerView in Java
Java development for beginners to start from 1-Vol.1-eclipse setup
How to use Truth (assertion library for Java / Android)
Summary of Java communication API (1) How to use Socket
Introduction to Java for beginners Basic knowledge of Java language ①
Summary of Java communication API (3) How to use SocketChannel
Summary of Java communication API (2) How to use HttpUrlConnection
How to loop Java Map (for Each / extended for statement)
How to execute WebCamCapture sample of NyARToolkit for Java
How to use GitHub for super beginners (team development)
[Java] How to use Map
How to use Chain API
How to lower java version
[Java] How to use Map
How to uninstall Java 8 (Mac)
Java --How to make JTable
Java debug execution [for Java beginners]
[Java] Basic statement for beginners
Java Stream API cheat sheet
How to write java comments
How to use java class
Java Stream API in 5 minutes
[Java] How to use Optional ②
[Java] How to use removeAll ()
[Java] How to display Wingdings
[Java] How to use string.format
How to use Java Map
[Java] Stream API --Stream termination processing
How to set Java constants
[Java] Stream API --Stream intermediate processing
Java for beginners, data hiding
How to use Java variables
How to convert Java radix
[Java] How to implement multithreading
[Java] How to use Optional ①