:: (двойной двоеточие) в 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 и возвращает одно значение типа 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. Например:

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 в Supplier возвращает значение, но не принимает аргумент. Таким образом, это приводит к ошибке.

Ссылка на метод относится к методу функционального интерфейса. (Как уже упоминалось, функциональные интерфейсы могут иметь только один метод каждый).

Еще несколько примеров: метод 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)

Для получения дополнительной информации см. Http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html.

Ответ 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

:: - это новый оператор, включенный в Java 8, который используется для ссылки на метод существующего класса. Вы можете ссылаться на статические методы и нестатические методы класса.

Для обращения к статическим методам синтаксис:

ClassName :: methodName 

Для ссылки на нестатические методы синтаксис

objRef :: methodName

и

ClassName :: methodName

Единственным предварительным условием для обращения к методу является тот метод, который существует в функциональном интерфейсе, который должен быть совместим с ссылкой на метод.

Ссылки на методы, при оценке, создают экземпляр функционального интерфейса.

Найдено по: http://www.speakingcs.com/2014/08/method-references-in-java-8.html

Ответ 5

Кажется, уже немного поздно, но вот мои два цента. Лямбда-выражение используется для создания анонимных методов. Он не делает ничего, кроме вызова существующего метода, но более понятно ссылаться на метод напрямую по его имени. И ссылка на метод позволяет нам сделать это, используя метод-ссылку operator ::.

Рассмотрим следующий простой класс, где у каждого сотрудника есть имя и класс.

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 exp

Вроде бы все хорошо, но что, если класс 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); // method reference

Согласно документации существует четыре вида ссылок на методы:

+----+-------------------------------------------------------+--------------------------------------+
|    | 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                       |
+------------------------------------------------------------+--------------------------------------+

Ответ 6

Это ссылка на метод в Java 8. Документация оракула здесь.

Как указано в документации...

Ссылка метода Person:: compareByAge - это ссылка на статическую Метод.

Ниже приведен пример ссылки на метод экземпляра конкретный объект:

class ComparisonProvider {
    public int compareByName(Person a, Person b) {
        return a.getName().compareTo(b.getName());
    }

    public int compareByAge(Person a, Person b) {
        return a.getBirthday().compareTo(b.getBirthday());
    }
}

ComparisonProvider myComparisonProvider = new ComparisonProvider();
Arrays.sort(rosterAsArray, myComparisonProvider::compareByName); 

Ссылка на метод myComparisonProvider:: compareByName вызывает метод compareByName это часть объекта myComparisonProvider. JRE представляет аргументы типа метода, которые в этом случае являются (Person, Person).

Ответ 7

:: Оператор был введен в Java 8 для ссылок на методы. Ссылка на метод - это сокращенный синтаксис для лямбда-выражения, которое выполняет только ОДИН метод. Вот общий синтаксис ссылки на метод:

Object :: methodName

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

Consumer<String> c = s -> System.out.println(s);

Чтобы сделать код более понятным, вы можете превратить это лямбда-выражение в ссылку на метод:

Consumer<String> c = System.out::println;

Ответ 8

The:: известен как ссылки на методы. Допустим, мы хотим вызвать метод calculatePrice класса Purchase. Тогда мы можем записать его как:

Purchase::calculatePrice

Его также можно рассматривать как короткую форму записи выражения лямбда. Поскольку ссылки на методы преобразуются в лямбда-выражения.

Ответ 9

Во время выполнения они ведут себя точно так же. Байт-код может/не быть одинаковым (для выше Incase он генерирует один и тот же байт-код (см. выше и проверьте javaap -c;))

Во время выполнения они ведут себя точно так же .method(math:: max);, он генерирует одну и ту же математику (см. выше и проверить javap -c;))

Ответ 10

return reduce(Math::max); НЕ РАВНО до return reduce(max());

Но это означает, что-то вроде этого:

IntBinaryOperator myLambda = (a, b)->{(a >= b) ? a : b};//56 keystrokes I had to type -_-
return reduce(myLambda);

Вы можете просто сохранить 47 нажатий клавиш, если вы пишете вот так

return reduce(Math::max);//Only 9 keystrokes ^_^

Ответ 11

Поскольку многие ответы здесь объясняли хорошо поведение ::, дополнительно я хотел бы пояснить, что оператор :: не должен иметь точно такую ​​же подпись, что и ссылочный функциональный интерфейс, если он используется для переменных экземпляра. Предположим, нам нужен BinaryOperator, который имеет тип TestObject. Традиционно он реализован следующим образом:

BinaryOperator<TestObject> binary = new BinaryOperator<TestObject>() {

        @Override
        public TestObject apply(TestObject t, TestObject u) {

            return t;
        }
    };

Как вы видите в анонимной реализации, он требует два аргумента TestObject и возвращает объект TestObject. Чтобы удовлетворить это условие, используя оператор ::, мы можем начать с статического метода:

public class TestObject {


    public static final TestObject testStatic(TestObject t, TestObject t2){
        return t;
    }
}

