it-swarm.com.ru

Оператор :: (double двоеточие) в 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?

870
Narendra Pathai

Обычно можно вызвать метод 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 выше!

(Смотрите также Перевод лямбда-выражений )

942
isnot2bad

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

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

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. Метод 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 .

449
Jatin

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

Официальную документацию от Oracle можно найти здесь .

Вы можете получить лучший обзор изменений JDK 8 в this статье. В разделе ссылки на метод/конструктор также приведен пример кода:

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);
   }
}
51
Olimpiu POP

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

Для ссылки на статические методы используется следующий синтаксис:

ClassName :: methodName 

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

objRef :: methodName

А также

ClassName :: methodName

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

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

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

24
sreenath

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

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

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 - это функциональный интерфейс. A Функциональный интерфейс - это тот, который содержит только один абстрактный метод (хотя он может содержать один или несколько стандартных или статических методов). Лямбда-выражение обеспечивает реализацию @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                       |
+------------------------------------------------------------+--------------------------------------+
23
i_am_zero

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

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

Ссылка на метод 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).

19
david99world

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

Object :: methodName

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

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

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

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

:: известен как ссылки на метод. Допустим, мы хотим вызвать метод Calculate для класса "Покупка". Тогда мы можем написать это как:

Purchase::calculatePrice

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

3
Sonu

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

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

2
Alfa khatoon

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

Мой 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 с одним параметром вместо двойного. Ok так, что случилось с нашими двумя параметрами? Давайте распечатаем и посмотрим:

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)" . Поэтому компилятор будет искать статическую ссылку, если она не того же типа. Ok как насчет полиморфизма? Если мы удалим окончательные модификаторы и добавим наш SubTestObject класс:

public class SubTestObject extends TestObject {

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

}

И позвоните:

BinaryOperator<TestObject> binary = SubTestObject::testInstance;

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

public class TestObject {

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

}

BinaryOperator<TestObject> binary = TestObject::testInstance;

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

2
HRgiger

return reduce(Math::max); is НЕ РАВНО до 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 ^_^
2
Jude Niroshan

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

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

2
Pramod

В более старых версиях 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();
}
1
Kamil Tomasz Jarmusik

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

Ниже приведены два примера поиска объекта с максимальным значением в 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's 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);
    }
}
1
Liutong Chen

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

Ответ довольно прост: :: это называется метод ссылки https://docs.Oracle.com/javase/tutorial/Java /javaOO/methodreferences.html

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


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

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

Чтобы соотнести это с вашими вопросами, необходимо понимать лямбда-выражение 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 имеет те же типы, что и InterfaceX callMe , мы можем приравнять эти два с помощью ссылки на метод.

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

InterfaceX o =  Q::anyFunction; 

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

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

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

1
Nertan Lucian

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

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

STEP1:

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

STEP2:

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

STEP3:

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

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

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

например: -

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

Используя Double Colon ::

  • stream.forEach (System.out :: Println (элемент))
0
ishant kulshreshtha