2013-11-15 4 views
679

я исследовал источник 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?

+31

Это синтаксический сахар, чтобы иметь компилятор авто- генерировать реализации интерфейса на основе функции, которую вы предоставляете (чтобы упростить использование всей лямбды с существующими кодовыми базами). – Neet

+2

http://java.dzone.com/articles/java-lambda-expressions-vs может помочь, не смотрел в глубину темы –

+5

@Neet это не совсем «синтаксический сахар», если только вы не можете сказать, для чего. то есть «x является синтаксическим сахаром для y». – Ingo

ответ

746

Обычно, можно было бы назвать метод 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-не имеет синтаксиса, который позволяет сам метод, который будет передан в качестве аргумента (вы можете передать результаты только метода, но не ссылки метода), синтаксис :: был введен в Java 8, эталонные методы:

reduce(Math::max); 

Обратите внимание, что это будет интерпретироваться компилятором, а не JVM во время выполнения! Хотя он создает разные байт-коды для всех трех фрагментов кода, они семантически равны, поэтому последние два можно считать короткими (и, вероятно, более эффективными) версиями реализации IntBinaryOperator выше!

(Смотри также Translation of Lambda Expressions)

43

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

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

Вы можете получить лучший обзор изменений 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); 
    } 
} 
+0

Увлекательный, не могли бы вы показать, как использовать mr или mr2? –

+0

Хорошее объяснение можно найти здесь: http://doanduyhai.wordpress.com/2012/07/14/java-8-lambda-in-details-part-iii-method-and-constructor-referencing/ –

+1

@ Метод RichardTingle '(Math :: max);' is invocation и определение метода будет выглядеть как public public void method (IntBinaryOperator op) {System.out.println (op.applyAsInt (1, 2));} '. Вот как его использовали. –

18

Это ссылка метод в Java 8. В документации оракул here.

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

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

+0

, но метод 'compareByAge' не является статическим. – abbas

+0

Bonjour Dexter^_^ – ABcDexter

+0

@abbas Nor сравниваетByName. Следовательно, вы получаете доступ к этим нестатическим методам через оператор ссылки с использованием объекта. Если они были статическими, вы можете использовать имя класса, например ComparisionProvider :: someStaticMethod –

340

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

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

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 выше, представляет собой функциональный интерфейс. Чтобы полностью объяснить ::, важно понять функциональный интерфейс. Просто, function interface - это интерфейс только с одним абстрактным методом.

Например: Runnable, Callable, ActionListener и так далее.

Function выше функциональный интерфейс только с одним способом apply. Он принимает один аргумент и дает результат.


Причина :: являются удивительным является because:

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

т.е. просто нравится писать лямбда тело:

Function<Double, Double> square = (Double x) -> x * x; 

Вы можете просто сделать:

Function<Double, Double> square = Hey::square; 

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

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

Ниже незаконна:

Supplier<Boolean> p = Hey::square; //illegal 

square ожидает аргумент и возвращает двойной. 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.

Другой пример:

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

+1

Благодарим вас за объяснение. В итоге: '::' использовать для извлечения метод, удовлетворяющий FunctionalInterface (лямбда): ClassX :: staticMethodX или instanceX :: instanceMethodX» – jessarah

20

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

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

ClassName :: methodName 

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

objRef :: methodName 

И

ClassName :: methodName 

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

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

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

2

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

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

2

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 ^_^ 
3

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

Purchase::calculatePrice 

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

+0

Могу ли я сделать вложенные ссылки метод например groupingBy (Order :: customer :: name) –

1

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

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

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

4

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

Object :: methodName 

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

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

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

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

It кажется его немного поздно, но вот мои два цента. A lambda expression используется для создания анонимных методов. Он ничего не делает, кроме вызова существующего метода, но яснее ссылаюсь на метод непосредственно по его имени. И 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; 
    } 
} 

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

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

Теперь мы знаем, что Comparable является функциональным интерфейсом. A Functional Interface - это тот, который содержит только один абстрактный метод (хотя он может содержать один или несколько стандартных или статических методов). Таким образом, мы можем использовать лямбда-выражения, как:

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

Это выражение ** лямбда ** (после исчисления лямбда) - это абсолютно не имеет никакого отношения к «lambada» .... –

+0

@marc_s Я не уверен, действительно ли я получил ваш комментарий Когда я сказал свое выражение lambada? –

+0

Я зафиксировал по крайней мере три случая, когда вы произнесли «lambada» вместо «лямбда» –

1

я нашел this source очень интересно.

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

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 
Comparator c = Comparator.comparing(Person::getAge()); 
+2

Кажется, что 'Person :: getAge() 'должно быть' Perso п :: getAge'. – Qwertiy

2

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

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

1

В более старых версиях 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(); 
} 
Смежные вопросы