а затем вызовите:

BinaryOperator<TestObject> binary = TestObject::testStatic;

Хорошо, он скомпилирован. А если нам нужен метод экземпляра? Позволяет обновить TestObject с помощью метода экземпляра:

public class TestObject {

    public final TestObject testInstance(TestObject t, TestObject t2){
        return t;
    }

    public static final TestObject testStatic(TestObject t, TestObject t2){
        return t;
    }
}

Теперь мы можем получить доступ к экземпляру, как показано ниже:

TestObject testObject = new TestObject();
BinaryOperator<TestObject> binary = testObject::testInstance;

Этот код компилируется отлично, но ниже одного нет:

BinaryOperator<TestObject> binary = TestObject::testInstance;

My eclipse сказать мне "Невозможно сделать статическую ссылку на нестатический метод testInstance (TestObject, TestObject) из типа TestObject..."

Достаточно справедливо его метод экземпляра, но если мы перегрузим testInstance, как показано ниже:

public class TestObject {

    public final TestObject testInstance(TestObject t){
        return t;
    }

    public final TestObject testInstance(TestObject t, TestObject t2){
        return t;
    }

    public static final TestObject testStatic(TestObject t, TestObject t2){
        return t;
    }
}

И вызов:

BinaryOperator<TestObject> binary = TestObject::testInstance;

Код будет просто компилироваться. Потому что он будет вызывать testInstance с единственным параметром вместо двойного. Хорошо, так что случилось с нашими двумя параметрами? Вывод распечатки и просмотр:

public class TestObject {

    public TestObject() {
        System.out.println(this.hashCode());
    }

    public final TestObject testInstance(TestObject t){
        System.out.println("Test instance called. this.hashCode:" 
    + this.hashCode());
        System.out.println("Given parameter hashCode:" + t.hashCode());
        return t;
    }

    public final TestObject testInstance(TestObject t, TestObject t2){
        return t;
    }

    public static final TestObject testStatic(TestObject t, TestObject t2){
        return t;
    }
}

Будет выводиться:

 1418481495  
 303563356  
 Test instance called. this.hashCode:1418481495
 Given parameter hashCode:303563356

Хорошо, поэтому JVM достаточно умен, чтобы вызвать param1.testInstance(param2). Можем ли мы использовать testInstance с другого ресурса, но не TestObject, то есть:

public class TestUtil {

    public final TestObject testInstance(TestObject t){
        return t;
    }
}

И вызов:

BinaryOperator<TestObject> binary = TestUtil::testInstance;

Он просто не компилируется, и компилятор скажет: "Тип TestUtil не определяет testInstance (TestObject, TestObject)" . Поэтому компилятор будет искать статическую ссылку, если это не тот же тип. Хорошо, что о полиморфизме? Если мы удалим финальные модификаторы и добавим класс SubTestObject:

public class SubTestObject extends TestObject {

    public final TestObject testInstance(TestObject t){
        return t;
    }

}

И вызов:

BinaryOperator<TestObject> binary = SubTestObject::testInstance;

Он не будет компилироваться, компилятор все равно будет искать статическую ссылку. Но ниже код будет компилироваться отлично, поскольку он проходит - это тест:

public class TestObject {

    public SubTestObject testInstance(Object t){
        return (SubTestObject) t;
    }

}

BinaryOperator<TestObject> binary = TestObject::testInstance;

* Я просто изучаю, поэтому я понял, что попробуй и посмотри, не стесняйся меня исправить, если я ошибаюсь

Ответ 12

Я нашел этот источник очень интересным.

На самом деле, это лямбда, которая превращается в двойной двоеточие. Двойной двоеточие более читабельно. Мы следуем этим шагам:

ШАГ 1:

// We create a comparator of two persons
Comparator c = (Person p1, Person p2) -> p1.getAge().compareTo(p2.getAge());

ШАГ 2:

// We use the interference
Comparator c = (p1, p2) -> p1.getAge().compareTo(p2.getAge());

ШАГ 3:

// The magic using method reference
Comparator c = Comparator.comparing(Person::getAge);

Ответ 13

В java-8 Streams Reducer в простых произведениях есть функция, которая принимает два значения в качестве входных данных и возвращает результат после некоторых вычислений. этот результат подается на следующей итерации.

в случае функции Math: max, метод возвращает максимальное значение двух пройденных значений и, в конце концов, у вас наибольшее количество в руке.

Ответ 14

В более старых версиях Java вместо "::" или lambd вы можете использовать:

public interface Action {
    void execute();
}

public class ActionImpl implements Action {

    @Override
    public void execute() {
        System.out.println("execute with ActionImpl");
    }

}

public static void main(String[] args) {
    Action action = new Action() {
        @Override
        public void execute() {
            System.out.println("execute with anonymous class");
        }
    };
    action.execute();

    //or

    Action actionImpl = new ActionImpl();
    actionImpl.execute();
}

Или переходя к методу:

