Java 8 functional programming

A list,

1.2 What is functional Programming

Functional programming is not always understood in the same way. But the core of it is: when you think about a problem, you use immutable values and functions, which take one value and map it to another.

2. Lambda expressions

Anonymous function:

button.addActionListener(new ActionListener(){
    public void actionPerformed(ActionEvent event){
        System.out.println("button clicked"); }}Copy the code

After using lambda:

button.addActionListener(event -> System.out.println("button clicked");
Copy the code

Another difference from using anonymous inner classes is the way the event parameter is declared. When using anonymous inner classes, you need to explicitly declare the parameter type ActionEvent Event, whereas you don’t need to specify the type in Lambda expressions, and the program can still compile. This is because Javac inferences the type of the event parameter behind the scene from the upper and lower part of the program (the signature of the addActionListener method). This means that if the parameter type is not explicitly specified, it does not need to be explicitly specified.

The target type is the type of context in which the Lambda expression resides. For example, if a Lambda expression is assigned to a local variable or passed to a method as an argument, the type of the local variable or method argument is the target type of the Lambda expression.

String name = "Zhang";
// name = "lisi"; / /; Example 2-7: You cannot assign name more than once. If you assign name more than once, you will get an error by referring to the name variable in a lambda
Button btn = new Button();
btn.addActionListener(event -> System.out.println(name));
Copy the code

Table 2-1 Important functional interfaces in Java

interface parameter The return type The sample
Predicate T boolean Has this record been released yet
Consumer T void Output a value
Function<T,R> T R Gets the name of the Artist object
Supplier None T The factory method
UnaryOperator T T Logic is not
BinaryOperator (T,T) T Take the product of two numbers (*)

** Important issues **

  1. ThreadLocal Lambda expression. Java has a ThreadLocal class that acts as a container for holding the values of local variables in the current thread. Java 8 adds a new factory method to this class that accepts a Lambda expression and generates a new ThreadLocal object without inheritance, which is syntactically more concise. A. Find the method in a Javadoc or integrated development environment (IDE). B. The DateFormatter class is non-thread-safe. Use the constructor to create a thread-safe DateFormatter object and print the date, such as “01-Jan-1970”.

Third, flow

The builder pattern uses a series of actions to set properties and configurations, culminating in a call to the build method, at which point the object is actually created.

3.1 From external iteration to internal iteration

In our usual code, the ForEach loop is actually an external iteration. The for loop is essentially a syntactic sugar that encapsulates the iteration. How it works: We first call the iterator method, which produces a new iterator object and controls the entire iteration process. This is called an external iteration

Example 3-2 uses iterations to calculate the number of artists from London.

int count = 0;
Iterator<Artist> iterator = allArtists.iterator();
while(iterator.hasNext()) {
    Artist artist = iterator.next();
    if (artist.isFrom("London")) { count++; }}Copy the code

Internal iteration

Example 3-3 uses internal iterations to calculate the number of artists from London

logn count = allArtists.stream().filter(artist -> artist.isFrom("London")).count();

Copy the code

3.2 Implementation Mechanism

In Example 3-3, the whole process is broken down into two simpler operations: filtering and counting, which seems like a bit of a simplification — in Example 3-1, there is only one for loop. Does two operations mean two cycles? In fact, the library is so well designed that you only need to iterate over the list of artists once.

Example 3-4 only filter, do not count

allArtists.stream().filter(artist -> artist.isFrom("London"));
Copy the code

This line of code doesn’t actually do anything. The filter just characterizes the Stream, but it doesn’t generate a new set. Methods such as filter that only describe a Stream and ultimately do not produce a new set are called lazy evaluation methods; Methods such as count that eventually produce a value from Stream are called early evaluation methods

The whole process has something in common with the builder model. The builder pattern uses a series of actions to set properties and configurations, culminating in a call to the build method, at which point the object is actually created.

3.3 Common Flow Operations

3.3.1 collect (toList ())

The collect(toList()) method, which generates a list of values from the Stream, is an early evaluation operation.

List<String> collected = Stream.of("a"."b"."c") .collect(Collectors.toList()); 
Copy the code

The OF method of Stream generates a new Stream using an initial set of values.

Four, class,

4.3 Overload Resolution

In summary, when a Lambda expression is taken as an argument, its type is derived from its target type, following the following rules: 1. If there is only one possible target type, it is derived from the parameter types in the corresponding function interface. 2. If there are multiple possible target types, deduce from the most specific type. 3. If there are multiple possible target types and the most specific type is not clear, you need to make a type manually.

4.4 @ FunctionalInterface

Different from Closeable and Comparable interfaces. Every new interface introduced to make the Stream object more operable requires a Lambda expression to implement it. They exist to package blocks of code as data. Therefore, they all have @functionInterface annotations added.

4.7

Three laws of

If you’re not sure how default methods work, and especially how they behave under multiple inheritance (classes that implement multiple interfaces), three simple laws can help:

1. Classes are better than interfaces. Methods defined in the interface can be ignored if there is a method body or abstract method specification in the inheritance chain. 2. Subclasses are better than superclasses. If one interface inherits from another and both interfaces define a default method. Then the method defined in the subclass wins. 3. There is no rule number three. If the above two rules do not apply, the subclass will need to implement the method or declare it abstract.Copy the code

The first rule is to make your code backward compatible.

4.9 Static Methods of interfaces

Stream is an interface. Stream.of is a static method of the interface. This is also a new language feature added in Java 8, designed to help developers who write class libraries, but also for everyday application developers.

Stream and several other subclasses also contain several additional static methods. In particular, the range and iterate methods improve other ways of producing Stream.

4.10 Optional

An important point that has not yet been mentioned is that the reduce method has two forms, one that requires an initial value, as shown in the previous section, and the other variant that does not. In the absence of an initial value, the first step of reduce uses the first two elements of Stream. Sometimes it makes sense to do so when a Reduce operation does not have an interesting initial value. In this case, the reduce method returns an Optional object.

Optional is a new data type designed for the core library to replace null values. Developers often use a null value to indicate that the value does not exist. The Optional object better expresses this concept. The biggest problem with using NULL to indicate that a value does not exist is NullPointerException. A reference to a stored null value variable will crash the program immediately.

Using Optional objects serves two purposes. First, they encourage programmers to check if variables are empty to avoid code defects. Second, it documents potentially empty values in a class’s API, which is much simpler than reading the implementation code.

Optional’s common method

Of and ofNullable

Of and ofNullable are used to create Optional objects. Of does not create null objects, but ofNullable does.

Optional<String> str = Optional.of("sss");  
// If the of parameter is null, nullPointException is thrown
//Optional<String> str1 = Optional.of(null);  
//ofNullable. The parameter can be null. If the parameter is null, the return value is null
Optional<String> str1 = Optional.ofNullable(null); 
Copy the code
IsPresent and get

IsPresent is used to determine if the object is empty, and get gets the object

if (str.isPresent()) {  
    System.out.println(str.get());  
}  
if (str1.isPresent()) {  
    System.out.println(str1.get());  
}  
Copy the code
OrElse and orElseGet

OrElse and orElseGet assign a value to an object when the implementation Optional is empty. The orElse parameter is an assignment object, and orElseGet is an Supplier interface.

//orElse  
System.out.println(str.orElse("There is no value present!"));  
System.out.println(str1.orElse("There is no value present!"));  
  
//orElseGet, set the default value
System.out.println(str.orElseGet(() -> "default value"));  
System.out.println(str1.orElseGet(() -> "default value"));  
Copy the code
orElseThrow

OrElseThrow throws an exception when null is present

try {  
    //orElseThrow  
    str1.orElseThrow(Exception::new);  
} catch (Throwable ex) {  
    // Output: No value present in the Optional instance
    System.out.println(ex.getMessage());  
} 
Copy the code

Advanced collection classes and collectors

5.1 Method Reference

One common use of Lambda expressions is that Lambda expressions often call arguments. For example, to get the artist’s name: The Lambda expression looks like this; Artist -> artist. GetName () is so common. Java8 provides a shorthand syntax for this, called method references, to help programmers reuse existing methods. Override the Lambda expression above with a method reference as follows: Artist::getName The standard syntax is: Classname::methodName. Note: There is no need to add parentheses after the method name

Constructors have the same abbreviations, and if you want to use a Lambda expression to create a Person object, it might look like this

(name,age) -> new Person(name,age)
Copy the code

Using method references, the above code can be written as:

Person::new
Copy the code

You can also create the array String[]::new in this way

5.2 Element Order

5.3 the collector

5.3.1 Conversion to other sets

Can be converted to toList(),toSet(), and so on.

Example 5-5 uses toCollection, which collects elements with custom collections

stream.collect(toCollection(TreeSet::new));
Copy the code

5.3.2 Conversion to value

Example 5-6 Find the band with the most members

public Optional<Artist> biggestGroup(Stream<Artist> artists){
    Function<Artist,Long> getCount = artist -> artist.getMembers().count();
    return artists.collect(Collectors.maxBy(comparing(getCount)));
}
Copy the code

MinBy is to find the minimum.

Examples 5-7 find the average number of tracks on a set of albums

public double averageNumberOfTracks(List<Album> albums){
    return albums.stream().collect(Collectors.averagingInt(album -> album.getTrackList().size()));
}
Copy the code

Chapter 4 introduces special streams, such as IntStream, that define some additional methods for numeric values. In fact, Java 8 also provides collectors that can do similar things. Such as: averageingInt. You can use summingInt and its overloaded methods to sum. SummaryStatistics can also use summingInt and its combination phones.

5.3.3 Data blocks

The collector partitioningBy, which takes a stream and divides it into two parts (as shown in the figure). It uses Predicate objects to determine which part an element belongs to, and flips a Map to a list based on the Boolean values. So, for elements in the true List, Predicate returns true; For other elements in the List, Predicate returns false.

Examples 5-8 divide the stream of artists into bands and solo singers

public Map<Boolean, List<Artist>> bandsAndSolo(Stream<Artist> artists) {
    return artists.collect(partitioningBy(artist -> artist.isSolo()));
}
Copy the code

** 5-9 Usage methods citation divide the Stream composed of artists into bands and solo singers **

public Map<Boolean, List<Artist>> bandsAndSoloRef(Stream<Artist> artists) {
    return artists.collect(partitioningBy(Artist::isSolo));
}
Copy the code

Partition by = partition by = partition by

5.3.4 Data grouping

Data grouping is a more natural way to split data. Instead of splitting the data into true and false parts, you can group the data with any value. For example, there is now a stream of albums, and albums can be grouped by the lead singer on the album.

Examples 5-10 use the lead singer to group albums

public Map<Artist, List<Album>> albumsByArtist(Stream<Album> albums) {
    return albums.collect(groupingBy(album -> album.getMainMusician()));
}
Copy the code

The groupingBy collector (shown in Figure 5-2) accepts a classification function to group the data, just like partitioningBy, accepts a Predicate object to divide the data into true and false parts. The classifier we use is a Function object, the same as the map operation.

5.3.5 string

Example 5-12 uses the stream and collector to format the artist name

String result = artists.stream().map(Artist::getName).collect(Collectors.join(","."["."]"));
Copy the code

The map operation is used to extract the artist’s name and then the values in the stream are collected using illes.joining, which makes it easy to get a string from a stream, allowing the user to provide delimiters (to separate elements), prefixes, and suffixes.

5.3.6 Combined collector

Now consider how to count an artist’s albums.

A simple solution is to group and then count albums using the previous method

Examples 5-13 simple way to count albums per artist

Map<Artist,List<Album>> albumsByArtist = albums.collect(groupingBy(album -> album.getMainMusician()));

Map<Artist, Integer> numberOfAlbums = new HashMap<>();
for(Entry<Artist, List<Album>> entry : albumsByArtist.entrySet()) {
    numberOfAlbums.put(entry.getKey(), entry.getValue().size());
}
Copy the code

It looks simple, but it’s a little messy. This code is also imperative and does not automatically accommodate parallelization.

What you really need here is another collector to tell groupingBy that instead of generating an album list for each artist, you just need the technique for the album. The core class libraries already provide one such collector: Counting.

Examples 5-14 use the collector to count the number of albums per artist

public Map<Artist,Long> numberOfAlbums(Stream<Album> albums){
    return albums.collect(Collectors.groupingBy(album -> album.getMainMusician(),counting()));
}
Copy the code

GroupingBy divides elements into blocks, each associated with a key provided by getMainMusician. Then use another collector downstream to phone the elements in each block, preferably mapping the results into a Map.

Examples 5-15 use a simple method to find the album name of each artist

public Map<Artist, List<String>> nameOfAlbumsDumb(Stream<Album> albums) {
    Map<Artist, List<Album>> albumsByArtist =
        albums.collect(groupingBy(album ->album.getMainMusician()));
        Map<Artist, List<String>> nameOfAlbums = new HashMap<>();
        for(Entry<Artist, List<Album>> entry : albumsByArtist.entrySet()) {
                    nameOfAlbums.put(entry.getKey(), entry.getValue()
                    .stream()
                    .map(Album::getName)
                    .collect(toList()));
    }
    return nameOfAlbums;
}
Copy the code

Example 5-16 uses the collector to find the album name of each artist

    public Map<Artist, List<String>> nameOfAlbums(Stream<Album> albums) {
        return albums.collect(groupingBy(Album::getMainMusician,
                                            mapping(Album::getName, toList())));
    }
Copy the code

In both cases we use a second collector to collect a subset of the end result, and these collectors are called downstream collectors. The collector is a dose of the recipe that generates the final result, and the downstream collector is a partial result of the recipe, which is used in the main collector. This combination of collectors makes their role in the Stream library even more powerful.

Data parallelization

6.1 Parallelism and Concurrency

Concurrency is when two tasks share a time period, and parallel is when two tasks occur at the same time, such as on a multi-core CPU. If a program runs two tasks and only one CPU assigns them different slices of time, then this is concurrency, not parallelism. The difference between the two is as follows:

Data parallelization: Data parallelization is the partitioning of data into blocks and the allocation of individual processing units for each block of data. Data parallelism is useful when you need to perform the same operation on large amounts of data. It breaks the problem down into a form that can be solved on multiple chunks of data, performs operations on each chunk of data, and then sums up the results from each block to produce a final result.

Amdar’s Law is a simple rule that predicts the theoretical maximum speed at which a machine with a multi-core processor can increase its program speed.

6.3 Parallelized Flow Operations

The following methods can be used to obtain a stream with parallelism capabilities.

Parallel () : Stream object call. 2. ParallelStream () : A collection object call that creates a parallel-capable stream.

Question: Does parallelization run stream-based code faster than serialization?

6.4 Simulation System

Discuss the Monte Carlo simulation method. The Monte Carlo simulation repeats the same simulation many times, each time using randomly generated seeds. The results of each simulation are recorded and aggregated to produce a comprehensive simulation of the system. Monte Carlo simulation method is widely used in engineering, finance and scientific computing.

See the code for details.

6.5 limit

Limit the 1 identity value with reduce

Before the reduce method is called, the initial value can be any value, in order for it to work properly during parallelization, the initial value must be the identity value of the composition function ***. When the identity value and other values are used for reduce operations, the other values remain the same. *eg: * Use reduce operation sum, composition function (acc, element) -> acc + element, then its initial value must be 0.

Use reduce to limit 2 associativity.

Another limitation of reduce operations is that combined operations must be associative. This means that the order of the combination operation does not matter as long as the value of the sequence does not change.

Prallel () parallel flow sequential() Sequential flow

If both methods are called at the same time, the last call takes effect. The default is serial.

6.6 performance

There are five main factors affecting the performance of parallel flow, as follows:

① Data size

The size of the input data affects the performance of parallelization. Decomposing the problem, parallelizing it, and then merging the results incur additional overhead. So parallelization only makes sense if the data is large enough and each data processing pipeline takes a large enough amount of time.

② Source data structure

The operation of each pipe is based on some initial data source, usually a collection. It is relatively easy to separate the different data sources, and the overhead here makes it hard to see how much of a performance boost comes from processing data in parallel in the pipeline.

(3) packing

Processing the basic type is faster than processing the boxed type.

The number of cores

In the extreme case, there is only one core, so there is no need to parallelize at all. Obviously, the more cores you have, the greater the potential performance gains. In practice, the number of cores is not just how many cores you have on your machine, but how many cores your machine can use at runtime. This means that other processes running at the same time, or thread affinity (forcing threads to run on certain cores or cpus) can affect performance.

⑤ Unit processing overhead

It’s a battle between the time it takes to execute in parallel and the overhead of decompressing merge operations. The more time spent on each element in the flow, the more performance gains from parallel operations become apparent.

Take a look at a specific problem and see how to break it down and combine it.

Example 6-6: Parallel summation

private int addIntegers(List<Integer> values){
    return values.parallelStream().mapToInt(i -> i).sum();
}
Copy the code

At the bottom level, parallel streams still use the fork/join framework. Fork recursively decomsolves the problem, and then executes each segment in parallel, eventually merging the results by join and returning the final value.

Suppose a parallel stream breaks down our work and executes it in parallel on a four-core machine.

2. As shown in 6-6, computation is performed in parallel in each thread. This involves mapping each Integer object to an int value and then adding up 1/4 of the numbers in each thread. Ideally, we want to spend as much time here as possible, because this is the best place for parallel operations 3. Then merge the results. In example 6-6, this is the sum operation, but it could also be reduce, collect, or other finalizing operations.Copy the code

Impact of data structures on performance:

Performance is good

ArrayList, array, or IntStream.range, these data structures support random reading, which means they can easily be arbitrarily decomposed.

Performance in general

Data structures such as Hashsets and TreeSet are not easily decomposed fairly, but most of the time they are possible.

Performance is poor

Some data structures are difficult to decompose. For example, it may take order N time complexity to decompose the problem. This includes the LinkedList, which is too hard to split 50/50. As well as stream. iterate and bufferedreader. lines. They have an unknown length. So it’s hard to predict where it’s going to break down.