Java finally has a Lambda expression ~ Java 8 language changes-Lambda expressions and interface class changes [reprinted], javalambda
Original article address en cn
Download Demo
Java 8 contains some important new language functions, providing you with a simpler way to build a program.Lambda expressionsDefines a new syntax for Inline code blocks. Its flexibility is the same as that for anonymous internal classes, but the sample files are much less. The interface changes allow the interface to be added to the existing interface without compromising its compatibility with the existing code. This article describes how these changes work together.
The biggest change in Java 8 is the additionLambda expressions.Lambda expressions are code blocks that can be passed by reference. Similar to some other programming languagesClosure: They are code that implements a certain function. One or more input parameters can be accepted and one result value can be returned. Closures are defined in an context and can be accessed (read-only for lambda expressions) from context values.
If you are not familiar with closures, do not be afraid.Java 8 lambda expressions are actually specialized in anonymous internal classes,Almost all Java developers are familiar with anonymous internal classes. An anonymous internal class provides an inline implementation of an interface or a subclass of a base class. We generally only use it in one place in the code. Lambda expressions are used in the same way, but they have a short syntax that makes them simpler than standard internal class definitions.
In this article, you will learn how to use lambda expressions in various situations, as well as the Java languageinterfaceDefine related extensions.
Java finally has a Lambda expression ~
. NET's implementation of Lambda expressions and function delegation has gone through a technological evolution since 2008. For more information, see "How can we use anonymous functions without Lambda calculus-Anonymous functions (anonymous methods and Lambda), Delegate, LINQ, and.. net c # declare, instantiate and use delegation, and delegate development in C "~
Understanding lambdas
Lambda expressions are always calledFunctional InterfaceImplementation of an object: defining a single abstract MethodinterfaceClass. Restrictions on a single abstract method are important because the lambda expression syntax does not use the method name. Instead, this expression usesDuck typing(Match parameters and return types, as in many dynamic languages) to ensure that the provided lambda is compatible with the expected interface method.
Duck typing"When you see a bird walking like a duck, swimming like a duck, and shouting like a duck, the bird can be called a duck." We do not care about the object type, whether it is a duck or not, but behavior.
In the example of Listing 1, lambda is usedNameInstances.main()The first code block in the method uses an anonymous internal class.Comparator<Name>Interface. The second code block uses a lambda expression.
Listing 1. Comparison between anonymous internal classes and Lambda expressions
public class Name {
public final String firstName;
public final String lastName;
public Name(String first, String last) {
firstName = first;
lastName = last;
}
// only needed for chained comparator
public String getFirstName() {
return firstName;
}
// only needed for chained comparator
public String getLastName() {
return lastName;
}
// only needed for direct comparator (not for chained comparator)
public int compareTo(Name other) {
int diff = lastName.compareTo(other.lastName);
if (diff == 0) {
diff = firstName.compareTo(other.firstName);
}
return diff;
}
...
}
public class NameSort {
private static final Name[] NAMES = new Name[] {
new Name("Sally", "Smith"),
...
};
private static void printNames(String caption, Name[] names) {
...
}
public static void main(String[] args) {
// sort array using anonymous inner class
Name[] copy = Arrays.copyOf(NAMES, NAMES.length);
Arrays.sort(copy, new Comparator<Name>() {
@Override
public int compare(Name a, Name b) {
return a.compareTo(b);
}
});
printNames("Names sorted with anonymous inner class:", copy);
// sort array using lambda expression
copy = Arrays.copyOf(NAMES, NAMES.length);
Arrays.sort(copy, (a, b) -> a.compareTo(b));
printNames("Names sorted with lambda expression:", copy);
...
}
}
In listing 1, lambda is used to replace a normally anonymous internal class. This conventional internal class is common in practice, so lambda expressions immediately win the attention of Java 8 programmers. (In this example, both internal classes and lambda are used inNameClass to perform the comparison. IfcompareTo()The method code is inline in lambda, so the expression is not very concise .)
Standard functional interface
Newjava.util.functionThe package definition is intended to use a wide range of functional interfaces of lambdas. These interfaces are classified into several categories:
- Function: accepts a parameter and returns the result based on the parameter value.
- Predicate: accepts a parameter and returns a Boolean Value Based on the parameter value.
- BiFunction: two parameters are accepted and results are returned Based on the parameter values.
- Supplier: If the parameter is not accepted, a result is returned.
- Consumer: accept a parameter, no result (
void)
Most of these classes contain several variables used to process basic primitive parameters or return types. Many interface definitions can be used to combine instances, as shown in Listing 2.
Listing 2. Combined predicates
// Use the predicate combination to delete the matched name
List<Name> list = new ArrayList<>();
for (Name name : NAMES) {
list.add(name);
}
Predicate<Name> pred1 = name -> "Sally".equals(name.firstName);
Predicate<Name> pred2 = name -> "Queue".equals(name.lastName);
list.removeIf(pred1.or(pred2));
printNames("Names filtered by predicate:", list.toArray(new Name[list.size()]));
The code in Listing 2 defines a pairPredicate<Name>, One with the nameSallyMatch, second match with last nameQueueMatch.pred1.or(pred2)The method call constructs the defined composite predicates. The method applies two predicates in sequence. If one of the two predicates is equaltrue(With the logic in Java||Returnstrue.List.removeIf()Method to apply this combination predicate to delete the matched name from the list.
Java 8 definesjava.util.functionMany useful combinations of interfaces, but the combinations are inconsistent. Predicate variable (DoublePredicate,IntPredicate,LongPredicateAndPredicate<T>) Define the same combination and modification method:and(),negate()Andor(). HoweverFunction<T>Does not define any combination or modification methods. If you have experience using functional programming languages, you may find these differences and omissions odd.
Change
interfaces
interfaceClass structure (for example,Comparator) There has been a change in Java 8, partly because it is used to make lambda expressions more available. Interfaces before Java 8 can only define constants and abstract methods that must be implemented later. Java 8 adds simultaneous definitions in InterfacesstaticAnddefaultMethod capabilities. Static methods in an interface are actually the same as static methods in an abstract class. The default method is more like an old interface method, but there is an accompanying implementation that is used only when the method is rewritten.
An important feature of the default method is that you can add them to an existinginterfaceWithout compromising compatibility with other code that uses this interface (unless your existing code uses the same method name for another purpose ). This is a powerful feature that Java 8 designers use to improve support for lambda expressions in many preset Java libraries. Listing 3 shows an example. The third method is to sort the names added to the listing 1 code.
Listing 3. concatenating key-extractor Comparator
// sort array using key-extractor lambdas
copy = Arrays.copyOf(NAMES, NAMES.length);
Comparator<Name> comp = Comparator.comparing(name -> name.lastName);
comp = comp.thenComparing(name -> name.firstName);
Arrays.sort(copy, comp);
printNames("Names sorted with key extractor comparator:", copy);
The code in listing 3 first shows how to use the newComparator.comparing()Static Method to create a Comparator based on your defined key-extraction lambda (technically, key-extraction lambda isjava.util.function.Function<T,R>An instance of the interface, where the Comparator type isTCompatible and extracted key typesRImplementedComparableInterface .) It also shows how to use the newComparator.thenComparing()By default, Comparator is combined. In listing 3, this method returns a new comparator, which sorts the first array by the last name and the second array by name.
You may think that you can link the comparator constructor:
Comparator<Name> comp = Comparator.comparing(name -> name.lastName)
.thenComparing(name -> name.firstName);
Unfortunately, this does not work for Java 8 type inference. You need to provide the compiler with more information about the expected types of results returned by static methods in any of the following forms:
Comparator<Name> com1 = Comparator.comparing((Name name1) -> name1.lastName)
.thenComparing(name2 -> name2.firstName);
Comparator<Name> com2 = Comparator.<Name,String>comparing(name1 -> name1.lastName)
.thenComparing(name2 -> name2.firstName);
The first form adds the lambda parameter type to the lambda expression:(Name name1) -> name1.lastName. With this assistance, the compiler can understand the rest of the work to be done. The second form will be passedcomparing()Type of the method's functional interface (implemented by lambda in this example)TAndRTell the compiler.
The ability to easily construct and concatenate comparator is a useful feature of Java 8, but the cost is increased complexity. Java 7ComparatorThe interface defines two methods (compare()And ensure that every object is defined everywhereequals()). Java 8 defines 18 methods (two original methods, nine new static methods and seven new default methods ). You will find that this large-scale interface expansion mode generated to use lambdas will be repeated in a considerable part of the Java standard library.
Use existing methods such as lambdas
If an existing method meets your needs, you can useMethod referenceTo directly pass this method. Listing 4 shows this method.
Listing 4. using existing methods such as lambdas
...
// sort array using existing methods as lambdas
copy = Arrays.copyOf(NAMES, NAMES.length);
comp = Comparator.comparing(Name::getLastName).thenComparing(Name::getFirstName);
Arrays.sort(copy, comp);
printNames("Names sorted with existing methods as lambdas:", copy);
Listing 4 has the same functionality as listing 3. The difference is that the existing method is used. You can use Java 8ClassName::methodNameMethod reference syntax, using any method like using a lambda expression. This is exactly the same effect as lambda that defines the call to this method. You can use static methods, lambda-specific objects, or input-type instance methods (as shown in Listing 4, wheregetFirstName()AndgetLastName()The method is comparedNameAnd constructor reference.
Method references are not only convenient to use, but may be more effective than using lambda expressions and for compilers (that's why the method reference works normally in the last part of Listing 4 compared to problems with lambdas) it also provides better type information. If you make a choice between using an existing method reference and using a lambda, you should always prefer to use method reference.
Captured and non-captured lambdas
The lambda examples you see in this article areNon-capturedThat is to say, they are simple expressions that are passed in only using the equivalent values of interface method parameters. Java 8CapturedLambdas uses the value in the included context. The captured lambdas is similar to the closures used in some other JVM languages (including Scala), but the difference is that in Java 8, any value in the context must beEffectively final. That is, the value must be true.final(Because the value of the class referenced from the anonymous internal class must be in earlier Java versions)OrIt has never been modified in the context. This standard applies to both lambda expressions and anonymous internal classes.
You can use some solutions to cope with the limit tively final. For example, if you want to use only the current values of some variables in a lambda expression, you can add a new method to accept these values as parameters, return the captured value for the lambda expression (in the form of an appropriate interface reference. If you want a lambda expression to modify the value in the closed context, you can wrap the value into a variable container.
Compared with captured lambdas, non-captured lambdas can be processed more efficiently, because the compiler can generate them into static methods in the inclusion class and can call them directly in line during runtime. The captured lambdas may be slightly less efficient, but its performance should be at least the same as that of anonymous internal classes in the same context.
Lambdas
Lambda expressions look like Anonymous internal classes, but they are implemented in different ways. Java internal classes are a huge constructor. Each internal class has an independent class file at the bytecode level. A lot of data is repeated (mainly in the form of constant pool items), class loading increases a considerable runtime overhead, all of which is just to support a small amount of additional code.
Java 8 does not use independent class files for lambdas, but relies oninvokedynamicBytecode command.invokedynamicTake a bootstrap method as the target. This method creates a lambda expression implementation when it is called for the first time. Then, the returned implementation is called directly. This avoids the space overhead of independent class files and the large amount of runtime overhead of loading classes. What is a lambda function?HowThe implementation is determined by bootstrap. The currently generated bootstrap code in Java 8 creates a new class for lambda at runtime, but different methods can be freely used in future implementations.
Java 8 combines some optimization measuresinvokedynamicThe implementation of lambdas is effective in practice. Most other JVM languages, including Scala (2.10.x), use internal classes generated by the compiler for closures. Future versions of these languages may turninvokedynamicTo use Java 8 (and later) optimization.
Lambda restrictions
As I mentioned at the beginning of this article, lambda expressions are almost the implementation of some special function interfaces. You can only use lambdas as interface reference and implementation of other interfaces. You can only use one lambda as the specific interface to be created. Listing 5 demonstrates this restriction through a pair of function interfaces with the same name. Java 8 compiler acceptanceString::lengthMethod is implemented as lambda of two interfaces. However, after lambda is defined as the instance of the first interface, it cannot be used as the instance of the second interface.
Listing 5. Lambda restrictions
private interface A {
public int valueA(String s);
}
private interface B {
public int valueB(String s);
}
public static void main(String[] args) {
A a = String::length;
B b = String::length;
// compiler error!
// b = a;
// ClassCastException at runtime!
// b = (B)a;
// works, but ugly (wraps in a new lambda)
b = (x) -> a.valueA(x);
System.out.println(b.valueB("abc"));
}
Functional programming languages such as Scala use function types rather than interfaces to define variables. In this languageHigh-order functionsA high-order function is a function that passes a function as a parameter or returns a function as a value. The programming style is much more flexible than lambdas, including the ability to build other functions as building blocks. Since Java 8 does not define function types, you cannot create lambdas in this way. You can createInterface(As shown in listing 3), but the code is only used to process the specific interface involved. Only in the newjava.util.functionSpecifically, 43 interfaces for lambdas are created in the package. Add these interfaces to hundreds of existing interfaces. You can see that the method of building interfaces is always subject to great restrictions.
When selecting interfaces (instead of adding function types to Java), you must think carefully. This will eliminate the need for major changes to the Java library, and support the use of lambda expressions for existing libraries. The disadvantage of doing so is that it limits Java 8 to the so-called "interface programming" or functional-like programming, rather than the real functional programming. However, as JVM starts to support other languages, including functional languages, this restriction is not that serious.
Conclusion
Lambdas is a major extension of the Java language, and as all Java staff migrate their applications to Java 8, Lambda expressions will soon become an indispensable tool for them. InStreamsLambdas is particularly useful when used in combination. Refer to "JVM concurrency: Java 8 concurrency basics" to learn how lambdas and Java 8 streams work together to simplify concurrent programming and improve application performance.
References
- Lambda expressions: this topic in the Java tutorial explains how to use lambda expressions in various contexts.
- Lambda: A Peek Under the Hood: view this JavaOne 2013 presentation by A Java language architect and IBM developerWorks writer Brian Goetz to learn about the logic behind the design and implementation of Java 8 lambda expressions.
- Program using Lambda expressions in Java: view the lambda use example in this real-time Encoding demonstration provided by Venkat Subramaniam on JavaOne 2013.
Download Demo