• Tidak ada hasil yang ditemukan

Book java 8 in action

N/A
N/A
Jedion Melbin Paucar Cardenas

Academic year: 2023

Membagikan "Book java 8 in action"

Copied!
497
0
0

Teks penuh

For online information and to order this and other Manning books, visit www.manning.com. Recognizing the importance of preservation, it is Manning's policy to print the books we publish on acid-free paper, and we make every effort to do so.

Fundamentals

Java 8: why should you care?

Why is Java still changing?

  • Java’s place in the programming language ecosystem
  • Stream processing
  • Passing code to methods with behavior parameterization
  • Parallelism and shared mutable data
  • Java needs to evolve

We introduce them in a slightly different order than the rest of the book to allow for a Unix-based analogy and to expose the "here's why this is needed" dependencies in Java 8's new parallelism for multicore. We now introduce the new concepts in Java 8 one by one, highlighting in more detail how the chapters cover these concepts.

Figure 1.1. Programming languages ecosystem and climate change
Figure 1.1. Programming languages ecosystem and climate change

Functions in Java

  • Methods and lambdas as first-class citizens

Values, as mentioned earlier, are first-class citizens of Java, but various other Java concepts, such as methods and classes, exemplify second-class citizens. Experiments in other languages ​​such as Scala and Groovy found that allowing concepts such as methods to be used as first-class values ​​made programming easier by adding to the set of tools available to programmers.

Lambdas—anonymous functions

  • Passing code: an example
  • From passing methods to lambdas
  • Streams
    • Multithreading is difficult
  • Default methods
  • Other good ideas from functional programming
  • Summary
  • Coping with changing requirements
    • First attempt: filtering green apples
    • Second attempt: parameterizing the color
    • Third attempt: filtering with every attribute you can think of
  • Behavior parameterization
    • Fourth attempt: filtering by abstract criteria

Try to imagine how you could code this algorithm in Java (even for an index smaller than Google's, you would have to use all the cores in your computer). Here, Scala's syntax expr match corresponds to Java's switch (expr); don't worry about this code for now—you'll read more about pattern matching in Chapter 14.

Figure 1.5. A possible problem with two threads trying to add to a shared sum variable
Figure 1.5. A possible problem with two threads trying to add to a shared sum variable

Passing code/behavior

Multiple behaviors, one parameter

  • Tackling verbosity
    • Anonymous classes
    • Fifth attempt: using an anonymous class
    • Sixth attempt: using a lambda expression
    • Seventh attempt: abstracting over List type
  • Real-world examples
    • Sorting with a Comparator
    • Executing a block of code with Runnable
    • GUI event handling
  • Summary
  • Lambda expressions
    • Lambdas in a nutshell
    • Where and how to use lambdas

For example, by using a lambda expression, you can create a custom comparator object in a more concise way. We explain in the next section exactly where and how you can use lambda expressions.

Figure 2.3. Parameterizing the behavior of filterApples and passing different filter strategies
Figure 2.3. Parameterizing the behavior of filterApples and passing different filter strategies

Note

Function descriptor

Predicate T -> boolean IntPredicate, LongPredicate, DoublePredicate Consumer T -> void IntConsumer, LongConsumer, DoubleConsumer. Dobavitelj () -> T BooleanSupplier, IntSupplier, LongSupplier, DoubleSupplier UnaryOperator T -> T IntUnaryOperator, LongUnaryOperator, DoubleUnaryOperator BinaryOperator (T, T) -> T IntBinaryOperator, LongBinaryOperator, DoubleBinaryOperator.

Putting lambdas into practice: the execute around pattern

  • Step 1: Remember behavior parameterization
  • Step 2: Use a functional interface to pass behaviors
  • Step 3: Execute a behavior!
  • Step 4: Pass lambdas

You can now reuse the processFile method and process files in different ways by passing different lambdas. So far, we've shown how you can use functional interfaces to pass lambdas.

Figure 3.2. Tasks A and B are surrounded by the same redundant code responsible for preparation/cleanup.
Figure 3.2. Tasks A and B are surrounded by the same redundant code responsible for preparation/cleanup.

Using functional interfaces

  • Predicate
  • Consumer
  • Function

The interface java.util.function.Consumer defines an abstract method called accept that takes an object of generic type T and returns no result (invalid). You can use this interface when you want to access an object of type T and perform certain operations on it.

Primitive specializations

Type checking, type inference, and restrictions

  • Type checking
  • Same lambda, different functional interfaces
  • Type inference
  • Using local variables

The type expected for the lambda expression within the context (for example, a method parameter it is passed to or a local variable to which it is assigned) is called the target type. But lambda expressions may also use free variables (variables that are not parameters and are defined in an outer scope), just as anonymous classes can.

Figure 3.4. Deconstructing the type-checking process of a lambda expression
Figure 3.4. Deconstructing the type-checking process of a lambda expression

Restrictions on local variables

Method references

  • In a nutshell

For example, Apple::getWeight is a method reference to the getWeight method defined in the Apple class. You can think of method references as syntactic sugar for lambdas that only refer to a single method because you write less to express the same thing.

Recipe for constructing method references

Constructor references

If you have a constructor with an Apple signature (integer weight), it fits the signature of the Function interface, so you can do this. If you have a two-argument constructor, Apple(String color, integer weight), it fits the signature of the BiFunction interface, so you can do this.

Putting lambdas and method references into practice!

  • Step 1: Pass code
  • Step 2: Use an anonymous class
  • Step 3: Use lambda expressions
  • Step 4: Use method references

The signature of the abstract method (called the function descriptor) can describe the signature of a lambda expression. We explained that the Java compiler could infer the type of the parameters of a lambda expression using the context in which the lambda appears.

Useful methods to compose lambda expressions

  • Composing Comparators

It's not just because it's shorter; it's also clear what it means, and the code reads like the problem statement "sort inventory by comparing the weight of apples".

Reversed order

So you can simply modify the previous example to sort the apples by decreasing the weight by reusing the original Comparator:.

Chaining Comparators

Composing Predicates

From simpler lambda expressions, you can imagine more complicated lambda expressions that still read like the problem statement.

Composing Functions

Similar ideas from mathematics

  • Integration
  • Connecting to Java 8 lambdas

As we mentioned earlier, Java 8 uses the notation (double x) -> x+10 (a lambda expression) for exactly this purpose; hence you can write. As a side note, it's a bit of a shame that you have to write f.apply(a) instead of just f(a) as in math, but Java just can't escape the view that everything is an object - rather than the idea that a function is truly independent.

Figure 3.8. Area under the function f(x) = x + 10 for x from 3 to 7
Figure 3.8. Area under the function f(x) = x + 10 for x from 3 to 7

Summary

Functional-style data processing

Introducing streams

  • What are streams?
  • Getting started with streams
  • Streams vs. collections
    • Traversable only once
    • External vs. internal iteration
  • Stream operations
    • Intermediate operations
    • Terminal operations
    • Working with streams
  • Summary

In the next chapter, we will explore in detail the stream operations available for expressing complex data processing queries. Note that this is an incomplete list of operations provided by the Streams API; you will see more of them in the next chapter.

Figure 4.1. Chaining stream operations forming a stream pipeline
Figure 4.1. Chaining stream operations forming a stream pipeline

Working with streams

  • Filtering and slicing
    • Filtering with a predicate
    • Filtering unique elements
    • Truncating a stream
    • Skipping elements
  • Mapping
    • Applying a function to each element of a stream
    • Flattening streams

If you have a list of words, you would like to return a list of the number of characters for each word. So the stream returned by the map method is actually of type Stream.

Figure 5.1. Filtering a stream with a predicate
Figure 5.1. Filtering a stream with a predicate

Using flatMap

Finding and matching

  • Checking to see if a predicate matches at least one element
  • Checking to see if a predicate matches all elements

The allMatch method works similarly to anyMatch, but will check whether all the elements of the stream match the given predicate. For example, you can use it to find out if the menu is healthy (that is, all dishes are under 1000 calories):

Finding an element

Optional in a nutshell

Finding the first element

If you don't care which element is returned, use findAny because it's less restrictive when using parallel streams.

Reducing

  • Summing the elements

Next, the lambda is called again with the accumulated value and the next element of the stream, 5, which produces the new accumulated value, 9. Next, the lambda is called again with the accumulated value and the next element, 3, which produces 12.

Figure 5.7. Using reduce to sum the numbers in a stream
Figure 5.7. Using reduce to sum the numbers in a stream

No initial value

Maximum and minimum

If you add the necessary synchronization, you'll likely find that thread contention robs you of all the performance parallelism is supposed to give you. It certainly is in many applications, as you saw in the previous examples.

Figure 5.8. A reduce operation—calculating the maximum
Figure 5.8. A reduce operation—calculating the maximum

Putting it all into practice

  • The domain: Traders and Transactions
  • Solutions

We now provide the solutions in the following code listings, so that you can verify your understanding of what you have learned so far.

Numeric streams

  • Primitive stream specializations

Each of these interfaces brings new methods to perform common numerical reductions, such as sum to calculate the sum of a numerical stream and max to find the maximum element. The thing to remember is that these specializations are not more complexity around streams, but instead more complexity caused by boxing - the (efficiency-based) difference between int and Integer and so on.

Mapping to a numeric stream

Converting back to a stream of objects

In the next section, you'll learn that framing is especially useful when you're dealing with numeric ranges that need to be bounded into a general stream.

Default values: OptionalInt

Numeric ranges

It produces a stream so you can chain the filter method to select only even numbers. Because count is a terminal operation, it will process the stream and return the result 50, which is the number of even numbers from 1 to 100, inclusive.

Putting numerical streams into practice: Pythagorean triples

Note that if you used IntStream.range(1, 100) instead, the result would be 49 even numbers, because the range is exclusive.

Pythagorean triple

Representing a triple

Filtering good combinations

Generating tuples

Generating b values

Generating a values

The FlatMap method performs the mapping and also flattens all the generated streams of triples into a single stream.

Running the code

Can you do better?

Building streams

  • Streams from values
  • Streams from arrays
  • Streams from files
  • Streams from functions: creating infinite streams!

You can create a stream with clear values ​​using the static Stream.of method, which can take any number of parameters. For example, in the following code you create a stream of strings directly using Stream.of.

Iterate

As a result, the iteration method produces a stream of all even numbers: the first element of the stream is the initial value 0. Your task is to use the iteration method to generate the first 20 elements of the sequence of Fibonacci tuples.

Generate

Summary

You can combine all the elements of a stream iteratively to produce a result using the reduce method, for example, to calculate the sum or find the maximum of a stream. Some operations such as sort and distinct also preserve state because they must store all elements of a stream before returning a new stream.

Collecting data with streams

  • Collectors in a nutshell
    • Collectors as advanced reductions
    • Predefined collectors
  • Reducing and summarizing
    • Finding maximum and minimum in a stream of values
    • Summarization
    • Joining Strings
    • Generalized summarization with reduction

Finally, in Section 6.5 you will learn more about the Collector interface before exploring (Section 6.6) how you can create your own custom collectors to use in those cases not covered by the factory methods of the Collectors class. For the remainder of this chapter, we assume that you have imported all of the static factory methods from the Collectors class.

Figure 6.1. The reduction process grouping the transactions by currency
Figure 6.1. The reduction process grouping the transactions by currency

Collection framework flexibility: doing the same operation in different ways

But here it just means that the type of the collector's accumulator is unknown, or in other words, the accumulator itself can be of any type. We have used it here to report exactly the signature of the method as originally defined in the Collector class, but in the rest of the chapter we avoid any wildcard notation to keep the discussion as simple as possible.

Choosing the best solution for your situation

Grouping

  • Multilevel grouping
  • Collecting data in subgroups

Here you pass a function (expressed in the form of a method reference) to the groupingBy method that extracts the corresponding Dish.Type for each Dish in the stream. In the previous section, you saw that it is possible to pass a second groupBy collector to the outer one to achieve a multilevel grouping.

Figure 6.4. Classification of an item in the stream during the grouping process
Figure 6.4. Classification of an item in the stream during the grouping process

Adapting the collector result to a different type

The collectors are represented by the dashed lines, sogroupingBy is the outermost one and groups the menu flow into three sub-flows according to the different dish types. The reduction operation on the partial streams is then performed by the reducing collector, but it.

Figure 6.6. Combining the effect of multiple collectors by nesting one inside the other
Figure 6.6. Combining the effect of multiple collectors by nesting one inside the other

Other examples of collectors used in conjunction with groupingBy

Partitioning

  • Advantages of partitioning
  • Partitioning numbers into prime and nonprime

Partitioning has the advantage of keeping both lists of the stream elements for which applying the partitioning function returns true or false. Reduce the stream to a single value, starting from an initial value used as an accumulator, and iteratively combine it with each element of the stream using a BinaryOperator.

The Collector interface

  • Making sense of the methods declared by Collector interface

It is the accumulator type, the object on which the partial result will be accumulated during the aggregation process. R is the object type (usually, but not always, collection) that results from the collection operation.

Making a new result container: the supplier method

For example, you can implement a ToListCollector class that collects all elements of a Stream into a List that has the following signature. When we do this, you'll notice that each of the first four methods returns a function that will be called by the collection method, while the fifth, characteristics, provides an array of characteristics that is a list of tips used by the collection method. know which optimizations (for example, parallelization) it is allowed to use when performing the reduce operation.

Adding an element to a result container: the accumulator method

Applying the final transformation to the result container: the finisher method

These first three methods are enough to perform a successive reduction of the current which, at least from a logical point of view, can proceed as figure 6.7. The implementation details are a bit trickier in practice due to both the lazy nature of the stream, which may require a pipeline of other intermediate operations to run before the collect operation, and the possibility, in theory, to parallelize the reduction to execute.

Merging two result containers: the combiner method

At this point, all substreams can be processed in parallel, each using the sequential reduction algorithm shown in Figure 6.7. This is done by combining the results corresponding to the substreams associated with each division of the original stream.

Figure 6.8. Parallelizing the reduction process using the combiner
Figure 6.8. Parallelizing the reduction process using the combiner

Characteristics method

Putting them all together

Finally, all partial results are combined pairwise using the function returned by the combiner. Note that this implementation is not the same as the one returned by the Collectors .toList method, but differs only in some minor optimizations.

Performing a custom collect without creating a Collector implementation

  • Developing your own collector for better performance
    • Divide only by prime numbers
  • Defining the Collector class signature
  • Implementing the reduction process
  • Making the collector work in parallel (if possible)
  • The finisher method and the collector’s characteristic method
    • Comparing collectors’ performances

This means that when you test whether a given candidate number is prime or not, you do not have access to the list of the other primes found so far. In this method, you call the isPrime method and pass to it (along with the number for which you want to test whether it is prime or not) the list of the primes found so far (these are the values ​​indexed by the true key in the accumulating card).

Gambar

Figure 1.1. Programming languages ecosystem and climate change
Figure 1.4. Passing the method reference File::isHidden to the method
Figure 1.5. A possible problem with two threads trying to add to a shared sum variable
Figure 1.6. Forking filter onto two CPUs and joining the result
+7

Referensi

Dokumen terkait