JEP draft: Concise Method Bodies
Краткие тела методов
| Ответственный | Brian Goetz |
| Тип | Feature |
| Область | SE |
| Статус | Draft |
| Компонент | specification / language |
| Обсуждение | amber dash dev at openjdk dot java dot net |
| Трудоёмкость | M |
| Длительность | S |
| Создан | 2018/08/13 15:12 |
| Обновлён | 2019/03/25 12:43 |
| Задача | 8209434 |
Аннотация
Поддержать в исходном коде на Java более краткие тела методов, чтобы простые методы стали прозрачнее. Привести формы тел методов в соответствие с формами лямбда-выражений.
Мотивация
В языке Java есть три формы литералов поведения: лямбды-операторы, лямбды-выражения и ссылки на методы. У лямбд-операторов тело заключено в фигурные скобки и содержит операторы:
ToIntFunction<String> lenFn = (String s) -> { return s.length(); };
Часто тело вычисляет лишь одно выражение (или оператор-выражение) и возвращает результат. Поэтому есть более простая форма — лямбды-выражения:
ToIntFunction<String> lenFn = (String s) -> s.length();
Эта форма не только синтаксически короче, но и нагляднее показывает, что делает лямбда.
Многие лямбды-выражения лишь передают аргументы (и, возможно, получатель) существующему методу, поэтому есть третья форма — ссылки на методы:
ToIntFunction<String> lenFn = String::length;
Эта форма не всегда короче равнозначного лямбда-выражения, но она прозрачнее: сразу видно, что мы просто повторно используем существующий метод.
Любую ссылку на метод можно записать как лямбда-выражение, а любое лямбда-выражение — как лямбду-оператор, так что две последние формы строго говоря не обязательны. Однако разработчики по возможности предпочитают последние формы, и обычно код от этого становится читабельнее.
Те же соображения применимы к объявлению методов: тела многих методов целиком состоят из вычисления одного выражения или оператора-выражения и возврата результата. Мы можем применить к телам методов те же синтаксические варианты, что и к телам лямбд, и получить те же преимущества.
Описание
Традиционно в объявлении неабстрактного метода есть тело в фигурных скобках, содержащее операторы, — точно так же, как у лямбды-оператора:
int length(String s) {
return s.length();
}
Как и в случае лямбд-операторов, тело метода часто вычисляет лишь одно выражение и возвращает результат. Мы предлагаем для таких тел методов краткую форму по образцу синтаксиса лямбд-выражений:
int length(String s) -> s.length(); // -> is "single expression form"
И, как и в случае лямбд-выражений, многие методы — лишь обёртки вокруг существующих методов, поэтому по аналогии со ссылками на методы мы вводим ещё одну краткую форму:
int length(String s) = String::length; // = is "method reference form"
Когда тело метода имеет краткую форму, сигнатура метода задаёт структурный целевой тип для единственного выражения или ссылки на метод. Для двух кратких тел выше мы выполняли бы выбор перегрузки для String::length, ища метод, совместимый с целевым типом «String в int».
Любой неабстрактный и не-native метод класса или интерфейса может быть объявлен с кратким телом. У тел конструкторов, инициализаторов экземпляра и статических инициализаторов краткой формы не будет. То, является ли тело метода кратким, никак не влияет на наследование, перегрузку и переопределение.
В форме ссылки на метод после знака = можно использовать ссылки на методы всех видов: статические и несвязанные ссылки на методы, связанные ссылки на методы, ссылки на методы через super (для методов экземпляра), ссылки на конструкторы и ссылки на создание массивов.
Форма ссылки на метод
Форма ссылки на метод особенно полезна, поскольку идиома, в которой метод лишь делегирует другому методу, встречается чрезвычайно часто. Например, большинство фабричных методов лишь делегируют существующему конструктору:
public static Foo make(int a, int b, int c) {
return new Foo(a, b, c);
}
private Foo(int a, int b, int c) {...}
что можно упростить до:
public static Foo make(int a, int b, int c) = Foo::new;
private Foo(int a, int b, int c) {...}
Аналогично форма ссылки на метод полезна для перенаправляющих методов:
class MyList<T> implements List<T> {
private List<T> aList;
public int size() = aList::size;
public T get(int index) = aList::get;
...
}
Так не только читабельнее, но и надёжнее: автору не нужно повторять список аргументов, а значит, он не ошибётся в нём.
Форма с единственным выражением
Краткие тела методов упрощают вынос кода в новый метод — так же, как var упрощает вынос подвыражения в новую переменную. Каждое использование метода тогда повышает читабельность по сравнению с меньшим числом более крупных методов. Иными словами, краткие тела методов улучшают декомпозицию и сокращают дублирование. Форма с единственным выражением особенно удобна для этого, например в сочетании с выражением switch:
String dayOfWeek(int d) -> switch (d) {
case 1 -> "SUNDAY";
case 2 -> "MONDAY";
...
};
Форма с единственным выражением позволяет пользоваться стилистической краткостью и непосредственностью лямбд-выражений в большей части программы. Лямбда-выражение удобно, когда нужен экземпляр класса с одним конкретным методом:
button.addActionListener(ActionEvent e -> log(e.getWhen()));
но если нужно больше одного, вы возвращаетесь к объявлению анонимного класса с множеством { } — «шумом в строке»:
document.addDocumentListener(new DocumentListener() {
public void changedUpdate(DocumentEvent e) {
newFilter();
}
public void insertUpdate(DocumentEvent e) {
newFilter();
}
public void removeUpdate(DocumentEvent e) {
newFilter();
}
});
Краткие тела методов избавляют от этого шума и позволяют обращаться с анонимным классом почти как с семейством лямбд:
document.addDocumentListener(new DocumentListener() {
public void changedUpdate(DocumentEvent e) -> newFilter();
public void insertUpdate(DocumentEvent e) -> newFilter();
public void removeUpdate(DocumentEvent e) -> newFilter();
});
или, чтобы действительно максимально повысить повторное использование:
document.addDocumentListener(new DocumentListener() {
public void changedUpdate(DocumentEvent e) -> newFilter();
public void insertUpdate(DocumentEvent e) = this::changedUpdate;
public void removeUpdate(DocumentEvent e) = this::changedUpdate;
});
Единственным выражением после -> может быть выражение ссылки на метод. В этом случае краткое тело вернёт экземпляр функционального интерфейса:
Predicate<String> isEmpty(String s) -> String::isEmpty;
а не вызовет метод, на который указывает ссылка, как было бы со знаком =:
boolean isEmpty(String s) = String::isEmpty;
Обратите внимание: -> выводит тип тела метода из возвращаемого типа метода, а = выводит тип тела метода из типов параметров и возвращаемого типа метода.
В качестве ещё одного примера рассмотрим такой класс:
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; }
}
В методе fun1 выражение this::bar после -> вычисляется в экземпляр функционального интерфейса, воплощающий этот метод:
String bar(String s) { return null; }
В методе fun2 выражение this::bar после = приводит к вызову этого метода:
Function<String,String> bar() { return null; }
Предыстория
В C# 6 появились «методы с телом-выражением» (expression-bodied methods) для поддержки формы тела метода с единственным выражением (но с толстой стрелкой => вместо тонкой ->). Kotlin тоже поддерживает эту форму — в виде «функций с единственным выражением» (single expression functions).