`

java8 Lambda expressions

    博客分类:
  • Java
 
阅读更多

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 object-oriented programming language, when object-oriented programming was the principal paradigm for software development. Long before there was object-oriented programming, there were functional programming languages such as Lisp and Scheme, but their benefits were not much appreciated outside academic circles. Recently, functional programming has risen in importance because it is well suited for concurrent and event-driven (or "reactive") programming. That doesn't mean that object orientation is bad. Instead, the winning strategy is to blend object-oriented and functional programming. This makes sense even if you are not interested in concurrency. For example, collection libraries can be given powerful APIs if the language has a convenient syntax for functional expressions.

The principal enhancement in Java 8 is the addition of functional programming constructs to its object-oriented roots. In this article, I demonstrate the basic syntax and examine how to use it several important contexts. The key points are:

<!--[if !supportLists]-->·        <!--[endif]-->A lambda expression is a block of code with parameters.

<!--[if !supportLists]-->·        <!--[endif]-->Use a lambda expression whenever you want a block of code executed at a later point in time.

<!--[if !supportLists]-->·        <!--[endif]-->Lambda expressions can be converted to functional interfaces.

<!--[if !supportLists]-->·        <!--[endif]-->Lambda expressions can access effectively final variables from the enclosing scope.

<!--[if !supportLists]-->·        <!--[endif]-->Method and constructor references refer to methods or constructors without invoking them.

<!--[if !supportLists]-->·        <!--[endif]-->You can now add default and static methods to interfaces that provide concrete implementations.

<!--[if !supportLists]-->·        <!--[endif]-->You must resolve any conflicts between default methods from multiple interfaces.

Why Lambdas?

A lambda expression is a block of code that you can pass around so it can be executed later, just once or multiple times. Before getting into the syntax (or even the curious name), let's step back and see where you have used similar code blocks in Java all along.

When you want to do work in a separate thread, you put the work into the run method of a Runnable, like this:

?

1

2

3

4

5

6

7

classWorker implementsRunnable {

     publicvoidrun() {

        for(inti = 0; i < 1000; i++)

           doWork();

     }

     ...

  }

Then, when you want to execute this code, you construct an instance of the Worker class. You can then submit the instance to a thread pool, or keep it simple and start a new thread:

?

1

2

Worker w = newWorker();

  newThread(w).start();

The key point is that the run method contains code that you want to execute in a separate thread.

Consider sorting with a custom comparator. If you want to sort strings by length instead of the default dictionary order, you can pass a Comparator object to the sort method:

?

1

2

3

4

5

6

7

classLengthComparator implementsComparator<String> {

     publicintcompare(String first, String second) {

        returnInteger.compare(first.length(), second.length());

     }

  }

   

Arrays.sort(strings, newLengthComparator());

The sort method keeps calling the compare method, rearranging the elements if they are out of order, until the array is sorted. You give the sort method a snippet of code needed to compare elements, and that code is integrated into the rest of the sorting logic, which you'd probably not care to reimplement. Note that the call Integer.compare(x, y) returns zero if x and y are equal, a negative number if x < y, and a positive number if  x > y. This static method was added to Java 7. You shouldn't compute x - y to compare x and y because that computation can overflow for large operands of opposite sign.

As another example for deferred execution, consider a button callback. You put the callback action into a method of a class implementing the listener interface, construct an instance, and register the instance with the button. That happens so often that many programmers use the "anonymous instance of anonymous class" syntax:

?

1

2

3

4

5

button.setOnAction(newEventHandler<ActionEvent>() {

     publicvoidhandle(ActionEvent event) {

        System.out.println("Thanks for clicking!");

     }

  });

What matters is the code inside the handle method. That code is executed whenever the button is clicked.

Since Java 8 positions JavaFX as the successor to the Swing GUI toolkit, I use JavaFX in these examples. (See Eric Bruno's posts for more information on JavaFX. — Ed.) The details don't matter. In every user interface toolkit, be it Swing, JavaFX, or Android, you give a button some code that you want to run when the button is clicked.

In all three examples, you saw the same approach. A block of code was passed to someone — a thread pool, a sort method, or a button. The code was called at some later time.

Up to now, giving someone a block of code hasn't been easy in Java. You couldn't just pass code blocks around. Java is an object-oriented language, so you had to construct an object belonging to a class that has a method with the desired code.

In other languages, it is possible to work with blocks of code directly. The Java designers have resisted adding this feature for a long time. After all, a great strength of Java is its simplicity and consistency. A language can become an unmaintainable mess if it includes every feature that yields marginally more-concise code. However, in those other languages, it isn't just easier to spawn a thread or to register a button-click handler; large swaths of their APIs are simpler, more consistent, and more powerful. In Java, one could have written similar APIs that take objects of classes implementing a particular function, but such APIs would be unpleasant to use.

For some time now, the question was not whether to augment Java for functional programming, but how to do it. It took several years of experimentation before a design emerged that is a good fit for Java. In the next section, you will see how you can work with blocks of code in Java 8.

The Syntax of Lambda Expressions

Consider the previous sorting example again. We pass code that checks whether one string is shorter than another. We compute

?

1

Integer.compare(first.length(), second.length())

What are first and second? They are both strings! Java is a strongly typed language, and we must specify that as well:

?

1

2

(String first, String second)

     -> Integer.compare(first.length(), second.length())

You have just seen your first lambda expression! Such an expression is simply a block of code, together with the specification of any variables that must be passed to the code.

Why the name? Many years ago, before there were any computers, the logician Alonzo Church wanted to formalize what it means for a mathematical function to be effectively computable. (Curiously, there are functions that are known to exist, but nobody knows how to compute their values.) He used the Greek letter lambda (λ) to mark parameters. Had he known about the Java API, he would have written:

?

1

λfirst.λsecond.Integer.compare(first.length(), second.length())

Why the letter λ? Did Church run out of other letters of the alphabet? Actually, the venerablePrincipia Mathematica used the ˆ accent to denote free variables, which inspired Church to use an uppercase lambda (Λ) for parameters. But in the end, he switched to the lowercase version. Ever since, an expression with parameter variables has been called a "lambda expression."

 

 

The single most important change in Java 8 enables faster, clearer coding and opens the door to functional programming. Here's how it works.

You have just seen one form of lambda expressions in Java: parameters, the -> arrow, and an expression. If the code carries out a computation that doesn't fit in a single expression, write it exactly like you would have written a method: enclosed in {} and with explicit returnstatements. For example,

?

1

2

3

4

5

(String first, String second) -> {

     if(first.length() < second.length()) return-1;

     elseif(first.length() > second.length()) return1;

     elsereturn0;

  }

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

?

1

() -> { for(inti = 0; i < 1000; i++) doWork(); }

If the parameter types of a lambda expression can be inferred, you can omit them. For example,

?

1

2

3

Comparator<String> comp

     = (first, second) // Same as (String first, String second)

        -> Integer.compare(first.length(), second.length());

Here, the compiler can deduce that first and second must be strings because the lambda expression is assigned to a string comparator. (We will have a closer look at this assignment later.)

If a method has a single parameter with inferred type, you can even omit the parentheses:

?

1

2

3

EventHandler<ActionEvent> listener = event ->

     System.out.println("Thanks for clicking!");

        // Instead of (event) -> or (ActionEvent event) ->

You can add annotations or the final modifier to lambda parameters in the same way as for method parameters:

?

1

2

(finalString name) -> ...

    (@NonNullString name) -> ...

You never specify the result type of a lambda expression. It is always inferred from context. For example, the expression

?

1

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

can be used in a context where a result of type int is expected.

Note that it is illegal for a lambda expression to return a value in some branches but not in others. For example, (int x) -> { if (x >= 0) return 1; } is invalid.

Functional Interfaces

As we discussed, there are many existing interfaces in Java that encapsulate blocks of code, such as Runnable or Comparator. Lambdas are backwards compatible with these interfaces.

You can supply a lambda expression whenever an object of an interface with a single abstract method is expected. Such an interface is called a functional interface.

You may wonder why a functional interface must have a single abstract method. Aren't all methods in an interface abstract? Actually, it has always been possible for an interface to redeclare methods from the Object class such as toString or clone, and these declarations do not make the methods abstract. (Some interfaces in the Java API redeclare Object methods in order to attach javadoc comments. Check out the Comparator API for an example.) More importantly, as you will see shortly, in Java 8, interfaces can declare non-abstract methods.

To demonstrate the conversion to a functional interface, consider the Arrays.sort method. Its second parameter requires an instance of Comparator, an interface with a single method. Simply supply a lambda:

?

1

2

Arrays.sort(words,

     (first, second) -> Integer.compare(first.length(), second.length()));

Behind the scenes, the Arrays.sort method receives an object of some class that implementsComparator<String>. Invoking the compare method on that object executes the body of the lambda expression. The management of these objects and classes is completely implementation dependent, and it can be much more efficient than using traditional inner classes. It is best to think of a lambda expression as a function, not an object, and to accept that it can be passed to a functional interface.

This conversion to interfaces is what makes lambda expressions so compelling. 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 fact, conversion to a functional interface is the only thing that you can do with a lambda expression in Java. In other programming languages that support function literals, you can declare function types such as (String, String) -> int, declare variables of those types, and use the variables to save function expressions. In Java, you can't even assign a lambda expression to a variable of type Object because Objectis not a functional interface. The Java designers decided to stick strictly with the familiar concept of interfaces instead of adding function types to the language.

The Java API defines several generic functional interfaces in the java.util.function package. One of the interfaces, BiFunction<T, U, R>, describes functions with parameter types T and Uand return type R. You can save our string comparison lambda in a variable of that type:

?

1

2

BiFunction<String, String, Integer> comp

     = (first, second) -> Integer.compare(first.length(), second.length());

However, that does not help you with sorting. There is no Arrays.sort method that wants aBiFunction. If you have used a functional programming language before, you may find this curious. But for Java programmers, it's pretty natural. An interface such as Comparator has a specific 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 specific functional interface for it.

The interfaces 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 can equally well convert a lambda expression into a functional interface that is a part of whatever API you use today. Also, you can tag any functional interface with the @FunctionalInterface annotation. This has two advantages. The compiler checks that the annotated entity is an interface with a single abstract method. And the javadoc page includes a statement that your interface is a functional interface. You are not required to use the annotation. Any interface with a single abstract method is, by definition, a functional interface. But using the @FunctionalInterface annotation is a good idea.

Finally, note that checked exceptions matter when a lambda is converted to an instance of a functional interface. If the body of a lambda expression can throw a checked exception, that exception needs to be declared in the abstract method of the target interface. 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

Because the Runnable.run cannot throw any exception, this assignment is illegal. To fix the error, you have two choices. You can catch the exception in the body of the lambda expression. Or you can assign the lambda to an interface whose single abstract method can throw the exception. For example, the call method of the Callable interface can throw any exception. Therefore, you can assign the lambda to a Callable<Void> (if you add a statement return null).

Method References

Sometimes, there is already a method that carries out exactly the action that you'd like to pass on to some other code. For example, suppose you simply want to print the event object whenever a button is clicked. Of course, you could call

?

1

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

It would be nicer if you could just pass the println method to the setOnAction method. Here is how you do that:

?

1

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

The expression System.out::println is a method reference that is equivalent to the lambda expression x -> System.out.println(x).

As another example, suppose you want to sort strings regardless of letter case. You can pass this method expression:

?

1

Arrays.sort(strings, String::compareToIgnoreCase)

As you can see from these examples, the :: operator separates the method name from the name of an object or class. There are three principal cases:

<!--[if !supportLists]-->·        <!--[endif]-->object::instanceMethod

<!--[if !supportLists]-->·        <!--[endif]-->Class::staticMethod

<!--[if !supportLists]-->·        <!--[endif]-->Class::instanceMethod

In the first two cases, the method reference is equivalent to a lambda expression that supplies the parameters of the method. As already mentioned, System.out::println is equivalent to x -> System.out.println(x). Similarly, Math::pow is equivalent to (x, y) -> Math.pow(x, y). In the third case, the first parameter becomes the target of the method. For example,String::compareToIgnoreCase is the same as (x, y) -> x.compareToIgnoreCase(y).

 

 

 

 

 

When there are multiple overloaded methods with the same name, the compiler will try to find from the context which one you mean. For example, there are two versions of the Math.maxmethod, one for integers and one for double values. Which one gets picked depends on the method parameters of the functional interface to which Math::max is converted. Just like lambda expressions, method references don't live in isolation. They are always turned into instances of functional interfaces.

You can capture the this parameter in a method reference. For example, this::equals is the same as x -> this.equals(x). It is also valid to use super. The method expressionsuper::instanceMethod uses this as the target and invokes the superclass version of the given method. Here is an artificial example that shows the mechanics:

?

1

2

3

4

5

6

7

8

9

10

11

12

classGreeter {

     publicvoidgreet() {

        System.out.println("Hello, world!");

     }

  }

   

  classConcurrentGreeter extendsGreeter {

     publicvoidgreet() {

        Thread t = newThread(super::greet);

        t.start();

     }

  }

When the thread starts, its Runnable is invoked, and super::greet is executed, calling the greetmethod of the superclass. (Note that in an inner class, you can capture the this reference of an enclosing class as EnclosingClass.this::method or EnclosingClass.super::method.)

Constructor References

Constructor references are just like method references, except that the name of the method isnew. For example, Button::new is a reference to a Button constructor. Which constructor? It depends on the context. Suppose you have a list of strings. Then, you can turn it into an array of buttons, by calling the constructor on each of the strings, with the following invocation:

?

1

2

3

List<String> labels = ...;

  Stream<Button> stream = labels.stream().map(Button::new);

  List<Button> buttons = stream.collect(Collectors.toList());

Details of the streammap, and collect methods are beyond the scope of this article. For now, what's important is that the map method calls the Button(String) constructor for each listelement. There are multiple Buttonconstructors, but the compiler picks the one with a Stringparameter because it infers from the context that the constructor is called with a string.

You can form constructor references with array types. For example, int[]::new is a constructor reference with one parameter: the length of the array. It is equivalent to the lambda expressionx -> new int[x].

Array constructor references are useful to overcome a limitation of Java. It is not possible to construct an array of a generic type T. The expression new T[n] is an error since 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 Stream interface has a toArray method that returns an Objectarray:

?

1

Object[] buttons = stream.toArray();

But that is unsatisfactory. The user wants an array of buttons, not objects. The stream library solves that problem with constructor references. Pass Button[]::new to the toArray method:

?

1

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

The toArray method invokes this constructor to obtain an array of the correct type. Then it fills and returns the array.

The single most important change in Java 8 enables faster, clearer coding and opens the door to functional programming. Here's how it works.

Variable Scope

Often, you want to be able to access variables from an enclosing method or class in a lambda expression. Consider this example:

?

1

2

3

4

5

6

7

8

9

publicstaticvoidrepeatMessage(String text, intcount) {

     Runnable r = () -> {

        for(inti = 0; i < count; i++) {

           System.out.println(text);

           Thread.yield();

        }

     };

     newThread(r).start();

  }

Consider a call:

?

1

repeatMessage("Hello", 1000); // Prints Hello 1,000 times in a separate thread

Now look at the variables count and text inside the lambda expression. Note that these variables are not defined in the lambda expression. Instead, these are parameter variables of therepeatMessage method.

If you think about it, something not obvious is going on here. The code of the lambda expression may run long after the call to repeatMessagehas returned and the parameter variables are gone. How do the text and count variables stay around?

To understand what is happening, we need to refine our understanding of a lambda expression. A lambda expression has three ingredients:

<!--[if !supportLists]-->1.   <!--[endif]-->A block of code

<!--[if !supportLists]-->2.   <!--[endif]-->Parameters

<!--[if !supportLists]-->3.   <!--[endif]-->Values for the free variables; that is, the variables that are not parameters and not defined inside the code

In our example, the lambda expression has two free variables, text and count. The data structure representing the lambda expression must store the values for these variables, in our case, "Hello" and 1000. We say that these values have been captured by the lambda expression. (It's an implementation detail how that is done. For example, one can translate a lambda expression into an object with a single method, so that the values of the free variables are copied into instance variables of that object.)

The technical term for a block of code together with the values of the free variables is a closure. If someone gloats that their language has closures, rest assured that Java has them as well. In Java, lambda expressions are closures. In fact, inner classes have been closures all along. Java 8 gives us closures with an attractive syntax.

As you have seen, a lambda expression can capture the value of a variable in the enclosing scope. In Java, to ensure that the captured value is well defined, there is an important restriction. In a lambda expression, you can only reference variables whose value doesn't change. For example, the following is illegal:

?

1

2

3

4

5

6

7

8

9

10

publicstaticvoidrepeatMessage(String text, intcount) {

     Runnable r = () -> {

        while(count > 0) {

           count--; // Error: Can't mutate captured variable

           System.out.println(text);

           Thread.yield();

        }

     };

     newThread(r).start();

  }

There is a reason for this restriction. Mutating variables in a lambda expression is not thread-safe. Consider a sequence of concurrent tasks, each updating a shared counter.

?

1

2

3

4

intmatches = 0;

  for(Path p : files)

     newThread(() -> { if(p has some property) matches++; }).start();

        // Illegal to mutate matches

If this code were legal, it would be very, very bad. The increment matches++ is not atomic, and there is no way of knowing what would happen if multiple threads execute that increment concurrently.

Inner classes can also capture values from an enclosing scope. Before Java 8, inner classes were allowed to access only final local variables. This rule has now been relaxed to match that for lambda expressions. An inner class can access any effectively final local variable; that is, any variable whose value does not change.

Don't count on the compiler to catch all concurrent access errors. The prohibition against mutation holds only for local variables. If matchesis an instance or static variable of an enclosing class, then no error is reported, even though the result is just as undefined.

Also, it's perfectly legal to mutate a shared object, even though it is unsound. For example,

?

1

2

3

4

List<Path> matches = newArrayList<>();

  for(Path p : files)

     newThread(() -> { if(p has some property) matches.add(p); }).start();

        // Legal to mutate matches, but unsafe

Note that the variable matches is effectively final. (An effectively final variable is a variable that is never assigned a new value after it has been initialized.) In our case, matches always refers to the same ArrayList object. However, the object is mutated, and that is not thread-safe. If multiple threads call add, the result is unpredictable.

There are safe mechanisms for counting and collecting values concurrently. You may want to use streams to collect values with certain properties. In other situations, you may want to use thread-safe counters and collections.

As with inner classes, there is an escape hatch that lets a lambda expression update a counter in an enclosing local scope. Use an array of length 1, like this:

?

1

2

int[] counter = newint[1];

  button.setOnAction(event -> counter[0]++);

Of course, code like this is not thread-safe. For a button callback, that doesn't matter, but in general, you should think twice before using this trick.

The body of a lambda expression has the same scope as a nested block. The same rules for name conflicts and shadowing apply. It is illegal to declare a parameter or a local variable in the lambda that has the same name as a local variable.

?

1

2

3

4

Path first = Paths.get("/usr/bin");

  Comparator<String> comp =

     (first, second) -> Integer.compare(first.length(), second.length());

     // Error: Variable first already defined

Inside a method, you can't have two local variables with the same name. Therefore, you can't introduce such variables in a lambda expression either. When you use the this keyword in a lambda expression, you refer to the this parameter of the method that creates the lambda. For example, consider

?

1

2

3

4

5

6

publicclassApplication() {

     publicvoiddoWork() {

        Runnable runner = () -> { ...; System.out.println(this.toString()); ... };

        ...

     }

  }

The expression this.toString() calls the toString method of the Application object, not theRunnable instance. There is nothing special about the use of this in a lambda expression. The scope of the lambda expression is nested inside the doWork method, and this has the same meaning anywhere in that method.

Default Methods

Many programming languages integrate function expressions with their collections library. This often leads to code that is shorter and easier to understand than the loop equivalent. For example, consider a loop:

?

1

2

for(inti = 0; i < list.size(); i++)

     System.out.println(list.get(i));

There is a better way. The library designers can supply a forEach method that applies a function to each element. Then you can simply call

?

1

list.forEach(System.out::println);

That's fine if the collections library has been designed from the ground up. But the Java collections library was designed many years ago, and there is a problem. If the Collectioninterface gets new methods, such as forEach, then every program that defines its own class implementing Collection will break until it, too, implements that method. That is simply unacceptable in Java.

The Java designers decided to solve this problem once and for all by allowing interface methods with concrete implementations (called default methods). Those methods can be safely added to existing interfaces. In this section, we'll look at default methods in detail. In Java 8, the forEachmethod has been added to the Iterable interface, a superinterface of Collection, using the mechanism that I will describe here.

Consider this interface:

?

1

2

3

4

interfacePerson {

     longgetId();

     defaultString getName() { return"John Q. Public"; }

  }

The interface has two methods: getId, which is an abstract method, and the default methodgetName. A concrete class that implements the Person interface must, of course, provide an implementation of getId, but it can choose to keep the implementation of getName or to override it.

Default methods put an end to the classic pattern of providing an interface and an abstract class that implements most or all of its methods, such as Collection/AbstractCollection orWindowListener/WindowAdapter. Now, you can just implement the methods in the interface.

What happens if the exact same method is defined as a default method in one interface and then again as a method of a superclass or another interface? Languages such as Scala and C++ have complex rules for resolving such ambiguities. Fortunately, the rules in Java are much simpler. They are:

<!--[if !supportLists]-->1.   <!--[endif]-->Superclasses win. If a superclass provides a concrete method, default methods with the same name and parameter types are simply ignored.

<!--[if !supportLists]-->2.   <!--[endif]-->Interfaces clash. If a super interface provides a default method, and another interface supplies a method with the same name and parameter types (default or not), then you must resolve the conflict by overriding that method.

Let's look at the second rule. Consider another interface with a getName method:

?

1

2

3

interfaceNamed {

     defaultString getName() { returngetClass().getName() + "_"+ hashCode(); }

  }

What happens if you form a class that implements both of them?

?

1

2

3

classStudent implementsPerson, Named {

     ...

  }

The class inherits two inconsistent getName methods provided by the Person and Named interfaces. Rather than choosing one over the other, the Java compiler reports an error and leaves it up to the programmer to resolve the ambiguity. Simply provide a getName method in the Student class. In that method, you can choose one of the two conflicting methods, like this:

?

1

2

3

4

classStudent implementsPerson, Named {

     publicString getName() { returnPerson.super.getName(); }

     ...

  }

Now assume that the Named interface does not provide a default implementation for getName:

?

1

2

3

interfaceNamed {

     String getName();

  }

Can the Student class inherit the default method from the Person interface? This might be reasonable, but the Java designers decided in favor of uniformity. It doesn't matter how two interfaces conflict. If at least one interface provides an implementation, the compiler reports an error, and the programmer must resolve the ambiguity.

If neither interface provides a default for a shared method, then we are in the pre-Java 8 situation and there is no conflict. An implementing class has two choices: implement the method, or leave it unimplemented. In the latter case, the class is itself abstract.

I just discussed name clashes between two interfaces. Now consider a class that extends a superclass and implements an interface, inheriting the same method from both. For example, suppose that Person is a class and Student is defined as:

?

1

classStudent extendsPerson implementsNamed { ... }

In that case, only the superclass method matters, and any default method from the interface is simply ignored. In our example, Student inherits the getName method from Person, and it doesn't make any difference whether the Named interface provides a default for getName or not. This is the "class wins" rule. The "class wins" rule ensures compatibility with Java 7. If you add default methods to an interface, it has no effect on code that worked before there were default methods. But be warned: You can never make a default method that redefines one of the methods in theObject class. For example, you can't define a default method for toString or equals, even though that might be attractive for interfaces such as List. As a consequence of the "classes win" rule, such a method could never win against Object.toString or Object.equals.

Static Methods in Interfaces

As of Java 8, you are allowed to add static methods to interfaces. There was never a technical reason why this should be outlawed: It simply seemed to be against the spirit of interfaces as abstract specifications.

Until now, it has been common to place static methods in companion classes. You find pairs of interfaces and utility classes such as Collection/Collections or Path/Paths in the standard library.

Have a look at the Paths class. It has only a couple of factory methods. You can construct a path from a sequence of strings, such as Paths.get("jdk1.8.0", "jre", "bin"). In Java 8, you can add this method to the Path interface:

?

1

2

3

4

5

6

publicinterfacePath {

     publicstaticPath get(String first, String... more) {

        returnFileSystems.getDefault().getPath(first, more);

     }

     ...

  }

Then the Paths class is no longer necessary.

When you look at the Collections class, you will find two kinds of methods. A method such as:

?

1

publicstaticvoidshuffle(List<?> list)

would work well as a default method of the List interface:

?

1

publicdefaultvoidshuffle()

You could then simply call list.shuffle() on any list.

For a factory method, that doesn't work because you don't have an object on which to invoke the method. That is where static interface methods come in. For example,

?

1

2

publicstatic<T> List<T> nCopies(intn, T o)

     // Constructs a list of n instances of o

could be a static method of the List interface. Then you would call List.nCopies(10, "Fred")instead of Collections.nCopies(10, "Fred") and it would be clear to the reader that the result is a List.

It is unlikely that the Java collections library will be refactored in this way, but when you implement your own interfaces, there is no longer a reason to provide a separate companion class for utility methods.

In Java 8, static methods have been added to quite a few interfaces. For example, theComparator interface has a very useful static comparing method that accepts a "key extraction" function and yields a comparator that compares the extracted keys. To compare Person objects by name, use Comparator.comparing(Person::name).

Conclusion

In this article, I compared strings by length with the lambda expression (first, second) -> Integer.compare(first.length(), second.length()). But with the static compare method, we can do much better and simply use Comparator.compare(String::length). This is a fitting way of closing this article because it demonstrates the power of working with functions. The comparemethod turns a function (the key extractor) into a more complex function (the key-based comparator). Such "higher-order functions" are discussed in more detail in my book, as well as in various online resources for Java 8.

 

 

分享到:
评论

相关推荐

    Harnessing the Power of Java 8 Lambda Expressions

    根据提供的文件信息,此文档是一本关于Java 8中Lambda表达式的书籍的一部分,具体书名为《Functional Programming in Java - Harnessing the Power of Java 8 Lambda Expressions》,作者是Venkat Subramaniam,出版...

    JAVA 8 Lambda表达式-Lambda Expressions.rar

    `JAVA 8 Lambda Expressions.pdf` 这份文档可能涵盖了以下主题: 1. Lambda 表达式的基本概念和语法 2. 函数式接口及其应用 3. Lambda 表达式与方法引用来实现函数式编程 4. Stream API 和 Lambda 表达式的结合使用...

    Lambda Expressions in Java 8 epub

    Lambda Expressions in Java 8 英文epub 本资源转载自网络,如有侵权,请联系上传者或csdn删除 本资源转载自网络,如有侵权,请联系上传者或csdn删除

    Lambda Expressions in Java 8 azw3

    Lambda Expressions in Java 8 英文azw3 本资源转载自网络,如有侵权,请联系上传者或csdn删除 本资源转载自网络,如有侵权,请联系上传者或csdn删除

    java 8 lambda- expressions 学习资料,例子。练习 java example

    Java 8是Java语言的一个重大版本更新,它引入了lambda表达式,这是Java平台的核心特性之一。Lambda表达式为Java带来了函数式编程的元素,使得开发者可以以更简洁的方式编写代码,尤其是在集合和多线程方面。本资料...

    Lambda Expressions in Java 8 无水印pdf

    Lambda Expressions in Java 8 英文无水印pdf pdf所有页面使用FoxitReader和PDF-XChangeViewer测试都可以打开 本资源转载自网络,如有侵权,请联系上传者或csdn删除 本资源转载自网络,如有侵权,请联系上传者...

    Lambda Expressions in Java 8 mobi

    Lambda Expressions in Java 8 英文mobi 本资源转载自网络,如有侵权,请联系上传者或csdn删除 本资源转载自网络,如有侵权,请联系上传者或csdn删除

    英文原版-Lambda Expressions in Java 8 1st Edition

    In Java 8, a Lambda Expression is nothing but a block of code which can be passed around to execute. ,解压密码 share.weimo.info

    lambda Functional_Programming.pdf

    《Functional Programming in Java: Harnessing the Power of Java 8 Lambda Expressions》这本书由Venkat Subramaniam撰写,是Java开发者深入理解并掌握Java 8中核心功能编程概念的重要资源。随着Java 8的发布,...

    Java Closures and Lambda(Apress,2015)

    These new changes make their debut in Java 8, and their highlight is the long-awaited support for lambda expressions in the Java language. You'll learn to write lambda expressions and use them to ...

    Java8 lambda表达式2种常用方法代码解析

    Java8 lambda表达式2种常用方法代码解析 Java8 lambda表达式是Java8中的一种新功能,它允许开发者使用简洁的语法来实现函数式编程。lambda表达式可以用来替换匿名类,实现Runnable接口和事件处理。下面我们将详细...

    Java SE 8 for the Really Impatient

    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 ...

    java-lambda-expressions:Java 8中Lambda表达式的一些示例

    Java 8引入了一项革命性的特性,即Lambda表达式,它极大地简化了函数式编程,使得Java开发者可以更简洁、高效地处理集合数据。Lambda表达式是Java 8中的一个核心概念,它允许我们将匿名函数作为参数传递,或者直接...

    Mastering Lambdas Java Programming in a Multicore World

    he Definitive Guide to Lambda Expressions Mastering Lambdas: Java Programming in a Multicore World describes how the lambda-related features of Java SE 8 will enable Java to meet the challenges of ...

    Pro.Java.8.Programming.3rd.Edition.1484206428

    Pro Java 8 Programming covers the core Java development kit and the finer points of the core standard edition (SE) and development kit version 8. You'll discover the particulars of working with the ...

    Android代码-java8-tutorial

    Backed by short and simple code samples you'll learn how to use default interface methods, lambda expressions, method references and repeatable annotations. At the end of the article you'll be ...

    Mastering Lambdas- Java Programming in a Multicore World

    Lambda expressions were introduced in Java 8 as a significant feature to enhance functional programming capabilities within the Java ecosystem. These expressions allow developers to treat functions as...

    The Busy Coders Guide to Android Development最终版2019

    Java 8 Lambda Expressions Rx Basics Notifications Advanced Notifications Multi-Window Support Advanced ConstraintLayout GridLayout Dialogs and DialogFragments Advanced ListViews Action Modes Other ...

Global site tag (gtag.js) - Google Analytics