[Java study notes] Effective Java (Third Edition) Chapter 7 Lambda and Stream

In Java 8, add the function interface (functional interface), Lambda expressions, and references method (method reference), makes creating a function object (function object) easier. Stream API also provides class libraries levels of support for the processing sequence of data elements.

 

42 Tiao: Lambda anonymous class

Previously, a single abstract methods with the interface as a function of the type (function type), examples of which are called function object (function object), represents a function or action to be taken. The main way JDK1.1 start function object is created by anonymous class (anonymous class) (Article 24). It follows with an anonymous class to create a sort of comparison function:

// Anonymous class instance as a function object - obsolete!
Collections.sort(words, new Comparator<String>() {
    public int compare(String s1, String s2) {
        return Integer.compare(s1.length(), s2.length());
    }
});

Anonymous class applies to classical object-oriented design patterns need function objects, especially policy mode. Comparator interface represents the abstract policy for ordering; anonymous class is above specific strategies for the collation string.

In Java 8, the formal language of the concept, i.e., a single abstract method is a special interface should be given special treatment. These interfaces are now called functional interfaces, and the language allows you to use lambda lambda expressions or simply to create instances of these interfaces. Lambdas anonymous class is similar in function, but more concise. The following code uses the anonymous lambdas replace the above classes. Model disappeared, behavior is also very clear:

// Lambda expression as function object (replaces anonymous class)
Collections.sort(words, (s1, s2) -> Integer.compare(s1.length(), s2.length()));

Lambda type (Comparator <String>), its parameter types (s1 and s2, are both String) and type (int) returns a value, it does not appear in the code. The compiler uses a process called type inference (type inference) deduced from the context of these types.

About type inference should add a warning. Article 26 tells you not to use the original ecological types, 29 said to support generic types, 30 said to support generic method. Because the compiler allow it to get the most from generic type inference type information.

If the comparator is configured to use a method in place of lambda, the code comparator may become more compact (article 14, 43):

Collections.sort(words, comparingInt(String::length));

In fact, added to the Java sort Method 8 by using List interface, a fragment can become shorter:

words.sort(comparingInt(String::length));

The first 34 entries, said attribute preferable to enumerate instances of a particular class constants body. Lambdas can be achieved constant specific behavior easily use the former but not the latter. Each pass will only achieve enumeration constant behavior of lambda to its constructor method. The constructor lambda stored in the instance attributes, apply the method to forward the call to lambda. The resulting code is much simpler than the original version, more clearly:

public enum Operation {
    PLUS  ("+", (x, y) -> x + y),
    MINUS ("-", (x, y) -> x - y),
    TIMES ("*", (x, y) -> x * y),
    DIVIDE("/", (x, y) -> x / y);
    
    private final String symbol;
    private final DoubleBinaryOperator op;
    Operation(String symbol, DoubleBinaryOperator op) {
        this.symbol = symbol;
        this.op = op;
    }

    @Override public String toString() { return symbol; }
    public double apply(double x, double y) {
        return op.applyAsDouble(x, y);
    }
}

lambda no names and documents; if the calculation is not self-explanatory, or more than a few lines, do not set it into the lambda expression. One line of code for lambda said to be ideal, three lines of code is a reasonable maximum. If you violate this provision, it could seriously damage the readability of the program.

Lambda is limited to the function interface. If you want to create an instance of an abstract class, you can use an anonymous class to implement, but can not use lambda. Similarly, you can use an anonymous class to create an interface instance with a more abstract approach.

Finally, lambda can not get a reference to itself. In the lambda, this keyword refers to instances closed, which is usually what you want. In an anonymous class, this keyword refers to the anonymous class instance. If you need to access its internal function object, you must use an anonymous class.

Lambda and share your anonymous class can not be reliably serialization and de-serialization by implementing property. Therefore, try not to (unless forced to) a sequence of Lambda.

In short, Java8 start, Lambda has become the best way to express a small function object. Never use an anonymous class to a function object, unless you must create an instance of the type of non-functional interface.

 

 

43 Section: precedence method references Lambda

The main advantage of better than lambda anonymous class is it more concise. Java provides a method for generating a function object, but also simpler than lambda, that is: the method reference ( Method, the References ) .

Below is a piece of program code, it maintains a mapping from a key to an integer value. If the value is interpreted as a number of examples of keys, the program is a set of a multiple. The function code is to find an integer value according to the key, and then added to 1 on the basis of:

map.merge(key, 1, (count, incr) -> count + incr);

This code uses the merge method that has been added to the Java 8 Map interface. If no mapping given key, the method only inserts the given value; If the mapping exists, then the combined value of the current applied to a given function and a given value, and overwrite the current value with the result.

Starting Java 8, Integer class (and all other types of packaging basic numbers) to provide the sum of a static method, and it is identical. We can simply pass a reference to this method, with less visual clutter and get the same results:

map.merge(key, 1, Integer::sum);

The more method parameters, you can eliminate the reference method by more boilerplate.

The following table summarizes all five methods referenced:

 

In short, the method is often cited than Lambda more concise expression. As long as the reference method is more concise, clear, use the method reference. If the method is simple references do not, they stick with Lambda.

 

 

44 bar: stick with the standard function interface

java.util.Function package provides a number of standard function interface. As long as a standard function interface to meet the needs, priority should be given, rather than re-build a new special function interface.

java.util.Function provides 43 interfaces, do not remember all, if you can remember where six basic interfaces, if necessary, can be inferred that the rest of the interface. Basis as an interface reference type to object.

Operator interface represents a function of the parameters were consistent with the type.

Predicate interface represents a parameter and returns a boolean function of.

Interface Function on behalf of its arguments and the return type of inconsistency.

Supplier interface represents no parameters and returns (or "offer") value of a function.

Consumer interface represents a function with a function, but does not return any value, equivalent to consume its parameters.

This base 6 has three interfaces and various variants were applied to the basic int, long, and double. Their naming is to add a base type in front of the name of the underlying interface. For example, the interface with the predicateA int, which is a variant name IntPredicate.

Function Interface variants 9, the results for the type of the basic type.

Source and result type is always different, because from UnaryOperator type to its own function. If the source type and result types are primitive types, with the prefix Function SrcToResult, e.g. LongToIntFunction (six variants). If the source is a base type, the result is an object reference, then with <Src> ToObj prefix Function, e.g. DoubleToObjFunction (three variants).

There are three parameters comprising two versions of the basic functions of the interface, so that they make sense: BiPredicate <T, U>, BiFunction <T, U, R> and BiConsumer <T, U>. Also returns three related types BiFunction basic variants: ToIntBiFunction <T, U>, ToLongBiFunction <T, U> and ToDoubleBiFunction <T, U>. Consumer two variables, which has an object reference and a basic types: ObjDoubleConsumer <T>, ObjIntConsumer <T> and ObjLongConsumer <T>. A total of nine two versions of the basic interface parameters.

Finally, there is a BooleanSupplier interface, which is a variant of the Supplier, which returns a Boolean value. This is a Boolean function of any standard interface name only explicitly mentioned, but returns a Boolean value supported by Predicate and its four variants form. BooleanSupplier interfaces and interfaces 42 described in the preceding paragraph forty-three cent of all functional interface standard.

Most standard functional interface is only intended to provide support for basic types. Do not try to use the basic functions to interface boxed primitive wrapper class instead of the basic types of functional interfaces.

 

Now that you know you should generally use the standard function interface priority to write your own interface. But when should you write your own interface? Of course, if there is no standard modules to meet your needs, for example, if you need a Predicate with three parameters, or abnormalities of a Predicate throw, you need to write your own code. But sometimes you should write your own functions interface, even if the same structure in which a standard functional interface.

 

@FunctionalInterface notes, similar to the type @Override. This is a statement of intent of the programmer, it serves three purposes: it tells the reader that class and its documentation, the interface is designed to achieve a lambda expression; it keeps you reliable, because only an abstract method unless otherwise interface will not compile; it prevents the maintenance personnel inadvertently add an abstract method to the interface when the interface changes occur.

Always use @FunctionalInterface annotation label your function interface.

 

In short, Java now have lambda expressions, it is necessary to consider the lambda expression to design your API . Receiving function on the interface type input and returns them in the output. In general, it is best to use java.util.function.Function standard interfaces are provided, but please note that in relatively rare cases, it is best to write your own function interface.

 

 

45 bar: Use caution Stream

Java8 increased Stream API, simplified serial or parallel operation of large quantities. The API provides two key Abstract: Stream (stream) data representative of elements of a finite or infinite sequence, Stream pipeline (pipe flow) represents a multi-stage calculation of these elements.

