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.
In short, it feels like people who are new to function interfaces, Streams, and Optional are developing with Java 8.
Stream # map
and Stream # filter
) with method referencessort
, implement and write java.util.Comparator
Stream # collect
is cut out elsewhereAnyway, the point is, let's stop stuffing everything into the method chain.
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.
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 ...
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));
We have formulated some rules on the premise that many inexperienced members are enrolled.
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:
class name (variable name) :: method name
, so it's easy to understand what you're doingThe 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
}
}
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.
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();
}
}
java.util.Comparator
to sort with multiple keysSuppose 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.
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.
public int compareTo (element class o)
in the element classThis time, the scores are sorted in the order of national language scores, math scores, and English scores, so I implemented it as follows.
// 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.
Unlike 1, it defines the sort order in another class. The procedure is as follows:
Comparator <element class>
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.
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 implements
Comparable 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.
Stream # collect
is cut out elsewhereI 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());
}
/**
*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
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