:: оператор (двойного двоеточия) в Java 8
Я изучал исходный код Java 8 и нашел эту конкретную часть кода очень удивительной:
// Defined in IntPipeline.java
@Override
public final OptionalInt reduce(IntBinaryOperator op) {
return evaluate(ReduceOps.makeInt(op));
}
@Override
public final OptionalInt max() {
return reduce(Math::max); // This is the gotcha line
}
// Defined in Math.java
public static int max(int a, int b) {
return (a >= b) ? a : b;
}
Это Math::max
что-то вроде указателя на метод?
Как обычный static
метод преобразуется в IntBinaryOperator
?
Переведено автоматически
Ответ 1
Обычно можно вызвать reduce
метод, используя Math.max(int, int)
следующим образом:
reduce(new IntBinaryOperator() {
int applyAsInt(int left, int right) {
return Math.max(left, right);
}
});
Это требует большого синтаксиса для простого вызова Math.max
. Вот тут-то и вступают в игру лямбда-выражения. Начиная с Java 8, разрешено делать то же самое гораздо более коротким способом:
reduce((int left, int right) -> Math.max(left, right));
Как это работает? Компилятор Java "обнаруживает", что вы хотите реализовать метод, который принимает два int
s и возвращает один int
. Это эквивалентно формальным параметрам одного-единственного метода интерфейса IntBinaryOperator
(параметр метода, который reduce
вы хотите вызвать). Итак, компилятор сделает все остальное за вас - он просто предполагает , что вы хотите реализовать IntBinaryOperator
.
Но поскольку Math.max(int, int)
сам по себе удовлетворяет формальным требованиям IntBinaryOperator
, его можно использовать напрямую. Поскольку Java 7 не имеет синтаксиса, который позволяет передавать сам метод в качестве аргумента (вы можете передавать только результаты метода, но никогда ссылки на методы), ::
синтаксис был введен в Java 8 для ссылки на методы:
reduce(Math::max);
Обратите внимание, что это будет интерпретироваться компилятором, а не JVM во время выполнения! Хотя он создает разные байт-коды для всех трех фрагментов кода, они семантически равны, поэтому последние два можно рассматривать как короткие (и, вероятно, более эффективные) версии IntBinaryOperator
приведенной выше реализации!
(Смотрите также Перевод лямбда-выражений)
Ответ 2
::
называется ссылкой на метод. По сути, это ссылка на один метод. Т.е. он ссылается на существующий метод по имени.
Краткое объяснение:
Ниже приведен пример ссылки на статический метод:
class Hey {
public static double square(double num){
return Math.pow(num, 2);
}
}
Function<Double, Double> square = Hey::square;
double ans = square.apply(23d);
square
может передаваться точно так же, как ссылки на объекты, и запускаться при необходимости. Фактически, его можно так же легко использовать как ссылку на "обычные" методы объектов, как и static
ones . Например:
class Hey {
public double square(double num) {
return Math.pow(num, 2);
}
}
Hey hey = new Hey();
Function<Double, Double> square = hey::square;
double ans = square.apply(23d);
Function
выше приведен функциональный интерфейс. Для полного понимания ::
важно понимать и функциональные интерфейсы. Очевидно, что функциональный интерфейс - это интерфейс только с одним абстрактным методом.
Примеры функциональных интерфейсов включают Runnable
, Callable
и ActionListener
.
Function
выше приведен функциональный интерфейс только с одним методом: apply
. Он принимает один аргумент и выдает результат.
Причина, по которой ::
они потрясающие, заключается в том, что:
Ссылки на методы - это выражения, которые обрабатываются так же, как и лямбда-выражения (...), но вместо предоставления тела метода они ссылаются на существующий метод по имени.
Например, вместо записи тела лямбда
Function<Double, Double> square = (Double x) -> x * x;
Вы можете просто сделать
Function<Double, Double> square = Hey::square;
Во время выполнения эти два square
метода ведут себя точно так же, как друг друга. Байт-код может совпадать, а может и не совпадать (хотя в приведенном выше случае генерируется один и тот же байт-код; скомпилируйте приведенный выше и проверьте с помощью javap -c
).
Единственный важный критерий, которому необходимо удовлетворять: предоставляемый вами метод должен иметь сигнатуру, аналогичную сигнатуре метода функционального интерфейса, который вы используете в качестве ссылки на объект.
Приведенное ниже является незаконным:
Supplier<Boolean> p = Hey::square; // illegal
square
ожидает аргумент и возвращает double
. get
Метод в поставщике возвращает значение, но не принимает аргумент. Таким образом, это приводит к ошибке.
Ссылка на метод относится к методу функционального интерфейса. (Как упоминалось, функциональные интерфейсы могут иметь только один метод каждый.)
Еще несколько примеров: accept
метод в consumer принимает входные данные, но ничего не возвращает.
Consumer<Integer> b1 = System::exit; // void exit(int status)
Consumer<String[]> b2 = Arrays::sort; // void sort(Object[] a)
Consumer<String> b3 = MyProgram::main; // void main(String... args)
class Hey {
public double getRandom() {
return Math.random();
}
}
Callable<Double> call = hey::getRandom;
Supplier<Double> call2 = hey::getRandom;
DoubleSupplier sup = hey::getRandom;
// Supplier is functional interface that takes no argument and gives a result
Выше, getRandom
не принимает никаких аргументов и возвращает double
. Таким образом, может использоваться любой функциональный интерфейс, который удовлетворяет критериям: не принимать аргументов и возвращать double
.
Другой пример:
Set<String> set = new HashSet<>();
set.addAll(Arrays.asList("leo","bale","hanks"));
Predicate<String> pred = set::contains;
boolean exists = pred.test("leo");
В случае параметризованных типов:
class Param<T> {
T elem;
public T get() {
return elem;
}
public void set(T elem) {
this.elem = elem;
}
public static <E> E returnSame(E elem) {
return elem;
}
}
Supplier<Param<Integer>> obj = Param<Integer>::new;
Param<Integer> param = obj.get();
Consumer<Integer> c = param::set;
Supplier<Integer> s = param::get;
Function<String, String> func = Param::<String>returnSame;
Ссылки на методы могут иметь разные стили, но по сути все они означают одно и то же и могут быть просто визуализированы как лямбда-выражения:
- Статический метод (
ClassName::methName
) - Метод экземпляра определенного объекта (
instanceRef::methName
) - Суперметод определенного объекта (
super::methName
) - Метод экземпляра произвольного объекта определенного типа (
ClassName::methName
) - Ссылка на конструктор класса (
ClassName::new
) - Ссылка на конструктор массива (
TypeName[]::new
)
Для получения дополнительной информации см. Состояние лямбда-выражения.
Ответ 3
Да, это правда. Оператор ::
используется для ссылки на методы. Таким образом, с его помощью можно извлекать статические методы из классов или методы из объектов. Один и тот же оператор может использоваться даже для конструкторов. Все упомянутые здесь случаи проиллюстрированы в приведенном ниже примере кода.
Официальную документацию Oracle можно найти здесь.
Вы можете получить более подробный обзор изменений в JDK 8 в этой статье. В разделе "Ссылки на метод / конструктор" также приведен пример кода:
interface ConstructorReference {
T constructor();
}
interface MethodReference {
void anotherMethod(String input);
}
public class ConstructorClass {
String value;
public ConstructorClass() {
value = "default";
}
public static void method(String input) {
System.out.println(input);
}
public void nextMethod(String input) {
// operations
}
public static void main(String... args) {
// constructor reference
ConstructorReference reference = ConstructorClass::new;
ConstructorClass cc = reference.constructor();
// static method reference
MethodReference mr = cc::method;
// object method reference
MethodReference mr2 = cc::nextMethod;
System.out.println(cc.value);
}
}
Ответ 4
Для создания анонимных методов используется лямбда-выражение. Оно ничего не делает, кроме вызова существующего метода, но понятнее ссылаться на метод непосредственно по его имени. И ссылка на метод позволяет нам сделать это с помощью оператора ссылки на метод ::
.
Рассмотрим следующий простой класс, в котором у каждого сотрудника есть имя и ранг.
public class Employee {
private String name;
private String grade;
public Employee(String name, String grade) {
this.name = name;
this.grade = grade;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGrade() {
return grade;
}
public void setGrade(String grade) {
this.grade = grade;
}
}
Предположим, у нас есть список сотрудников, возвращаемый каким-либо методом, и мы хотим отсортировать сотрудников по их классу. Мы знаем, что можем использовать анонимный класс как:
List<Employee> employeeList = getDummyEmployees();
// Using anonymous class
employeeList.sort(new Comparator<Employee>() {
@Override
public int compare(Employee e1, Employee e2) {
return e1.getGrade().compareTo(e2.getGrade());
}
});
где getDummyEmployee() - это некоторый метод, как:
private static List<Employee> getDummyEmployees() {
return Arrays.asList(new Employee("Carrie", "C"),
new Employee("Fanishwar", "F"),
new Employee("Brian", "B"),
new Employee("Donald", "D"),
new Employee("Adam", "A"),
new Employee("Evan", "E")
);
}
Теперь мы знаем, что Comparator - это функциональный интерфейс. Функциональный интерфейс - это интерфейс с ровно одним абстрактным методом (хотя он может содержать один или несколько стандартных или статических методов). Лямбда-выражение обеспечивает реализацию @FunctionalInterface
поэтому функциональный интерфейс может иметь только один абстрактный метод. Мы можем использовать лямбда-выражение как:
employeeList.sort((e1,e2) -> e1.getGrade().compareTo(e2.getGrade())); // Lambda expression
Кажется, все хорошо, но что, если класс Employee
также предоставляет аналогичный метод?
public class Employee {
private String name;
private String grade;
// getter and setter
public static int compareByGrade(Employee e1, Employee e2) {
return e1.grade.compareTo(e2.grade);
}
}
В этом случае использование самого имени метода будет более понятным. Следовательно, мы можем напрямую ссылаться на метод, используя ссылку на метод как:
EmployeeList.sort(employee::compareByGrade); // Ссылка на метод
Согласно документации, существует четыре вида ссылок на методы:
+----+-------------------------------------------------------+--------------------------------------+
| | Kind | Example |
+----+-------------------------------------------------------+--------------------------------------+
| 1 | Reference to a static method | ContainingClass::staticMethodName |
+----+-------------------------------------------------------+--------------------------------------+
| 2 |Reference to an instance method of a particular object | containingObject::instanceMethodName |
+----+-------------------------------------------------------+--------------------------------------+
| 3 | Reference to an instance method of an arbitrary object| ContainingType::methodName |
| | of a particular type | |
+----+-------------------------------------------------------+--------------------------------------+
| 4 |Reference to a constructor | ClassName::new |
+------------------------------------------------------------+--------------------------------------+