Ответ 1
Из Справочник по методам методов Oracle:
Ссылка на метод экземпляра произвольного объекта определенного типа
Ниже приведен пример ссылки на метод экземпляра произвольного объекта определенного типа:
String[] stringArray = { "Barbara", "James", "Mary", "John", "Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, String::compareToIgnoreCase);
Эквивалентное лямбда-выражение для ссылки методаString::compareToIgnoreCase
будет иметь формальный список параметров(String a, String b)
, где a и b - произвольные имена, используемые для лучшего описания этого примера. Ссылка на метод вызовет методa.compareToIgnoreCase(b)
.
Но что означает оператор ::
? Ну, оператор ::
может быть описан следующим образом (от этот вопрос SO):
Ссылка на метод может быть получена в разных стилях, но все они означают одно и то же:
- Статический метод (
ClassName::methodName
)- Метод экземпляра конкретного объекта (
instanceRef::methodName
)- Супер метод конкретного объекта (
super::methodName
)- Метод экземпляра произвольного объекта определенного типа (
ClassName::methodName
)- Ссылка на конструктор класса (
ClassName::new
)- Ссылка на конструктор массива (
TypeName[]::new
)
Итак, это означает, что ссылка метода String::compareToIgnoreCase
попадает под вторую категорию (instanceRef::methodName
), что означает, что ее можно перевести на (a, b) -> a.compareToIgnoreCase(b)
.
Я считаю, что следующие примеры иллюстрируют это далее. A Comparator<String>
содержит один метод, который работает с двумя операндами String
и возвращает int
. Это может быть псевдоописано как (a, b) ==> return int
(где операнды a
и b
). Если вы просмотрите его таким образом, все перечисленные ниже подпадают под эту категорию:
// Trad anonymous inner class
// Operands: o1 and o2. Return value: int
Comparator<String> cTrad = new Comparator<String>() {
@Override
public int compare(final String o1, final String o2) {
return o1.compareToIgnoreCase(o2);
}
};
// Lambda-style
// Operands: o1 and o2. Return value: int
Comparator<String> cLambda = (o1, o2) -> o1.compareToIgnoreCase(o2);
// Method-reference à la bullet #2 above.
// The invokation can be translated to the two operands and the return value of type int.
// The first operand is the string instance, the second operand is the method-parameter to
// to the method compareToIgnoreCase and the return value is obviously an int. This means that it
// can be translated to "instanceRef::methodName".
Comparator<String> cMethodRef = String::compareToIgnoreCase;
Этот отличный SO-ответ объясняет, как скомпилированы лямбда-функции. В этом ответе Jarandinor ссылается на следующий отрывок из Брайана Гетца превосходного документа, в котором описывается больше о lambda translations.
Вместо генерации байт-кода для создания объекта, реализующего лямбда-выражение (например, вызов конструктора для внутреннего класса), мы описываем рецепт построения лямбда и делегируем фактическую конструкцию языку runtime. Этот рецепт закодирован в статических и динамических списках аргументов invokedynamic инструкции.
В основном это означает, что собственная среда выполнения решает, как перевести лямбда.
Брайан продолжает:
Ссылки на методы обрабатываются так же, как и лямбда-выражения, за исключением того, что большинство ссылок на методы не нужно отбрасывать в новый метод; мы можем просто загрузить дескриптор константного метода для ссылочного метода и передать его метафайлу.
Итак, lambdas desugared в новый метод. Например.
class A {
public void foo() {
List<String> list = ...
list.forEach( s -> { System.out.println(s); } );
}
}
Приведенный выше код будет скрыт примерно так:
class A {
public void foo() {
List<String> list = ...
list.forEach( [lambda for lambda$1 as Consumer] );
}
static void lambda$1(String s) {
System.out.println(s);
}
}
Но, Брайан также объясняет это в документе:
если метод desugared является методом экземпляра, приемник считается первым аргументом
Брайан продолжает объяснять, что оставшиеся аргументы lambdas передаются как аргументы упомянутому методу.
Итак, с помощью этой записи Moandji Ezana десурагирование compareToIgnoreCase
как Comparator<String>
можно разбить на следующие шаги:
-
Collections#sort
для aList<String>
ожидает aComparator<String>
-
Comparator<String>
- это функциональный интерфейс с методомint sort(String, String)
, который эквивалентенBiFunction<String, String, Integer>
- Таким образом, экземпляр-компаратор может быть снабжен
BiFunction
-собираемым lambda:(String a, String b) -> a.compareToIgnoreCase(b)
-
String::compareToIgnoreCase
ссылается на метод экземпляра, который принимает аргументString
, поэтому он совместим с приведенным выше лямбда:String a
становится получателем, аString b
становится аргументом метода
Изменить: После ввода из OP я добавил пример низкого уровня, который объясняет desugaring