Lambda Expressions in Java 8

Share Embed Donate


Short Description

The single most important change in Java 8 enables faster, clearer coding and opens the door to functional programming. ...

Description

Lambda Expressions in Java 8 By Cay S. Horstmann, March 25, 2014 7 Comments

The single most important change in Java 8 enables faster, clearer coding and opens the door to functional programming. Here's how it works. Java was designed in the 1990s as an objec t-oriented programming language, when objec t-oriented programming was the princ ipal paradigm for software development. Long before there was objec toriented programming, there were func tional programming languages suc h as Lisp and Sc heme, but their benefits were not muc h apprec iated outside ac ademic c irc les. Rec ently, func tional programming has risen in importanc e bec ause it is well suited for c onc urrent and event-driven (or "reac tive") programming. That doesn't mean that objec t orientation is bad. Instead, the winning strategy is to blend objec t-oriented and func tional programming. This makes sense even if you are not interested in c onc urrenc y. For example, c ollec tion libraries c an be given powerful APIs if the language has a c onvenient syntax for func tional expressions. The princ ipal enhanc ement in Java 8 is the addition of func tional programming c onstruc ts to its objec t-oriented roots. In this artic le, I demonstrate the basic syntax and examine how to use it several important c ontexts. The key points are: A lambda expression is a bloc k of c ode with parameters. Use a lambda expression whenever you want a bloc k of c ode exec uted at a later point in time. Lambda expressions c an be c onverted to func tional interfac es. Lambda expressions c an ac c ess effec tively final variables from the enc losing sc ope. Method and c onstruc tor referenc es refer to methods or c onstruc tors without invoking them. You c an now add default and static methods to interfac es that provide c onc rete implementations. You must resolve any c onflic ts between default methods from multiple interfac es.

Why Lambdas? A lambda expression is a bloc k of c ode that you c an pass around so it c an be exec uted later, just onc e or multiple times. Before getting into the syntax (or even the c urious name), let's step bac k and see where you have used similar c ode bloc ks in Java all along. When you want to do work in a separate thread, you put the work into the runmethod of a Runnable, like this: 1 2 3 4 5 6 7

