JEP draft: Concise Method Bodies

OwnerBrian Goetz
TypeFeature
ScopeSE
StatusDraft
Componentspecification / language
Discussionamber dash dev at openjdk dot java dot net
EffortM
DurationS
Created2018/08/13 15:12
Updated2019/03/25 12:43
Issue8209434

Summary

Support more concise method bodies in Java source code, increasing transparency for simple methods. Align the forms of method bodies with the forms of lambda expressions.

Motivation

The Java language offers three forms of behavior literals: statement lambdas, expression lambdas, and method references. Statement lambdas have a brace-delimited body containing statements:

ToIntFunction<String> lenFn = (String s) -> { return s.length(); };

It is common for the body to compute only a single expression (or expression statement) and return the result. Accordingly, a simpler form, expression lambdas, is provided:

ToIntFunction<String> lenFn = (String s) -> s.length();

Not only is this form syntactically more concise, but it is more obvious what the lambda does.

Because many expression lambdas consist only of passing the arguments (including possibly the receiver) to an existing method, a third form, method references, is provided:

ToIntFunction<String> lenFn = String::length;

While this form is not always more concise than the equivalent expression lambda, it is more transparent; it is clear that we are merely reusing an existing method.

All method references can be expressed as expression lambdas, and all expression lambdas can be expressed as statement lambdas, so the latter two forms are not strictly required. However, developers prefer to use the latter forms where possible, and this usually results in more readable code.

The same considerations can be applied to the declaration of methods: many method bodies consist entirely of evaluating a single expression or expression statement, and returning the result. We can apply the same syntactic options to method bodies as we do to lambda bodies, and achieve the same benefits.

Description

Traditionally, the declaration of a non-abstract method has a brace-delimited body containing statements, just like a statement lambda:

int length(String s) {
    return s.length();
}

As with statement lambdas, it is common for a method body to compute only a single expression and return the result. We propose a concise form for such method bodies, modeled on the syntax of expression lambdas:

int length(String s) -> s.length();     //  -> is "single expression form"

And, as with expression lambdas, many methods are merely wrappers around existing methods, so we appeal to the analogy with method references for another concise form:

int length(String s) = String::length;  //  = is "method reference form"

When the body of a method has a concise form, the signature of the method provides a structural target type for the single expression or method reference. For the two concise bodies above, we would perform overload selection on String::length, looking for a method compatible with the target type of "String to int".

Any non-abstract, non-native method in a class or interface may be declared with a concise body. The bodies of constructors, instance initializers, and static initializers will not have a concise form. Whether a method body is concise has no effect on inheritance, overloading, or overriding.

The method reference form can use all kinds of method references after the = sign: static and unbound method references, bound method references, super method references (for instance methods), constructor references, and array creation references.

The method reference form

The method reference form is particularly useful, as the idiom where a method merely delegates to another method is exceedingly common. For example, most factory methods merely delegate to an existing constructor:

public static Foo make(int a, int b, int c) {
    return new Foo(a, b, c);
}

private Foo(int a, int b, int c) {...}

which can be simplified to:

public static Foo make(int a, int b, int c) = Foo::new;

private Foo(int a, int b, int c) {...}

Similarly, forwarding methods can benefit from the method reference form:

class MyList<T> implements List<T> {
    private List<T> aList;

    public int size() = aList::size;
    public T get(int index) = aList::get;
    ...
}

This is not only more readable, but also less error-prone: the author need not repeat the argument list, and therefore avoids getting it wrong.

The single expression form

Concise method bodies lower the bar to pulling code into a new method, in the same way that var lowers the bar to pulling a subexpression into a new variable. Each use of the method is then a boost for readability, compared with having a smaller number of larger methods. In other words, concise method bodies improve factoring and reduce duplication. The single expression form is especially convenient for this, such as when used for a switch expression:

String dayOfWeek(int d) -> switch (d) {
    case 1 -> "SUNDAY";
    case 2 -> "MONDAY";
    ...
};

The single expression form allows the stylistic brevity and directness of expression lambdas to be enjoyed by more of the program. An expression lambda is convenient when you need a class instance with one concrete method:

button.addActionListener(ActionEvent e -> log(e.getWhen()));

but if you need more than one, then you're back to declaring an anonymous class with lots of { } "line noise":

document.addDocumentListener(new DocumentListener() {
    public void changedUpdate(DocumentEvent e) {
        newFilter();
    }
    public void insertUpdate(DocumentEvent e) {
        newFilter();
    }
    public void removeUpdate(DocumentEvent e) {
        newFilter();
    }
});

Concise method bodies avoid the line noise, and let you treat the anonymous class almost like a family of lambdas:

document.addDocumentListener(new DocumentListener() {
    public void changedUpdate(DocumentEvent e) -> newFilter();
    public void insertUpdate(DocumentEvent e)  -> newFilter();
    public void removeUpdate(DocumentEvent e)  -> newFilter();
});

or, to really maximize reuse:

document.addDocumentListener(new DocumentListener() {
    public void changedUpdate(DocumentEvent e) -> newFilter();
    public void insertUpdate(DocumentEvent e)  =  this::changedUpdate;
    public void removeUpdate(DocumentEvent e)  =  this::changedUpdate;
});

It is possible for the single expression after -> to be a method reference expression. In this case, the concise body will return an instance of a functional interface:

Predicate<String> isEmpty(String s) -> String::isEmpty;

as opposed to invoking the referenced method, which would happen with an = sign:

boolean isEmpty(String s) = String::isEmpty;

Note that -> infers the type of the method's body from the method's return type, while = infers the type of the method's body from the method's parameter types and return type.

As a further example, consider this class:

class C {
    Function<String,String> fun1() -> this::bar;
    Function<String,String> fun2() =  this::bar;

    String bar(String s) { return null; }
    Function<String,String> bar() { return null; }
}

In method fun1, the this::bar after -> evaluates to an instance of a functional interface embodying this method:

String bar(String s) { return null; }

In method fun2, the this::bar after = causes invocation of this method:

Function<String,String> bar() { return null; }

Background

C# 6 introduced "expression-bodied methods" to support the single expression form of a method body (but with a fat arrow => rather than a thin arrow ->). Kotlin also supports this form, with "single expression functions".