Вопрос-ответ

:: (double colon) operator in Java 8

:: оператор (двойного двоеточия) в 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 "обнаруживает", что вы хотите реализовать метод, который принимает два ints и возвращает один 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;

Ссылки на методы могут иметь разные стили, но по сути все они означают одно и то же и могут быть просто визуализированы как лямбда-выражения:


  1. Статический метод (ClassName::methName)

  2. Метод экземпляра определенного объекта (instanceRef::methName)

  3. Суперметод определенного объекта (super::methName)

  4. Метод экземпляра произвольного объекта определенного типа (ClassName::methName)

  5. Ссылка на конструктор класса (ClassName::new)

  6. Ссылка на конструктор массива (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 |
+------------------------------------------------------------+--------------------------------------+
java java-8