lambda Usage
Java 8 Functional Programming
NOTES: All content taken from Java 8 Functional Programming
Refactoring Legacy Code
public Set<String> findLongTracks(List<Album> albums) {
Set<String> trackNames=new HashSet<>();
for(Album album : albums) {
for (Track track : album.getTrackList()) {
if (track.getLength() > 60) {
String name=track.getName();
trackNames.add(name);
}
}
}
return trackNames;
}
Refactor 1 for to forEach
public Set<String> findLongTracks(List<Album> albums) {
Set<String> trackNames=new HashSet<>();
albums.stream()
.forEach(album-> {
album.getTracks()
.forEach(track-> {
if (track.getLength() > 60) {
String name=track.getName();
trackNames.add(name);
}
});
});
return trackNames;
}
Refactor 2 if to filter
public Set<String> findLongTracks(List<Album> albums) {
Set<String> trackNames=new HashSet<>();
albums.stream()
.forEach(album-> {
album.getTracks()
.filter(track-> track.getLength() > 60)
.map(track-> track.getName())
.forEach(name-> trackNames.add(name));
});
return trackNames;
}
Refactor 3 Outermost forEach to flatMap
public Set<String> findLongTracks(List<Album> albums) {
Set<String> trackNames=new HashSet<>();
albums.stream()
.flatMap(album-> album.getTracks())
.filter(track-> track.getLength() > 60)
.map(track-> track.getName())
.forEach(name-> trackNames.add(name));
return trackNames;
}
Refactor 4 forEach adding elements to collect for collection
public Set<String> findLongTracks(List<Album> albums) {
return albums.stream()
.flatMap(album-> album.getTracks())
.filter(track-> track.getLength() > 60)
.map(track-> track.getName())
.collect(toSet());
}
Function Method Parameter Overloading
overloadedMethod((x, y)-> x+y);
private interface IntegerBiFunction extends BinaryOperator<Integer> {
}
private void overloadedMethod(BinaryOperator<Integer> Lambda) {
System.out.print("BinaryOperator");
}
private void overloadedMethod(IntegerBiFunction Lambda) {
System.out.print("IntegerBinaryOperator");
}
Accept
BinaryOperatorand a subclass of this interface as parameters respectively. When calling these methods, the type of Lambda expression inferred by Java is exactly the type of the most specific functional interface. For example, when Example 4-7 chooses between the two methods in Example 4-8, the output isIntegerBinaryOperator.
General Principles
-
If there is only one possible target type, it is inferred from the parameter type in the corresponding functional interface;
-
If there are multiple possible target types, it is inferred from the most specific type;
-
If there are multiple possible target types and the most specific type is ambiguous, the type needs to be manually specified.
Interface Multiple Inheritance
public interface Jukebox {
public default String rock() {
return "... all over the world! ";
}
}
public interface Carriage {
public default String rock() {
return "... from side to side";
}
}
public class MusicalCarriage implements Carriage, Jukebox {
}
javac is not clear which interface's method should be inherited, so the compiler will report an error: class Musical Carriage inherits unrelated defaults for rock() from types Carriage and Jukebox.
public class MusicalCarriage
implements Carriage, Jukebox {
@Override
public String rock() {
return Carriage.super.rock();
}
}
General Principles
-
Classes win over interfaces. If there is a method body or abstract method declaration in the inheritance chain, then the method defined in the interface can be ignored.
-
Subclasses win over parent classes. If an interface inherits another interface, and both interfaces define a default method, then the method defined in the subclass wins.
-
No rule three. If the above two rules do not apply, the subclass must either implement the method or declare the method as abstract.
- For example, when using parallel streams,
forEachmethod cannot guarantee that elements are processed in order. If processing in order is required,forEachOrderedmethod should be used.
Sub-collector groupingBy
public Map<Artist, List<String>> nameOfAlbums(Stream<Album> albums) {
return albums.collect(groupingBy(Album::getMainMusician, mapping(Album::getName, toList())));
}
mapping allows perform map-like operations on the collector's container. But need to specify what kind of collection class to use to store results, such as toList.
Refactoring Domain Methods
public long countFeature(ToLongFunction<Album> function) {
return albums.stream()
.mapToLong(function)
.sum();
}
public long countTracks() {
return countFeature(album-> album.getTracks().count());
}
public long countRunningTime() {
return countFeature(album-> album.getTracks()
.mapToLong(track-> track.getLength())
.sum());
}
public long countMusicians() {
return countFeature(album-> album.getMusicians().count());
}
Tips
-
It is simple to judge whether an operation is lazy evaluation or eager evaluation: just look at its return value. If the return value is Stream, then it is lazy evaluation; if the return value is another value or empty, then it is eager evaluation.
-
After extracting the logic of lambda into a method, you can test the method and cover all boundary cases.
-
Use peek method to observe lambda intermediate values.
Set<String> nationalities = album.getMusicians()
.filter(artist-> artist.getName().startsWith("The"))
.map(artist-> artist.getNationality())
.peek(nation-> System.out.println("Found nationality: "+nation))
.collect(Collectors.<String>toSet());
- Attribution: Retain the original author's signature and code source information in the original and derivative code.
- Preserve License: Retain the Apache 2.0 license file in the original and derivative code.
- Attribution: Give appropriate credit, provide a link to the license, and indicate if changes were made.
- NonCommercial: You may not use the material for commercial purposes. For commercial use, please contact the author.
- ShareAlike: If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original.