Stream data elements can be substantially or object reference type. It supports three basic types: int, long and double.

Flow conduit from the source stream (source stream) zero or more intermediate operations and consisting of a termination operation. Each intermediate operation in some way commutations, for example, a function mapping each element of the element or filter out all the elements do not satisfy certain conditions. An intermediate operation will stream into another stream, which may be the same or different from the element type of the input stream. End of the last operation performed convection calculated to produce the final intermediate operations, for example, to store its elements to the collection, return an element or all elements print.

Pipeline Delay (lazily) calculated evaluation: Calculate the start until the end of the operation before being called and the end in order to complete the operation without the need for data elements will never be calculated. This delay is calculated as evaluation makes it possible to use an unlimited stream.

Stream API-flow type (fluent) :: It is designed to allow all calls to the composition of the pipeline is linked to a single expression. In fact, a plurality of pipes can be linked together to form an expression.

 

Excessive use of stream to make the program harder to read and maintain.

"Hello world!".chars().forEach(System.out::print);

You may want to print it Hello world !, but if you run it, it prints found 721011081081113211911111410810033. This is because the "Hello world!". Chars () char value of the stream element is not returned, but int value, so called print the int overload.

"Hello world!".chars().forEach(x -> System.out.print((char) x));

Ideally, it should be avoided to handle the flow char value .

Stream could easily do something:

• Unified conversion element sequence

• Filter element sequence

• sequence of combinations of elements in a single operation (e.g., add, or connected calculates a minimum value)

• accumulate the sequence of elements into a set, possibly through a number of common attributes to group them

• In the elements of a sequence satisfy certain elements of the search condition

 

For flow, it is difficult to do one thing at the same time access to the corresponding elements in multiple stages in the pipeline: Once the value is mapped to a different value, the original value will be lost.

In short, some tasks better to use the stream to complete some tasks better to use iterations to complete. These two methods are combined, can best accomplish many tasks. The choice of which method to use the task, there is no hard and fast rules, but there are some useful heuristics. In many cases, the use of which will be clear; in some cases, it is not very clear. If you are unsure task is better done through an iterative or flow, so try both methods to see which is better.

 

46 Section: Stream Priority Select a function without the side effects

Stream is not just an API, which is a functional programming model. For a description and brought Stream velocity, sometimes parallelism, and it must be generic API.

Paradigm Stream is the most important part of the calculated number of variant configuration, each have a structure as close to a pure function (pure function) on a result. Pure function means that it depends on the function input Results: it does not depend on any state variable, not update any state.

// Uses the streams API but not the paradigm--Don't do this!
Map<String, Long> freq = new HashMap<>();
try (Stream<String> words = new Scanner(file).tokens()) {
    words.forEach(word -> {freq.merge(word.toLowerCase(), 1L, Long::sum);});
}

This code is not the code stream; it is disguised as a stream of code iteration code. It does not benefit from the stream API, and it is longer than the corresponding iterative code harder to read, and difficult to maintain.

This code is a termination of operation forEach all work is done using a change external state (frequency table) of lambda.

This code is modified:

// Proper use of streams to initialize a frequency table
Map<String, Long> freq;
try (Stream<String> words = new Scanner(file).tokens()) {
    freq = words.collect(groupingBy(String::toLowerCase, counting()));
}

ForEach action is to terminate the operation is not the most powerful, but also to Stream most unfriendly. It is a Iteration, is not suitable for parallel.

forEach operation should only be used to report the results of calculation Stream, instead of performing the calculation.

ForEach sometimes be used for other purposes, such as the addition result to the calculation Stream previously set to already existing.

Improved code uses a collector (collector), which is a new concept using stream must learn. Collectors of API daunting: it has 39 methods, some of which have up to five types of parameters. The good news is that you can get most of the benefits from the API, rather than in-depth study of all its complexity. For starters, you can ignore the collection interface, the collector is seen as an opaque object encapsulates reduction strategy (reduction strategy) is. In this context, means that the flow reduction element combination as a single object. The collector is usually generated by a collection of objects (which represents the name of the collector).

 

The elementary stream to collect real collection collector is very simple. There are three such collectors: toList (), toSet () and toCollection (collectionFactory). They return the collection type sets, lists, and specified by the programmer. With this knowledge, we can write a stream pipe is extracted from the frequency table in front of our frequency list of 10 words appear.

