: (двойное двоеточие) оператор в 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;
}

Is Math::max что-то вроде указателя метода? Как нормальный static метод преобразуется в IntBinaryOperator?

14 ответов


обычно, можно было бы назвать 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 над реализацией!

(см. Также перевод лямбда-выражения)


:: называется ссылкой на метод. Это в основном ссылка на один метод. Т. е. он ссылается на существующий метод по имени.

Краткое Описание:
Ниже приведен пример ссылки на статический метод:

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. Он берет одну аргумент и дает результат.


почему ::s являются удивительными это это:

ссылки на метод-это выражения, которые имеют ту же обработку, что и лямбда-выражения (...), но вместо предоставления тела метода они ссылаются на существующий метод по имени.

Е. Г. вместо того, чтобы писать лямбда тела

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

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

еще несколько примеров:accept метод потребитель принимает ввод, но ничего не возвращает.

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.


Да, это правда. The :: оператор используется для ссылки на метод. Итак, можно извлечь static методы из классов, используя его или методы из объектов. Один и тот же оператор может использоваться даже для конструкторов. Все случаи, упомянутые здесь, приведены в примере кода ниже.

официальная документация от 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);
   }
}

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

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

ClassName :: methodName 

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

objRef :: methodName

и

ClassName :: methodName

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

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

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


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

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

метод reference 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).


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

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

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;
    }
}

Предположим, у нас есть список сотрудники возвращаются каким-то методом, и мы хотим отсортировать сотрудников по их классу. Мы знаем, что можем использовать анонимный класс as:

    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("Farhan", "F"),
                new Employee("Brian", "B"),
                new Employee("Donald", "D"),
                new Employee("Adam", "A"),
                new Employee("Evan", "E")
                );
    }

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

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

по состоянию на docs существует четыре вида ссылок на метод:

+----+-------------------------------------------------------+--------------------------------------+
|    | 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 8 для ссылок на методы. Ссылка на метод-это сокращенный синтаксис для лямбда-выражения, которое выполняет только один метод. Вот общий синтаксис ссылки на метод:

Object :: methodName

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

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

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

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

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

Purchase::calculatePrice

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


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

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


return reduce(Math::max); и НЕ РАВНО to 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 ^_^

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

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


так как многие ответы здесь объясняются хорошо :: поведение, кроме того, я хотел бы пояснить, что :: оператор не должен иметь точно такую же сигнатуру, как ссылающийся функциональный интерфейс, если он используется, например, переменные. Предположим, нам нужно 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;

Ok он скомпилирован нормально. Что, если нам нужен метод экземпляра? Позволяет обновить 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;

мое затмение скажи мне " не удается сделать статическую ссылку на нестатический метод 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;

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

public class TestObject {

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

}

BinaryOperator<TestObject> binary = TestObject::testInstance;

*я просто учусь, поэтому я понял, попробуйте и посмотрите, не стесняйтесь поправлять меня, если я неправильно!--28-->


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

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

Шаг 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
Comparator c = Comparator.comparing(Person::getAge());

в старых версиях 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();
}