public static void doSomething(Action action) {
    action.execute();
}

Ответ 15

Так что я вижу здесь тонны ответов, которые, честно говоря, слишком сложны, и это занижение.

Ответ довольно прост: :: он называется "Метод ссылки" https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html

Так что я не буду копировать-вставлять, по ссылке, вы можете найти всю информацию, если прокрутите вниз до таблицы.


Теперь давайте кратко рассмотрим, что такое "Ссылки на метод":

A :: B несколько заменяет следующее встроенное лямбда-выражение: (params...) → AB (params...)

Чтобы соотнести это с вашими вопросами, необходимо понимать лямбда-выражение java. Что не сложно.

Встроенное лямбда-выражение аналогично определенному функциональному интерфейсу (который является интерфейсом, который имеет не более и не менее 1 метода). Давайте кратко рассмотрим, что я имею в виду:

InterfaceX f = (x) -> x*x; 

InterfaceX должен быть функциональным интерфейсом. Любой функциональный интерфейс, единственное, что важно для InterfaceX для этого компилятора, это то, что вы определяете формат:

InterfaceX может быть любым из этого:

interface InterfaceX
{
    public Integer callMe(Integer x);
}

или это

interface InterfaceX
{
    public Double callMe(Integer x);
}

или более общий:

interface InterfaceX<T,U>
{
    public T callMe(U x);
}

Давайте возьмем первый представленный случай и встроенное лямбда-выражение, которое мы определили ранее.

До Java 8 вы могли бы определить это так:

 InterfaceX o = new InterfaceX(){
                     public int callMe (int x, int y) 
                       {
                        return x*x;
                       } };

Функционально это одно и то же. Разница больше в том, как компилятор это воспринимает.

Теперь, когда мы взглянули на встроенное лямбда-выражение, вернемся к методам References (: :). Допустим, у вас есть такой класс:

class Q {
        public static int anyFunction(int x)
             {
                 return x+5;
             } 
        }

Поскольку метод anyFunctions имеет те же типы, что и интерфейсный интерфейс callMe, мы можем сопоставить эти два с помощью метода Reference.

Мы можем написать это так:

InterfaceX o =  Q::anyFunction; 

и это эквивалентно этому:

InterfaceX o = (x) -> Q.anyFunction(x);

Крутая вещь и преимущество ссылок на методы в том, что сначала, до тех пор, пока вы не назначите их переменным, они будут не иметь типов. Таким образом, вы можете передавать их в качестве параметров любому эквивалентному (имеющему одинаковые типы) функциональному интерфейсу. Что именно происходит в вашем случае

Ответ 16

Предыдущие ответы вгляда относительно того, что :: эталонного метода делает. Подводя итог, он предоставляет способ ссылки на метод (или конструктор) без его выполнения, а при оценке он создает экземпляр функционального интерфейса, который предоставляет целевой контекст типа.

Ниже приведены два примера поиска объекта с максимальным значением в ArrayList WITH и БЕЗ использования ссылки на метод ::. Пояснения в комментариях ниже.


БЕЗ использования ::

import java.util.*;

class MyClass {
    private int val;
    MyClass (int v) { val = v; }
    int getVal() { return val; }
}

class ByVal implements Comparator<MyClass> {
    // no need to create this class when using method reference
    public int compare(MyClass source, MyClass ref) {
        return source.getVal() - ref.getVal();
    }
}

public class FindMaxInCol {
    public static void main(String args[]) {
        ArrayList<MyClass> myClassList = new ArrayList<MyClass>();
        myClassList.add(new MyClass(1));
        myClassList.add(new MyClass(0));
        myClassList.add(new MyClass(3));
        myClassList.add(new MyClass(6));

        MyClass maxValObj = Collections.max(myClassList, new ByVal());
    }
}

С использованием ::

import java.util.*;

class MyClass {
    private int val;
    MyClass (int v) { val = v; }
    int getVal() { return val; }
}

public class FindMaxInCol {
    static int compareMyClass(MyClass source, MyClass ref) {
        // This static method is compatible with the compare() method defined by Comparator. 
        // So there no need to explicitly implement and create an instance of Comparator like the first example.
        return source.getVal() - ref.getVal();
    }

    public static void main(String args[]) {
        ArrayList<MyClass> myClassList = new ArrayList<MyClass>();
        myClassList.add(new MyClass(1));
        myClassList.add(new MyClass(0));
        myClassList.add(new MyClass(3));
        myClassList.add(new MyClass(6));

        MyClass maxValObj = Collections.max(myClassList, FindMaxInCol::compareMyClass);
    }
}

Ответ 17

Двойное двоеточие, т.е. оператор :: представлен в Java 8 как ссылка на метод. Ссылка на метод - это форма лямбда-выражения, которая используется для ссылки на существующий метод по его имени.

имя_класса :: имяМетода

например: -

  • stream.forEach(element -> System.out.println(element))

Используя двойное двоеточие ::

  • stream.forEach(System.out::println(element))