// Pipeline to get a top-ten list of words from a frequency table
List<String> topTen = freq.keySet().stream().sorted(comparing(freq::get).reversed()).limit(10).collect(toList());

Note that there is no method to toList coupled with its Collectors class. All members of the static import Collectors convention is also wise, because it can improve the readability of Stream pipeline.

This code is part of a skill passed sorted comparator comapring (freq :: get) .reversed ().

comparing method is a constructor comparator (Article 14), which has a key extraction function to read a word, table query is actually extracted, restricted freq :: get method reference frequency word lookup table, and returns the number of times the word appears in the document. Finally, call reversed in the comparator, sorted according to the word of frequency.

 

toMap more complex form, and groupingBy method, various methods such conflicts (Collisions) process. One method is to provide a method other than the merge key and value mapper (by mappers) to toMap method. The method is a merge BinaryOperator, wherein V is the value of the map type. Any additional keys associated with the use in conjunction with conventional methods merge values.

// Using a toMap collector to make a map from string to enum
private static final Map<String, Operation> stringToEnum =
Stream.of(values()).collect(toMap(Object::toString, e -> e));

There toMap form with three parameters. Suppose there is a Stream, representing different singer album, we want to get from a singer to a mapping between the best-selling album.

// Collector to generate a map from key to chosen element for key
Map<Artist, Album> topHits = albums.collect(toMap(Album::artist, a->a, maxBy(comparing(Album::sales))));

Comparator static factory method maxBy, which is imported from BinaryOperator static. This method Comparator <T> is converted to BinaryOperator <T>, specify a maximum value comparator for calculating implied. In this case, the comparator comparing returned by the constructor of the comparator, which uses a function key extractor Album :: sales. It said, "the album (albums) stream into the map, to each artist (artist) is mapped to the best album sales."

 

Still another use form toMap with three parameters, i.e., generate a trap to force "Reserved Last Update" (last-write-wins) when there is a conflict.

// Collector to impose last-write-wins policy
toMap(keyMapper, valueMapper, (oldVal, newVal) ->newVal)

In addition toMap method, Collectors API also provides a method groupingBy: it returns the collector to generate a map, according to the classification categories function element.

This is the collector we used in the article 45 Anagram program, for generating a map from the words in alphabetical order to the list of words:

words.collect(groupingBy(word->alphabetize(word)))

Collectors final joining method, it is only Stream CharSequence instance operations, such as a string. It returns as a parameter a simply incorporated the collector element.

 

In short, write Stream pipeline is essentially no side effects are a function of the object. This applies to incoming Stream all function objects and related objects. ForEach termination operation should only be used by the Stream reports the results of execution, rather than let it perform the calculation. In order to properly use Stream , you must understand collector. The most important collector plant is toList, toSet, toMap, groupingBy and joining .

 

 

47 Article: Stream priority Collection used as the return type

Many methods return elements of the sequence (sequence). Before Java 8, the return type is generally Collection, Set List, and these interfaces; and further comprising Iterable array type. If the return type is the basic element or stringent performance requirements, the array is used. In Java 8, the stream (Stream) was added to the platform, so that the sequence returns to select the appropriate method return type of task becomes very complicated.

Perhaps you have heard, now returns Stream is a sequence of elements most sensible choice, but as stated in article 45, Stream did not eliminate iterations: to write good code must skillfully Stream and iterative combination. If an API only returns a stream, and some users want to use for-each loop through the sequence of returns, then those users will certainly feel uneasy.

Since Stream interface contains only abstract method in Iterable interface, compatible with the specification method Stream Iterable. The only reason to stop programmers use for-each loop iteration on stream Stream is not inherited Iterable.

Note that, the entry for the streaming version Anagrams 34 Files.lines method used to read the dictionary, and the iterative version of the scanner. Files.lines method is superior scanner, scanner silently swallow all exceptions in the reading file. Ideally, we will use Files.lines in an iterative version. If the API only provides access to the flow sequence, and programmers want to use for-each statement traversal sequence, then they would make such a compromise.

Conversely, if a programmer wants to use the pipeline to handle a flow sequence, then a Iterable only provide an API to make him feel uneasy. JDK does not provide the same adapter, but the adapter is very simple to write this:

// Adapter from Iterable<E> to Stream<E>
public static <E> Stream<E> streamOf(Iterable<E> iterable) {
    return StreamSupport.stream(iterable.spliterator(), false);
}

Collection Iterable interface is a sub-type, and having a stream method, it provides iteration and stream access. Thus, Collection , or appropriate sub-types are generally the best method of public sequence returns return type. Array also uses Arrays.asList and Stream.of method provides a simple iteration and stream access. If the sequence returns small enough to be easily placed in memory, it returns a set of criteria is preferably implemented, such as ArrayList or HashSet. But not in memory to store large sequences, only to return to it as a collection. If the returned sequence may be great but succinctly expressed, consider implementing a special collection.

 

In short, in the preparation method returns a sequence of elements, keep in mind that some users may want to use them as streaming, while others may want an iterative way to deal with them. Try to adapt to the two groups. If it is feasible to return the collection, please do so. If you already have elements in the collection, or the number of elements in the sequence is small enough, you can create a new element, then returns a set of criteria, such as ArrayList . Otherwise, consider implementing a custom collection, just as we do for the power set program.

 

 

48 Section: Stream with caution in parallel

In the mainstream language, Java has been in providing tools to simplify concurrent programming tasks forefront. When released Java in 1996, it built-in support for threads, including synchronization and wait / notify mechanism. Java.util.concurrent library introduced the Java 5, concurrently with the collection and the actuator frame. Java 7 introduced fork-join package, which is a high performance frame for parallel decomposition. Java 8 introduces a stream, by a call to a single parallel method for parallelization. Article 45 program:

// Stream-based program to generate the first 20 Mersenne primes
public static void main(String[] args) {
    primes().map(p -> TWO.pow(p.intValueExact()).subtract(ONE))
        .filter(mersenne -> mersenne.isProbablePrime(50))
        .limit(20)
        .forEach(System.out::println);
}

static Stream<BigInteger> primes() {
    return Stream.iterate(TWO, BigInteger::nextProbablePrime);
}

Stream library does not know how to parallel the pipeline, as well as how to explore the failure, even in the recent environment, if the source is from Stream.iterate, or limit the use of intermediate operations, then it is impossible to improve the performance of parallel pipeline.

Ten million do not arbitrarily parallel Stream pipeline. The performance of its consequences are likely to be catastrophic.

Steam in through parallel performance obtained, preferably by ArrayList, HashMap, HashSet upper and ConcurrentHashMap instance, array, int range and long range. Data structure common to these is that they can be accurately and inexpensively divided into subprograms of any size, which makes the division of work between the parallel threads becomes very easy.

Parallel Stream may not only reduce performance, including active failure, may also lead to wrong results, and unpredictable behavior (such as security failure). Use duct mapper (mappers), the filter (Filters) and other programmers do not meet the specifications of its functional objects parallelization may cause safety problems. For example, Stream collector spread function and the combination function reduce operation must be associated, without disturbing each other, and is stateless.

Remember: Parallel Stream is a rigorous performance optimization. For any optimization of the performance must be tested before and after the change, to make sure worth it. Generally, program Stream pipeline are all parallel in a common pool of fork-join operation. As long as there is a pipeline running abnormal, it will harm the performance of the system in other unrelated parts.

Under appropriate conditions, Stream pipeline to add parallel calls, indeed may be implemented in the near-linear multiplication multicore processors. Some fields such as machine learning and data processing.

As a simple example of the flow conduit effective parallelism, consider this function to calculate π (n), is less than or equal to a prime number n:

// Prime-counting stream pipeline - benefits from parallelization
static long pi(long n) {
    return LongStream.rangeClosed(2, n)
        .mapToObj(BigInteger::valueOf)
        .filter(i -> i.isProbablePrime(50))
        .count();
}

Use this function to calculate π (108) requires 31 seconds. Just add parallel () method call can be shortened to 9.2 seconds.

// Prime-counting stream pipeline - parallel version
static long pi(long n) {
    return LongStream.rangeClosed(2, n)
        .parallel()
        .mapToObj(BigInteger::valueOf)
        .filter(i -> i.isProbablePrime(50))
        .count();
}

In short, do not try to parallel Stream pipeline, unless there is sufficient reason to believe that it can ensure the correctness of the calculations, and the program can run faster.

 

Guess you like

Origin www.cnblogs.com/fyql/p/11582464.html