• Tidak ada hasil yang ditemukan

Book Java 8 Lambdas EN

N/A
N/A
Jedion Melbin Paucar Cardenas

Academic year: 2023

Membagikan "Book Java 8 Lambdas EN"

Copied!
182
0
0

Teks penuh

If you're interested in reading about lambda expressions and how they can improve your fortunes as a professional developer, read on. If you practice the exercises as you go, you will soon master lambda expressions.

Conventions Used in This Book

Because using lambda expressions is all about abstracting complexity into libraries, I'll introduce a bunch of common library goodies as I go along. The latter chapters are more complex, but they also teach you how to be a more complete programmer who can confidently use lambda expressions in your own designs.

Using Code Examples

Safari® Books Online

How to Contact Us

Acknowledgments

Introduction

Why Did They Need to Change Java Again?

You can write easier to read code - code that spends time expressing the intent of its business logic rather than the mechanics of how it is achieved. Easier to read code is also easier to maintain, more reliable and less error prone.

What Is Functional Programming?

Being able to pass functions around easily also makes it easier to write lazy code that only initializes values ​​when necessary. You might not consider it the perfect example topic, but it is simple, and many of the code examples in this book will be similar to what you might see in your business domain.

Lambda Expressions

Your First Lambda Expression

What happens under the hood is that javac infers the variable event type from its context, here from the addActionListener signature. What this means is that you don't have to write down the type explicitly if it is obvious.

How to Spot a Lambda in a Haystack

Instead of the body of the lambda expression being just an expression, it's a complete block of code, surrounded by curly braces. The target type of a lambda expression is the type of context in which the lambda expression occurs—for example, a local variable to which it is assigned or a method parameter in which it is passed.

Using Values

For example, example 2-7 will fail to compile with the error message: Local variables referenced by the lambda expression must be final or actually final. To avoid such pointless discussion, I will call them "lambda expressions" in this book.

Functional Interfaces

The functional interface here takes one ActionEvent parameter and returns nothing (empty), but functional interfaces can come in many types. From now on, I'll use diagrams to represent the different types of functional interfaces you'll encounter.

Figure 2-1. The ActionListener interface showing an ActionEvent going in and nothing (void) coming out
Figure 2-1. The ActionListener interface showing an ActionEvent going in and nothing (void) coming out

Type Inference

It is still type checked and offers all the security you are used to, but you do not have to explicitly state the types. In both cases, we map the variables to a functional interface so it's easier to see what's going on.

Figure 2-2. The Predicate interface diagram, generating a boolean from an Object
Figure 2-2. The Predicate interface diagram, generating a boolean from an Object

Key Points

In these cases, instead of making a wild guess, it will just stop what it's doing and ask for help in the form of a compilation error. Consequently, our compiler thinks that its arguments and return values ​​are all instances of java.lang.Ob ject.

Exercises

Streams

From External Iteration to Internal Iteration

You will need to rewrite each for loop individually to make them work in parallel. Filtering in this case means "keep only items that pass a test." The test is defined by a function which returns either true or false depending on whether the artist is from London.

Figure 3-1. External iteration
Figure 3-1. External iteration

What’s Actually Going On

So, if you ran Example 3-6 with the members of The Beatles as your list of artists, you would see Example 3-7 printed on your command line. This is how our counting example works, but it is the simplest case: only two operations.

Common Stream Operations

It is very easy to find out whether an operation is eager or lazy: see what it yields. If it gives you a Stream back, it's lazy; if it gives you back some other value or emptiness, then it is greedy.

Example 3-9 is the same example of converting a list of strings to their uppercase equivalents using the stream frames. The lambda expression passed to the map both takes a string as its only argument and returns a string.

Figure 3-3. The map operation
Figure 3-3. The map operation

If you're refactoring the legacy code, the presence of an if statement in the middle of a for loop is a pretty strong indication that you really want to use filter. Because this function does the same job as the if statement, it must return either true or false for a given value.

The stream to the filter has the elements of the stream before it, which evaluated to true. In each case, we replace the list with a stream using the stream method, and flatmap does the rest.

It is now possible for max to be called on an empty stream so that it returns what is known as an optional value. If our Stream is empty, it will not exist; if it is not empty, it will be.

A Common Pattern Appears

The accumulator starts with an initial Value and is then folded along with each element of the list by calling combine. To find the shortest value, our harvester returned the shortest trace of from the current element and the accumulator.

In the original example we used the first element in the list as our initialValue, but that doesn't have to be the case. In the imperative version we can see that the accumulator is a variable that we update on each loop iteration.

Figure 3-8. Implementing addition using the reduce operation
Figure 3-8. Implementing addition using the reduce operation

Putting Operations Together

It also encourages users of your domain class to write code in a more modern Java 8 style. Over time, you can rewrite your old code until you eventually remove all the getters that return a list or set.

Refactoring Legacy Code

We're really doing three things here: find only tracks over a minute long, get their names, and add their names to our Name Set. In summary, we took a piece of legacy code and refactored it to use idiomatic streams.

Multiple Stream Calls

It is less efficient because it requires eagerly creating new collection objects at each intermediate step. Of course, if you're writing your first few Stream-based examples, it's perfectly normal to write code that looks a bit like this.

Higher-Order Functions

Good Use of Lambda Expressions

This is actually a deliberate choice on behalf of the designers: an attempt to encourage people to use lambda expressions to capture values ​​rather than capture variables. When passing lambda expressions into the higher-order functions on the Stream interface, you should try to avoid side effects.

Advanced Exercises

Libraries

Using Lambda Expressions in Code

What we actually want to be able to do is pass a lambda expression that generates a string to use as a message. Calling the get() method in this example is equivalent to calling the lambda expression that was passed to the method to be called.

Primitives

If the return type is a primitive, the interface is prefixed with To and the primitive type, as in ToL ongFunction (shown in Figure 4-1). It is a good idea to use the primitive specialized functions whenever possible for the performance benefits.

Figure 4-2. LongFunction
Figure 4-2. LongFunction

Overload Resolution

The lambda expression passed to the overloaded method is compatible with both the normal predicate and the IntPredicate. If there is only one possible target type, the lambda expression infers the type from the corresponding argument to the functional interface.

FunctionalInterface

Binary Interface Compatibility

Here's the guarantee that if you have source code in Java 1-7, it will compile in Java 8. The Stream method was added to the Collection interface in Java 8, which means that any class that implements Collection must also have this method on that.

Default Methods

Unfortunately, this change still breaks binary compatibility because it means that any class outside the JDK that implements Collection—for example, MyCustomList—must also have implemented the stream method. Aside from the addition of a new keyword, standard methods also have slightly different inheritance rules than regular methods.

Default Methods and Subclassing

OverridingParent is chosen over OverridingChild (code in Example 4-17), being a more specific type because it is a concrete method from a class rather than a default method (see Figure 4-5). If the default method wasn't guaranteed to be overridden by this addAll method, we could break the existing implementation.

Figure 4-4. A diagram showing the inheritance hierarchy at this point
Figure 4-4. A diagram showing the inheritance hierarchy at this point

Multiple Inheritance

Since it is not clear to Java which method to inherit, this will only cause the MusicalCarriage compile-error class to inherit unrelated rock() defaults from the Carriage and Jukebox types. Previously, super acted as a reference to the parent class, but by using the variant InterfaceName.super it is possible to specify a method from an in‐.

The Three Rules

If we have a situation where two interfaces compete to provide a standard method and one interface extends the other, the subclass wins. If the previous two lines don't give us an answer, the subclass must implement the method or declare it abstractly.

Tradeoffs

Static Methods on Interfaces

For example, if you want to create a simple value stream, you would expect the method to be located on the stream. Previously, this was impossible, and adding a very demanding API in terms of Stream finally motivated the addition of static methods on interfaces.

Optional

Both are shown in Example 4-23, along with the use of the isPre sent method (which indicates whether the Optional has a value). Your assignment is to restructure the getArtist method to return an Optional.

Open Exercises

Advanced Collections and Collectors

Another thing to note here is that method references automatically support multiple parameters, as long as you have the right functional interface. We'll be using method references where appropriate from this point on, so you'll be seeing a lot more examples soon.

Element Ordering

If there is no meeting order on the input stream, there is no meeting order on the output stream. This can cause unexpected behavior, for example forEach makes no guarantees to encounter order if you use parallel streams.

Enter the Collector

However, most operations, such as filtering, mapping, and minification, can operate very efficiently on ordered flows. If you need an order guarantee in these situations, then forEachOr dered is your friend.

Into Other Collections

Later in this book, I'll talk about how you can use the streams library to perform data-parallel operations; collecting the results of parallel operations may require a different type of set to be produced than if there was no requirement for thread safety. For example, maybe you want to use a TreeSet instead of letting the framework determine what type of Set implementation you get.

To Values

You can do this by using the toCollection collector, which takes a function to build the collection as its argument (see Example 5-5). In fact, there is also a group of collectors that provide similar functionality, along the lines of averagingInt.

Partitioning the Data

There are also overloaded operations for double and long types, which allow you to convert ele‐. We can use these features to separate bands (artists with more than one member) from solo artists.

Grouping the Data

Strings

Our classifier is a feature - the same type we use for the normal map operation. Here we use a map to extract the artist names and then collect the stream using Collectors.joining.

Composing Collectors

We already know that we can group our albums by main artist using the groupingBy collector, but that would output a Map>. We can't just use the map method on streams because this list is created by the GroupingBy collector.

Refactoring and Custom Collectors

Now that we delegate all the String joining behavior to a custom col‐. We've already wrapped our mutable container over a stream of values, but it's not quite the final value we want.

Figure 5-3. Supplier
Figure 5-3. Supplier

Reduction as a Collector

It is entirely possible that the final value you collect is the same as con‐. In fact, this is what happens when the final value you collect is a collection, such as with the toList collector.

Collection Niceties

Thankfully, a new forEach method has been introduced that takes a BiConsumer (two values ​​in, nothing out) and produces easier-to-read code through internal iteration. Hint: you might want to start with the public class GroupingBy implements Collector>, Map>>.

Data Parallelism

Parallelism Versus Concurrency

The goal of parallelism is to reduce the execution time of a given task by dividing it into smaller components and executing them in parallel. Data parallelism is often contrasted with task parallelism, where each individual thread of execution can perform a completely different task.

Figure 6-1. Comparison of concurrency and parallelism
Figure 6-1. Comparison of concurrency and parallelism

Why Is Parallelism Important?

Parallel Stream Operations

Of course, it's important to make good use of parallelism in order to get the most out of your hardware, but the kind of data parallelism we get from the streams library is just one form. It is possible to get different performance numbers based on how you wrote your code and how many cores are available.

Simulations

The size of the input current is not the only factor to consider when deciding whether there is a parallel accelerator. It is also a good use for parallelism, as it is an implementation that gets good parallel acceleration.

Caveats

Performance

This involves mapping each integer to an int and also adding a quarter of the values ​​in each thread. You can't easily break these down with the perfect amount of balance, but most of the time it is possible to do so.

Figure 6-2 shows how this might apply to Example 6-6.
Figure 6-2 shows how this might apply to Example 6-6.

Parallel Array Operations

The code in Example 6-11 multiplies each number in a list together and multiplies the result by 5. The code in Example 6-12 also calculates the sum of the squares of the numbers in a list.

Testing, Debugging, and Refactoring

Lambda Refactoring Candidates

In, Out, In, Out, Shake It All About

The Lonely Override

It also makes it much clearer to anyone trying to read the code what its purpose is. If you try to read aloud the words in the second example, you can easily hear what he is saying.

Behavioral Write Everything Twice

We will count the number of musicians, the number of songs and the duration of our order. As shown in Figure 7-1, it is parameterized by the argument type, so we use ToLongFunction.

Figure 7-1. ToLongFunction
Figure 7-1. ToLongFunction

Unit Testing Lambda Expressions

All the lambda expression in this code does is call a core Java method directly. You really want to test for behavior specific to that code, but it's in a lambda expression and you have no way to reference it.

Using Lambda Expressions in Test Doubles

Lazy Evaluation Versus Debugging

Logging and Printing

However, this way has the disadvantage that we cannot continue to work on that stream, because streams can only be used once.

The Solution: peek

Midstream Breakpoints

In this case, the view can simply have an empty body in which you set a breakpoint. If you want to unit test a lambda expression of any complexity, cast it in a regular method.

Design and Architectural Principles

I will try to present some well-reasoned principles and patterns by which you can compose maintenance-free and reliable software - not just to use the shiny new JDK libraries, but to use lambda expressions in your own domain architecture and applications.

Lambda-Enabled Design Patterns

Command Pattern

Let's look at a concrete example of the command pattern and see how it is enhanced with lambda expressions. In fact, we can do this even better by recognizing that each of these lambda expressions executes a single method call.

Figure 8-1. The command pattern
Figure 8-1. The command pattern

Strategy Pattern

If we have a traditional implementation of the strategy pattern, then we can write client code that creates a new compressor with whatever strategy we want (Example 8-13). In this case, we can remove each of the concrete strategy implementations and refer to a method that implements the algorithm.

Figure 8-2. The strategy pattern
Figure 8-2. The strategy pattern

Observer Pattern

We have two concrete implementations of the LandingObserver class that represent the aliens' (example 8-17) and NASA's (example 8-18) views of the landing event. One thing to think about with both the observer and strategy patterns is that whether to go the lambda design route or the class route depends very much on the complexity of the strategy or observer code to be implemented.

Template Method Pattern

With lambda expressions and method references, we can think about the template method pattern in a different light and implement it differently as well. We can explicitly use a lambda expression to place the implementation in these classes, or use a method reference to the current class.

Lambda-Enabled Domain-Specific Languages

A specification is similar to a test method, an expectation is similar to an assertion, and a set is similar to a test class.

A DSL in Java

We can then describe the expectations about our object's behavior, all of which start with the prefix 'expect.that'.

How We Got There

You may have noticed one more detail that I've ignored so far that has nothing to do with lambda expressions. I've covered the parts of the DSL that interact with lambda expressions in order to give you a taste of how to implement this type of DSL.

Evaluation

There is a lot more work involved in implementing a complete BDD framework, but the purpose of this section is simply to show you how to use lambda expressions to create more fluid domain-specific languages.

Lambda-Enabled SOLID Principles

However, I will look at how the three principles can be applied in the context of lambda expressions. In the context of Java 8, some principles can be extended beyond their original limitations.

The Single Responsibility Principle

Each of the principles corresponds to a set of potential code smells that may exist in your code, and they offer a way out of the problems they cause. If we want to speed up the time it takes to perform this operation at the expense of using more CPU resources, we can use the parallelStream method without changing any of the other code (see Example 8-37).

The Open/Closed Principle

It implements the open/closed principle because we can get new behavior from ThreadLocal without changing it. Immutable objects implement the open/closed principle in the sense that because their internal state cannot be modified, it is safe to add new methods to them.

The Dependency Inversion Principle

We can use a Stream as the abstraction we want to depend on instead of a file. We also want to allow a pass in the function that throws our domain exception if there is a problem with the file.

Further Reading

Lambda-Enabled Concurrency

Why Use Nonblocking I/O?

So far I've avoided showing any code to demonstrate the ideas, because the concept of blocking vs. non-blocking I/O can be implemented in a number of different ways in terms of the API. The original version of NIO uses the concept of a selector, which allows a thread to manage multiple communication channels, such as the network socket used to write to your chat client.

Callbacks

The advantage of this approach is that the application does not control the threading model - the Vert.x framework. We will send this text message to the receiving user's chat client, which is done by writing the message to the TCP connection.

Figure 9-1. Event bus sending
Figure 9-1. Event bus sending

Message Passing Architectures

When it comes to the handler, we want to perform the same action we did earlier when we recorded dispatches: pass that message to the client (Example 9-6). We are looking at concurrency situations where we want to have many more units of I/O work, such as connected chat clients, than we have threads running in parallel.

The Pyramid of Doom

We have a withModule method that deploys the current Vert.x module, runs some code, and turns off the module. We also have a withConnection method that connects to the ChatVerticle and then disconnects when it's done with it.

Futures

Each of these login actions returns a Future object with the login information. The Future interface is generic, so Future can be read as an IOU for a Credentials object.

Completable Futures

At this point, it's worth reminding yourself that, just like with the Streams API, we don't actually do things; we are building a recipe that says how to do things. As shown in Example 9-12, to tell CompletableFuture that it is ready, you call the complete method.

Figure 9-4. A completable future is an IOU that can be processed by handlers
Figure 9-4. A completable future is an IOU that can be processed by handlers

Reactive Programming

Therefore, we need to replace each name with an Observable that represents one or more artists. Since an observable stream represents events, we need the ability to push multiple values.

When and Where

The first part of this exercise is to refactor the blocking return code to use a callback interface. Your mission, should you choose to accept it, is to modify BlockingArtistAnalyzer to implement ArtistAnalyzer (Example 9-19).

Moving Forward

If you already have your unit tests running under the Jenkins CI system, then it is very easy to run the same build under multiple Java versions. This could be an open source project you're interested in, or maybe even your working product if the trial deployment went well.

Index

About the Author

Colophon

Gambar

Table 2-1. Important functional interfaces in Java Interface name Arguments Returns Example
Figure 2-1. The ActionListener interface showing an ActionEvent going in and nothing (void) coming out
Figure 2-2. The Predicate interface diagram, generating a boolean from an Object
Figure 3-1. External iteration
+7

Referensi

Dokumen terkait

Just like any home based business, it takes perseverance and diligence to bring in the extra income you are looking for.. Many programs on the Internet promise you big money for just

To allow a virtual function declaration to act as an interface to functions defined in derived classes, the argument types specified for a function in a derived class cannot differ

However, there is a strong tendency that metaphor class with negative nuance (like +DESTRUCTION, +WAR, +FIGHT) and the presence of an argument that takes

The complete.cases() function takes a data frame or table as its argument and returns a boolean vector with TRUE for rows that have no missing values and FALSE otherwise:.

Types include static method reference, instance method of particular object, super method of particular object, and instance method of arbitrary object of particular type.

1.4 Generic Methods and Varargs Here is a method that accepts an array of any type and converts it to a list: class Lists { public static List toListT[] arr { List list = new

A Better Way As observant Java programmers, the minute we set our eyes on this code we’d quickly turn it into something more concise and easier to read, like this:

We discuss what it takes to build a cloud network, the evolution from the managed service pro- vider model to cloud computing and SaaS and from single-purpose archi- tectures to