class Worker implements Runnable {      public void run() {         for (int i = 0; i  {      if (first.length()  second.length()) return 1;      else return 0;   }

?

If a lambda expression has no parameters, you still supply empty parentheses, just as with a parameterless method: 1

() ‐> { for (int i = 0; i  Integer.compare(first.length(), second.length());

Here, the c ompiler c an deduc e that firstand secondmust be strings bec ause the lambda expression is assigned to a string c omparator. (We will have a c loser look at this assignment later.) If a method has a single parameter with inferred type, you c an even omit the parentheses: 1 2 3

EventHandler listener = event ‐>      System.out.println("Thanks for clicking!"); // Instead of (event) -> or (ActionEvent event) ->         

?

You c an add annotations or the finalmodifier to lambda parameters in the same way as for method parameters: 1 2

(final String name) ‐> ...     (@NonNull String name) ‐> ...

?

You never spec ify the resulttype of a lambda expression. It is always inferred from c ontext. For example, the expression 1

(String first, String second) ‐> Integer.compare(first.length(), second.length())

?

c an be used in a c ontext where a result of type intis expec ted. Note that it is illegal for a lambda expression to return a value in some branc hes but not in others. For example, (int x) -> { if (x >= 0) return 1; }is invalid.

Functional Interfaces As we disc ussed, there are many existing interfac es in Java that enc apsulate bloc ks of c ode, suc h as Runnableor Comparator. Lambdas are bac kwards c ompatible with these interfac es. You c an supply a lambda expression whenever an objec t of an interfac e with a single abstrac t method is expec ted. Suc h an interfac e is c alled a func tional interfac e. You may wonder why a func tional interfac e must have a single abstrac t method. Aren't all methods in an interfac e abstrac t? Ac tually, it has always been possible for an interfac e to redec lare methods from the Objectc lass suc h as toStringor clone, and these dec larations do not make the methods abstrac t. (Some interfac es in the Java API redec lare Objectmethods in order to attac h javadoc c omments. Chec k out the Comparator API for an example.) More importantly, as you will see shortly, in Java 8, interfac es c an dec lare non-abstrac t methods.

To demonstrate the c onversion to a func tional interfac e, c onsider the Arrays.sortmethod. Its sec ond parameter requires an instanc e of Comparator, an interfac e with a single method. Simply supply a lambda: 1 2

Arrays.sort(words,      (first, second) ‐> Integer.compare(first.length(), second.length()));

?

Behind the sc enes, the Arrays.sortmethod rec eives an objec t of some c lass that implementsComparator. Invoking the comparemethod on that objec t exec utes the body of the lambda expression. The management of these objec ts and c lasses is c ompletely implementation dependent, and it c an be muc h more effic ient than using traditional inner c lasses. It is best to think of a lambda expression as a func tion, not an objec t, and to ac c ept that it c an be passed to a func tional interfac e. This c onversion to interfac es is what makes lambda expressions so c ompelling. The syntax is short and simple. Here is another example: 1 2

button.setOnAction(event ‐>      System.out.println("Thanks for clicking!"));

?

That's awfully easy to read. In fac t, c onversion to a func tional interfac e is the only thing that you c an do with a lambda expression in Java. In other programming languages that support func tion literals, you c an dec lare func tion types suc h as (String, String) -> int, dec lare variables of those types, and use the variables to save func tion expressions. In Java, you c an't even assign a lambda expression to a variable of type Objectbec ause Objectis not a func tional interfac e. The Java designers dec ided to stic k stric tly with the familiar c onc ept of interfac es instead of adding func tion types to the language. The Java API defines several generic func tional interfac es in the java.util.functionpac kage. One of the interfac es, BiFunction, desc ribes func tions with parameter types Tand Uand return type R. You c an save our string c omparison lambda in a variable of that type: 1 2

BiFunction comp      = (first, second) ‐> Integer.compare(first.length(), second.length());

?

However, that does not help you with sorting. There is no Arrays.sortmethod that wants aBiFunction. If you have used a func tional programming language before, you may find this c urious. But for Java programmers, it's pretty natural. An interfac e suc h as Comparatorhas a spec ific purpose, not just a method with given parameter and return types. Java 8 retains this flavor. When you want to do something with lambda expressions, you still want to keep the purpose of the expression in mind, and have a spec ific func tional interfac e for it. The interfac es in java.util.function are used in several Java 8 APIs, and you will likely see them elsewhere in the future. But keep in mind that you c an equally well c onvert a lambda expression into a func tional interfac e that is a part of whatever API you use today. Also, you c an tag any func tional interfac e with the @FunctionalInterfaceannotation. This has two advantages. The c ompiler c hec ks that the annotated entity is an interfac e with a single abstrac t method. And the javadoc page inc ludes a statement that your interfac e is a func tional interfac e. You are not required to use the annotation. Any interfac e with a single abstrac t method is, by definition, a func tional interfac e. But using the @FunctionalInterfaceannotation is a good idea. Finally, note that c hec ked exc eptions matter when a lambda is c onverted to an instanc e of a func tional interfac e. If the body of a lambda expression c an throw a c hec ked exc eption, that exc eption needs to be dec lared in the abstrac t method of the target interfac e. For example, the following would be an error: 1 2

Runnable sleeper = () ‐> { System.out.println("Zzz"); Thread.sleep(1000); }; // Error: Thread.sleep can throw a checkedInterruptedException     

?

Bec ause the Runnable.run c annot throw any exc eption, this assignment is illegal. To fix the error, you have two c hoic es. You c an c atc h the exc eption in the body of the lambda expression. Or you c an assign the lambda to an interfac e whose single abstrac t method c an throw the exc eption. For example, the callmethod of the Callableinterfac e c an throw any exc eption. Therefore, you c an assign the lambda to a Callable(if you add a statement return null).

Method References Sometimes, there is already a method that c arries out exac tly the ac tion that you'd like to pass on to some other c ode. For example, suppose you simply want to print the eventobjec t whenever a button is c lic ked. Of c ourse, you c ould c all 1

button.setOnAction(event ‐> System.out.println(event));

?

It would be nic er if you c ould just pass the printlnmethod to the setOnActionmethod. Here is how you do that: 1

button.setOnAction(System.out::println);

?

The expression System.out::printlnis a method referenc e that is equivalent to the lambda expression x -> System.out.println(x). As another example, suppose you want to sort strings regardless of letter c ase. You c an pass this method expression: 1

Arrays.sort(strings, String::compareToIgnoreCase)

?

As you c an see from these examples, the ::operator separates the method name from the name of an objec t or c lass. There are three princ ipal c ases:

object::instanceMethod Class::staticMethod Class::instanceMethod In the first two c ases, the method referenc e is equivalent to a lambda expression that supplies the parameters of the method. As already mentioned, System.out::printlnis equivalent to x -> System.out.println(x). Similarly, Math::powis equivalent to (x, y) -> Math.pow(x, y). In the third c ase, the first parameter bec omes the target of the method. For example,String::compareToIgnoreCaseis the same as (x, y) -> x.compareToIgnoreCase(y). When there are multiple overloaded methods with the same name, the c ompiler will try to find from the c ontext whic h one you mean. For example, there are two versions of the Math.maxmethod, one for integers and one for double values. Whic h one gets pic ked depends on the method parameters of the func tional interfac e to whic h Math::maxis c onverted. Just like lambda expressions, method referenc es don't live in isolation. They are always turned into instanc es of func tional interfac es. You c an c apture the thisparameter in a method referenc e. For example, this::equalsis the same as x -> this.equals(x). It is also valid to use super. The method expressionsuper::instanceMethoduses this as the target and invokes the superc lass version of the given method. Here is an artific ial example that shows the mec hanic s: 1 2 3 4 5 6 7 8 9

class Greeter {      public void greet() {         System.out.println("Hello, world!");      }   }       class ConcurrentGreeter extends Greeter {      public void greet() {         Thread t = new Thread(super::greet);

?

10 11 12

        t.start();      }   }

When the thread starts, its Runnableis invoked, and super::greetis exec uted, c alling thegreetmethod of the superc lass. (Note that in an inner c lass, you c an c apture the this referenc e of an enc losing c lass as EnclosingClass.this::methodorEnclosingClass.super::method.)

Constructor References Construc tor referenc es are just like method referenc es, exc ept that the name of the method isnew. For example, Button::newis a referenc e to a Buttonc onstruc tor. Whic h c onstruc tor? It depends on the c ontext. Suppose you have a list of strings. Then, you c an turn it into an array of buttons, by c alling the c onstruc tor on eac h of the strings, with the following invoc ation: 1 2 3

List labels = ...;   Stream stream = labels.stream().map(Button::new);   List buttons = stream.collect(Collectors.toList());

?

Details of the stream, map, and collectmethods are beyond the sc ope of this artic le. For now, what's important is that the mapmethod c alls the Button(String) c onstruc tor for eac hlistelement. There are multiple Buttonc onstruc tors, but the c ompiler pic ks the one with aStringparameter bec ause it infers from the c ontext that the c onstruc tor is c alled with a string. You c an form c onstruc tor referenc es with array types. For example, int[]::newis a c onstruc tor referenc e with one parameter: the length of the array. It is equivalent to the lambda expression x > new int[x]. Array c onstruc tor referenc es are useful to overc ome a limitation of Java. It is not possible to c onstruc t an array of a generic type T. The expression new T[n]is an error sinc e it would be erased to new Object[n]. That is a problem for library authors. For example, suppose we want to have an array of buttons. The Streaminterfac e has a toArraymethod that returns anObjectarray: 1

Object[] buttons = stream.toArray();

?

But that is unsatisfac tory. The user wants an array of buttons, not objec ts. The stream library solves that problem with c onstruc tor referenc es. Pass Button[]::newto the toArraymethod: 1

Button[] buttons = stream.toArray(Button[]::new);

?

The toArraymethod invokes this c onstruc tor to obtain an array of the c orrec t type. Then it fills and returns the array.

Variable Scope Often, you want to be able to ac c ess variables from an enc losing method or c lass in a lambda expression. Consider this example: 1 2 3 4 5 6 7 8 9

public static void repeatMessage(String text, int count) {      Runnable r = () ‐> {         for (int i = 0; i  {         while (count > 0) { // Error: Can't m utate captured variable            count‐‐;             System.out.println(text);            Thread.yield();         }      };      new Thread(r).start();   }

?

There is a reason for this restric tion. Mutating variables in a lambda expression is not thread-safe. Consider a sequenc e of c onc urrent tasks, eac h updating a shared c ounter. 1 2 3 4

int matches = 0;   for (Path p : files)      new Thread(() ‐> { if (p has some property) matches++; }).start(); // Illegal to m utate m atches         

?

If this c ode were legal, it would be very, very bad. The inc rement matches++is not atomic , and there is no way of knowing what would happen if multiple threads exec ute that inc rement c onc urrently. Inner c lasses c an also c apture values from an enc losing sc ope. Before Java 8, inner c lasses were allowed to ac c ess only final loc al variables. This rule has now been relaxed to matc h that for lambda expressions. An inner c lass c an ac c ess any effec tively final loc al variable; that is, any variable whose value does not c hange. Don't c ount on the c ompiler to c atc h all c onc urrent ac c ess errors. The prohibition against mutation holds only for loc al variables. If matchesis an instanc e or static variable of an enc losing c lass, then no error is reported, even though the result is just as undefined. Also, it's perfec tly legal to mutate a shared objec t, even though it is unsound. For example, 1 2 3 4

List matches = new ArrayList();   for (Path p : files)      new Thread(() ‐> { if (p has some property) matches.add(p); }).start(); // Legal to m utate m atches, but unsafe         

?

Note that the variable matchesis effec tively final. (An effec tively final variable is a variable that is never assigned a new value after it has been initialized.) In our c ase, matchesalways refers to the same ArrayListobjec t. However, the objec t is mutated, and that is not thread-safe. If multiple threads c all add, the result is unpredic table. There are safe mec hanisms for c ounting and c ollec ting values c onc urrently. You may want to use streams to c ollec t values with c ertain properties. In other situations, you may want to use threadsafe c ounters and c ollec tions. As with inner c lasses, there is an esc ape hatc h that lets a lambda expression update a c ounter in an enc losing loc al sc ope. Use an array of length 1, like this: 1 2

int[] counter = new int[1];   button.setOnAction(event ‐> counter[0]++);

?

Of c ourse, c ode like this is not thread-safe. For a button c allbac k, that doesn't matter, but in general, you should think twic e before using this tric k. The body of a lambda expression has the same sc ope as a nested bloc k. The same rules for name c onflic ts and shadowing apply. It is illegal to dec lare a parameter or a loc al variable in the lambda that has the same name as a loc al variable. 1 2 3 4

Path first = Paths.get("/usr/bin");   Comparator comp =      (first, second) ‐> Integer.compare(first.length(), second.length()); // Error: Variable first already defined      

?

Inside a method, you c an't have two loc al variables with the same name. Therefore, you c an't introduc e suc h variables in a lambda expression either. When you use the thiskeyword in a lambda expression, you refer to the thisparameter of the method that c reates the lambda. For example, c onsider 1 2 3

public class Application() {      public void doWork() {         Runnable runner = () ‐> { ...; System.out.println(this.toString()); ... };

?

4 5 6

        ...      }   }

The expression this.toString()c alls the toStringmethod of the Applicationobjec t, notthe Runnableinstanc e. There is nothing spec ial about the use of thisin a lambda expression. The sc ope of the lambda expression is nested inside the doWorkmethod, and thishas the same meaning anywhere in that method.

Default Methods Many programming languages integrate func tion expressions with their c ollec tions library. This often leads to c ode that is shorter and easier to understand than the loop equivalent. For example, c onsider a loop: 1 2

for (int i = 0; i 
View more...

Comments

Copyright ©2017 KUPDF Inc.
SUPPORT KUPDF