Author: Cay S. Horstmann
Pub Date: 2014
Size: 17 Mb
Eagerly anticipated by millions of programmers, Java SE 8 is the most important Java update in many years. The addition of lambda expressions (closures) and streams represents the biggest change to Java programming since the introduction of generics and annotations.
Now, with Java SE 8 for the Really Impatient , internationally renowned Java author Cay S. Horstmann concisely introduces Java 8’s most valuable new features (plus a few Java 7 innovations that haven’t gotten the attention they deserve). If you’re an experienced Java programmer, Horstmann’s practical insights and sample code will help you quickly take advantage of these and other Java language and platform improvements. This indispensable guide includes
Coverage of using lambda expressions (closures) to write computation “snippets” that can be passed to utility functions
The brand-new streams API that makes Java collections far more flexible and efficient
Major updates to concurrent programming that make use of lambda expressions (filter/map/reduce) and that provide dramatic performance improvements for shared counters and hash tables
A full chapter with advice on how you can put lambda expressions to work in your own programs
Coverage of the long-awaited introduction of a well-designed date/time/calendar library (JSR 310)
A thorough discussion of many small library changes that make Java programming more productive and enjoyable
This is the first title to cover all of these highly anticipated improvements and is invaluable for anyone who wants to write tomorrow’s most robust, efficient, and secure Java code.
The stream transformations of the preceding sections were stateless. When an element is retrieved from a filtered or mapped stream, the answer does not depend on the previous elements. There are also a few stateful transformations. For example, the distinctmethod returns a stream that yields elements from the original stream, in the same order, except that duplicates are suppressed. The stream must obviously remember the elements that it has already seen.
The sortedmethod must see the entire stream and sort it before it can give out any elements—after all, the smallest one might be the last one. Clearly, you can’t sort an infinite stream.
There are several sortedmethods. One works for streams of Comparableelements, and another accepts a Comparator. Here, we sort strings so that the longest ones come first:
Now that you have seen how to create and transform streams, we will finally get to the most important point—getting answers from the stream data. The methods that we cover in this section are called reductions. They reduce the stream to a value that can be used in your program. Reductions are terminal operations. After a terminal operation has been applied, the stream ceases to be usable.
You have already seen a simple reduction: the count method that returns the number of elements of the stream.
Other simple reductions are maxand minthat return the largest or smallest value. There is a twist—these methods return an Optional<T>value that either wraps the answer or indicates that there is none (because the stream happened to be empty). In the olden days, it was common to return nullin such a situation. But that can lead to null pointer exceptions when an unusual situation arises in an incompletely tested program. In Java 8, the Optionaltype is the preferred way of indicating a missing return value. We discuss the Optional type in detail in the next section. Here is how you can get the maximum of a stream:
This is very, very bad code. The function passed to forEachruns concurrently in multiple threads, updating a shared array. That’s a classic race condition. If you run this program multiple times, you are quite likely to get a different sequence of counts in each run, each of them wrong.
It is your responsibility to ensure that any functions that you pass to parallel stream operations are threadsafe. In our example, you could use an array of AtomicIntegerobjects for the counters (see Exercise 12). Or you could simply use the facilities of the streams library and group strings by length (see Exercise 13).
By default, streams that arise from ordered collections (arrays and lists), from ranges, generators, and iterators, or from calling Stream.sorted, are ordered. Results are accumulated in the order of the original elements, and are entirely predictable.
If you run the same operations twice, you will get exactly the same results. Ordering does not preclude parallelization. For example, when computing stream.map(fun), the stream can be partitioned into nsegments, each of which is concurrently processed. Then the results are reassembled in order.
Some operations can be more effectively parallelized when the ordering requirement is dropped. By calling the Stream.unorderedmethod, you indicate that you are not interested in ordering. One operation that can benefit from this is Stream.distinct. On an ordered stream, distinctretains the first of all equal elements. That impedes parallelization—the thread processing a segment can’t know which elements to discard until the preceding segment has been processed. If it is acceptable to retain any of the unique elements, all segments can be processed
concurrently (using a shared set to track duplicates).
You can also speed up the limitmethod by dropping ordering. If you just want
any nelements from a stream and you don’t care which ones you get, call
Stream<T> sample = stream.parallel().unordered().limit(n);
As discussed in Section 2.10, “Collecting into Maps,” on page 34, merging maps is expensive. For that reason, the Collectors.groupingByConcurrent method uses a shared concurrent map. Clearly, to benefit from parallelism, the order of the map values will not be the same as the stream order. Even on an ordered stream, that collector has a “characteristic” of being unordered, so that it can be used efficiently without having to make the stream unordered. You still need to make the stream parallel, though:
A propertyis an attribute of a class that you can read or write. Commonly, the property is backed by a field, and the property getter and setter simply read and write that field. But the getter and setter can also take other actions, such as reading values from a database or sending out change notifications. In many programming languages, there is convenient syntax for invoking property getters and setters. Typically, using the property on the right-hand side of an assignments calls the getter, and using it on the left-hand side calls the setter.
value = obj.property;
// In many languages (but not Java), this calls the property getter
obj.property = value; // And this calls the property setter
Sadly, Java does not have such syntax. But it has supported properties by convention since Java 1.1. The JavaBeans specification states that a property should be inferred from a getter/setter pair. For example, a class with methods String getText()and void setText(String newValue)is deemed to have a textproperty. The Introspectorand BeanInfoclasses in the java.beanspackage let you enumerate all properties of a class.
The JavaBeans specification also defines bound properties, where objects emit property change events when the setters are invoked. JavaFX does not make use of this part of the specification. Instead, a JavaFX property has a third method, besides the getter and setter, that returns an object implementing the Property interface. For example, a JavaFX text property has a method Property<String> textProperty(). You can attach a listener to the property object. That’s different from old-fashioned JavaBeans. In JavaFX, the property object, not the bean, sends
out notifications. There is a good reason for this change. Implementing bound JavaBeans properties required boilerplate code to add, remove, and fire listeners; in JavaFX it’s much simpler because there are library classes that do all that work.
Let’s see how we can implement a property textin a class Greeting. Here is the simplest way to do that: