Java 8 language changes

Learn how Lambda expressions and changes to interface classes make Java 8 a new language


The biggest change in Java 8 is the addition of support for lambda expressions. Lambda expressions are blocks of code that can be passed by reference. They are similar to closures in some other programming languages: code that implements a function, optionally takes one or more input parameters, and optionally returns a result value. Closures are defined in a context and have access (read-only access, in the case of lambda expressions) to values from that context.

If you're unfamiliar with closures, fear not. Java 8 lambda expressions are effectively a specialization of anonymous inner classes, which almost every Java developer is familiar with. Anonymous inner classes supply an inline implementation of an interface, or subclass of a base class, that you want to use at only one point in your code. Lambda expressions are used the same way, but with an abbreviated syntax that makes them more concise than a standard inner class definition.

In this article, you'll see how to use lambda expressions in various situations, and you'll learn about the related extensions to Java language interface definitions. See the companion "Java 8 concurrency basics" article in the JVM concurrency series for more examples of working with lambdas, including using them with the Java 8 streams feature.

Getting into lambdas

Lambda expressions are always implementations of what Java 8 terms a functional interface : an interface class defining a single abstract method. The restriction to a single abstract method is important, because the lambda expression syntax doesn't use a method name. Instead, the expression uses duck typing (matching parameter and return types, as is done in many dynamic languages) to ensure that the supplied lambda is compatible with the expected interface method.

In the simple example in Listing 1, a lambda is used for sorting Name instances. The first block of code in the main() method uses an anonymous inner class to implement the Comparator<Name> interface, and the second block uses a lambda expression. (See Related topics for a link to the complete sample code for this article.)

Listing 1. Lambda expression compared to an anonymous inner class
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>() {
            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, the lambda is used as a replacement for a trivial anonymous inner class. This kind of trivial inner class is all too common in practice, so lambda expressions provide an immediate win for the Java 8 programmer. (In this case both the inner class and the lambda use a method implemented in the Name class to do the comparison work. If the compareTo() method code were inlined in the lambda, the expression would be less pleasingly succinct.)

Standard function interfaces

The new java.util.function package defines a wide variety of functional interfaces intended for use with lambdas. These are organized into several categories:

  • Function: Takes a single parameter, returns result based on parameter value
  • Predicate: Takes a single parameter, returns a boolean result based on parameter value
  • BiFunction: Takes two parameters, returns result based on parameter value
  • Supplier: Takes no parameters, returns a result
  • Consumer: Takes a single parameter with no return (void)

Most of these categories include several variations for working with basic primitive parameter or return types. Many of the interfaces define methods that you can use to combine instances, as in Listing 2.

Listing 2. Combining predicates
// use predicate composition to remove matching names
List<Name> list = new ArrayList<>();
for (Name name : NAMES) {
Predicate<Name> pred1 = name -> "Sally".equals(name.firstName);
Predicate<Name> pred2 = name -> "Queue".equals(name.lastName);
printNames("Names filtered by predicate:", list.toArray(new Name[list.size()]));

The Listing 2 code defines a pair of Predicate<Name>s, one matching the first name Sally, the second matching the last name Queue. The pred1.or(pred2) method call constructs a combined predicate defined by applying the two predicates in order, returning true if either of the two evaluates to true (with early out, as with the logical || operator in Java). The List.removeIf() method applies this combined predicate to remove matching names from the list.

Java 8 defines many useful combinations of the java.util.function interfaces, but the combinations are not consistent. The predicate variations (DoublePredicate, IntPredicate, LongPredicate, and Predicate<T>) all define the same composition and modification methods: and(), negate(), and or(). But the primitive variations for Function<T> don't define any composition or modification methods. If you have experience with functional programming languages, you probably find these differences and omissions odd.

Changing interfaces

The structure of interface classes (such as the Comparator used in the Listing 1 code) has changed in Java 8, in part to make lambda expressions more usable. Pre-Java 8 interfaces can define only constants, and abstract methods that you then must implement. Java 8 adds the ability to define both static and default methods in interfaces. Static methods in an interface are essentially the same as static methods in an abstract class. Default methods are more like old-style interface methods but have a supplied implementation that's used unless you override the method.

One important feature of default methods is that they can be added to an existing interface without breaking the compatibility of other code that uses that interface (unless your existing code happens to use the same method name for a different purpose). This is a powerful feature, and the Java 8 designers used it to retrofit support for lambda expressions to many of the preexisting Java libraries. Listing 3 shows an example, in the form of a third way to sort names added to the Listing 1 code.

Listing 3. Chaining key-extractor comparators
// 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 Listing 3 code first shows how you can use the new Comparator.comparing() static method to create a comparator based on a key-extraction lambda you define. (Technically, the key-extraction lambda is an instance of the java.util.function.Function<T,R> interface, where the type of the resulting comparator is assignment-compatible with T and the extracted key type R implements the Comparable interface.) It also shows how you can combine comparators using the new Comparator.thenComparing() default method, which in Listing 3 returns a new comparator that sorts first by last name, and second by first name.

You might think that you could inline the comparator construction as:

Comparator<Name> comp = Comparator.comparing(name -> name.lastName)
    .thenComparing(name -> name.firstName);

Unfortunately, this doesn't work with Java 8 type inference. You need to give the compiler additional information for the expected type of the result from the static method, using either 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 type of the lambda parameter to the lambda expression: (Name name1) -> name1.lastName. With this assist, the compiler can understand the rest of what it needs to do. The second form tells the compiler the types T and R for the function interface (in this case, implemented by the lambda) passed to the comparing() method.

The ability to construct and chain comparators easily is a useful feature of Java 8, but it comes at the cost of added complexity. The Java 7 Comparator interface defines two methods (compare(), plus the omnipresent equals() that's guaranteed to be defined for every object). The Java 8 version defines 18 (the original 2 methods plus 9 new static methods and 7 new default methods). You'll see this pattern of massive interface inflation to work with lambdas repeated in a significant portion of the Java standard library.

Using existing methods like lambdas

If you have an existing method that already does what you want, you can use a method reference to pass that method directly. Listing 4 illustrates this approach.

Listing 4. Using existing methods 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 does the same thing as the Listing 3 code but does it using existing methods. You can use the Java 8 ClassName::methodName method reference syntax to use an arbitrary method as though it were a lambda expression. The effect is exactly the same as if you defined a lambda that just calls that method. You can use method references to static methods, to instance methods of either a particular object or an input type to the lambda (as in Listing 4, where the getFirstName() and getLastName() methods are instance methods for the Name type being compared), and to constructors.

Method references are not only convenient, they're potentially more efficient than using lambda expressions and also supply better type information to the compiler (which is why the .thenComparing construct that caused problems with lambdas in the last section works fine with method references in Listing 4). If you have a choice between using a method reference to an existing method and using a lambda, you should always prefer to use the method reference.

Capturing and noncapturing lambdas

The lambda examples you've seen in this article are all noncapturing, meaning that they are simple expressions using only values passed in as the equivalent of interface method parameters. Capturing lambdas in Java 8 use values from the containing context. Capturing lambdas are similar to closures used in some other JVM languages (including Scala) but differ in that in Java 8, any values from the containing context must be effectively final. That is, the value must be either truly final (as values referenced from anonymous inner classes must be in earlier Java versions) or never modified within the context. This criterion applies to values used by both lambdas and anonymous inner classes.

You can use some workarounds to get around the effectively final restriction. For example, to use only the current values of certain variables in a lambda, you can add a new method that takes the values as parameters and returns the lambda (in the form of the appropriate interface reference) with the captured values. If you want a lambda to modify a value from the enclosing context, you can wrap the value in a mutable holder.

Noncapturing lambdas can potentially be handled more efficiently than capturing lambdas, because the compiler can generate them as static methods in the containing class, and runtime magic can inline the calls directly. Capturing lambdas might be less efficient, but a capturing lambda should perform at least as well as an anonymous inner class in the same context.

Lambdas behind the scenes

Lambda expressions look much like anonymous inner classes but are implemented differently. Java inner classes are bulky constructs; right down to the bytecode level, a separate class file exists for each inner class. Much data is duplicated (mostly in the form of constant pool entries), and class loading adds considerable runtime overhead, all for a small amount of added code.

Instead of using a separate class file for lambdas, Java 8 builds on the invokedynamic bytecode instruction added in Java 7. invokedynamic targets a bootstrap method, which in turn creates the lambda expression implementation the first time the method is called. Thereafter, the returned implementation is called directly. The space overhead of a separate class file, and much of the runtime overhead of loading the class, are avoided. Exactly how the lambda function is implemented is left up to the bootstrap. The bootstrap code currently generated by Java 8 builds a new class at runtime for the lambda, but future implementations are free to use different approaches.

Java 8 incorporates optimizations to make the implementation of lambdas through invokedynamic work well in practice. Most other JVM languages, including Scala (2.10.x), use compiler-generated inner classes for closures. Future versions of these languages are likely to move to the invokedynamic approach to take advantage of the Java 8 (and later) optimizations.

Lambda limitations

As I mentioned at the start of the article, lambda expressions are always implementations of some particular functional interface. You can only pass lambdas as interface references, and as with other interface implementations, you can only use a lambda as the specific interface it was created to be." Listing 5 demonstrates this limitation with a pair of identical (except for names) functional interfaces. The Java 8 compiler accepts the String::length method as a lambda implementation of both interfaces. But after the lambda is defined as an instance of the first interface, it can't be used as an instance of the second interface.

Listing 5. Lambda limitations
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, using a method reference
    b = a::valueA;

There's nothing surprising about the Listing 5 code to anyone thinking in terms of Java interfaces, because this is the way Java interfaces have always worked (except for the last bit, with the method reference — that's new in Java 8). But developers who have worked with functional programming languages such as Scala may find the interface restriction counter-intuitive.

Functional programming languages use function types, rather than interfaces, to define variables. It's common in such languages to work with higher-order functions: functions that pass functions as parameters or return functions as values. The result is a much more flexible style of programming than you get with lambdas, including the ability to use functions as building blocks to compose other functions. Because Java 8 doesn't define function types, you can't compose lambdas in that way. You can compose interfaces (as demonstrated in Listing 3), but only with code written to work with the specific interfaces involved. In the new java.util.function package alone, 43 interfaces are set up specifically for use with lambdas. Add those to the hundreds of preexisting interfaces, and you can see that the ways in which you can compose interfaces is always going to be severely limited.

The choice to use interfaces rather than add function types to Java was deliberate. It precludes the need for major changes to the Java libraries while also enabling the use of lambda expressions with the existing libraries. The downside is that it restricts Java 8 to what might be called "interface programming," or functional-ish programming, rather than true functional programming. But with the wealth of other languages — including functional languages — available on the JVM, this limitation isn't dire.


Lambdas are a major extension to the Java language, and along with their sibling method references, will quickly become indispensable tools for all Java developers as their applications move to Java 8. Lambdas are especially useful when combined with Java 8 streams. Check out "JVM concurrency: Java 8 concurrency basics" to see how lambdas and streams work together to simplify concurrent programming and improve application performance.

Downloadable resources

Related topics


Sign in or register to add and subscribe to comments.

Zone=Java development
ArticleTitle=Java 8 language changes