: (dwukropek) operator w Javie 8

Badałem źródło Javy 8 i uznałem tę konkretną część kodu za bardzo zaskakującą:

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

Czy {[1] } jest czymś w rodzaju wskaźnika metody? W jaki sposób normalna Metoda static zostaje przekonwertowana na IntBinaryOperator?

 776
Author: Olimpiu POP, 2013-11-15

14 answers

Zazwyczaj można wywołać metodę reduce używając Math.max(int, int) w następujący sposób:

reduce(new IntBinaryOperator() {
    int applyAsInt(int left, int right) {
        return Math.max(left, right);
    }
});

To wymaga dużo składni do wywołania Math.max. W tym miejscu pojawiają się wyrażenia lambda. Od Javy 8 dozwolone jest robienie tego samego w znacznie krótszy sposób:

reduce((int left, int right) -> Math.max(left, right));
Jak to działa? Kompilator Javy "wykrywa", że chcesz zaimplementować metodę, która akceptuje dwa int s i zwraca jedną int. Jest to równoważne parametrom formalnym jedynej i jedynej metody interface IntBinaryOperator (parametr metody reduce, którą chcesz wywołać). Więc kompilator robi resztę za Ciebie-zakłada, że chcesz zaimplementować IntBinaryOperator.

Ale ponieważ Math.max(int, int) sama spełnia wymogi formalne IntBinaryOperator, może być używana bezpośrednio. Ponieważ w Javie 7 nie ma składni pozwalającej na przekazywanie samej metody jako argumentu (można przekazywać tylko wyniki metody, ale nigdy nie odwoływać się do niej), składnia :: została wprowadzona w Javie 8 do odwoływania się metody:

reduce(Math::max);

Zauważ, że będzie to interpretowane przez kompilator, a nie przez JVM podczas wykonywania! Pomimo tego, że tworzy różne kody bajtowe dla wszystkich trzech fragmentów kodu, są one semantycznie równe, więc dwa ostatnie można uznać za krótkie (i prawdopodobnie bardziej wydajne) wersje implementacji IntBinaryOperator powyżej!

(Zobacz także tłumaczenie wyrażeń Lambda)

 852
Author: isnot2bad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-04 20:42:32

:: nazywa się referencją metody. Jest to w zasadzie odniesienie do jednej metody. Oznacza to, że odnosi się do istniejącej metody z nazwy.

Krótkie Wyjaśnienie :
Poniżej znajduje się przykład odniesienia do metody statycznej:

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 może być przekazywany podobnie jak odniesienia do obiektów i wyzwalany w razie potrzeby. W rzeczywistości może być równie łatwo użyte jako odniesienie do" normalnych " metod obiektów jak static. Na przykład:

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 powyżej znajduje się interfejs funkcjonalny . Aby w pełni zrozumieć ::, ważne jest również zrozumienie funkcjonalnych interfejsów. Po prostu, interfejs funkcjonalny jest interfejsem z tylko jedną abstrakcyjną metodą.

Przykłady funkcjonalnych interfejsów obejmują Runnable, Callable, i ActionListener.

Function powyżej znajduje się funkcjonalny interfejs z tylko jedną metodą: apply. Wymaga jednego argumentu i daje wynik.


Powodem, dla którego ::S są niesamowite jest to :

Odniesienia do metody są wyrażeniami, które mają takie samo traktowanie jak wyrażenia lambda (...), ale zamiast dostarczać ciało metody, odwołują się do istniejącej metody po nazwie.

Np. zamiast pisać ciało lambda

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

Możesz po prostu zrobić

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

W czasie wykonywania, te dwie metody square zachowują się dokładnie tak samo jak siebie nawzajem. Kod bajtowy może być, ale nie musi być taki sam (choć w powyższym przypadku ten sam kod bajtowy jest wygenerowane; skompiluj powyższe i sprawdź za pomocą javap -c).

Jedynym głównym kryterium do spełnienia jest: podana metoda powinna mieć podobny podpis do metody interfejsu funkcjonalnego używanego jako odniesienie do obiektu.

Poniżej jest nielegalne:

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

square oczekuje argumentu i zwraca double. Metoda get w dostawca oczekuje argumentu, ale niczego nie zwraca. W ten sposób powoduje to błąd.

Odniesienie do metody odnosi się do metody interfejsu funkcjonalnego. (Jak wspomniano, interfejsy funkcjonalne mogą mieć tylko jedną metodę).

Kilka przykładów: metoda accept W Consumer pobiera dane wejściowe, ale niczego nie zwraca.

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

Powyżej, getRandom nie pobiera żadnego argumentu i zwraca double. Więc każdy interfejs funkcjonalny spełniający kryteria: nie dyskutuj i zwracaj double może być używany.

Inny przykład:

Set<String> set = new HashSet<>();
set.addAll(Arrays.asList("leo","bale","hanks"));
Predicate<String> pred = set::contains;
boolean exists = pred.test("leo");

W przypadku typów parametryzowanych :

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;
Odniesienia do metod mogą mieć różne style, ale zasadniczo wszystkie oznaczają to samo i mogą być po prostu wizualizowane jako lambda.]}
  1. metoda statyczna (ClassName::methName)
  2. metoda instancji określonego obiektu (instanceRef::methName)
  3. super metoda konkretnego obiektu (super::methName)
  4. metoda instancji dowolnego obiektu określonego typu (ClassName::methName)
  5. Referencja konstruktora klasy (ClassName::new)
  6. odniesienie do konstruktora tablicy (TypeName[]::new)

W celu uzyskania dalszych informacji patrz http://cr.openjdk.java.net / ~briangoetz/lambda/lambda-state-final.html .

 401
Author: Jatin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-17 15:32:53

Tak, to prawda. Operator :: jest używany do odwoływania się do metod. Można więc wyodrębnić metody Statyczne z klas, używając ich lub metody z obiektów. Ten sam operator może być używany nawet dla konstruktorów. Wszystkie przypadki wymienione tutaj są przykładowe w poniższej próbce kodu.

Oficjalna dokumentacja firmy Oracle znajduje się tutaj .

Możesz mieć lepszy przegląd zmian w JDK 8 w Ten artykuł . W metodzie/Konstruktorze podajemy również przykład kodu sekcja a:

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);
   }
}
 50
Author: Olimpiu POP,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-31 09:22:03

:: jest nowym operatorem zawartym w Javie 8, który jest używany do odwoływania się do metody istniejącej klasy. Możesz odwoływać się do metod statycznych i niestatycznych klasy.

W odniesieniu do metod statycznych składnia jest następująca:

ClassName :: methodName 

W przypadku odwoływania się do metod niestatycznych, składnia jest

objRef :: methodName

I

ClassName :: methodName

Jedynym warunkiem odniesienia do metody jest to, że metoda istnieje w interfejsie funkcjonalnym, który musi być zgodny z odniesieniem do metody.

Metoda referencje, po ocenie, tworzą instancję funkcjonalnego interfejsu.

Znaleziono na: http://www.speakingcs.com/2014/08/method-references-in-java-8.html

 22
Author: sreenath,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-26 04:14:54

Jest to odniesienie do metody w Javie 8. Dokumentacja oracle jest Tutaj .

Zgodnie z dokumentacją...

Metoda reference Person:: compareByAge jest odniesieniem do statycznego metoda.

Poniżej znajduje się przykład odniesienia do metody instancji konkretny obiekt:

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

Odniesienie do metody myComparisonProvider:: compareByName wywołuje metodę compareByName to część obiektu myComparisonProvider. JRE wnioskuje argumenty typu metody, które w tym przypadku są (Person, Person).

 18
Author: david99world,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-26 12:02:24

Wydaje się, że trochę późno, ale oto moje dwa centy. Wyrażenie lambda jest używane do tworzenia anonimowych metod. Nie robi nic poza wywołaniem istniejącej metody, ale jaśniej jest odnosić się do metody bezpośrednio po jej nazwie. And method reference pozwala nam to zrobić za pomocą operatora method-reference ::.

Rozważ następującą prostą klasę, w której każdy pracownik ma imię i stopień.

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

Załóżmy, że mamy listę pracowników zwróconych przez niektórych metody i chcemy posortować pracowników według ich oceny. Wiemy, że możemy użyć klasy anonymous jako:

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

Gdzie getDummyEmployee() jest jakąś metodą jako:

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

Teraz wiemy, że komparator jest funkcjonalnym interfejsem. Interfejs funkcjonalny to interfejs z dokładnie jedną abstrakcyjną metodą (choć może zawierać jedną lub więcej domyślnych lub statycznych metod). Możemy więc użyć wyrażenia lambda jako:

employeeList.sort((e1,e2) -> e1.getGrade().compareTo(e2.getGrade())); // lambda exp

Wydaje się, że wszystko dobrze, ale co jeśli Klasa Employee zapewnia również podobną metodę:

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

W tym przypadku użycie samej nazwy metody będzie bardziej jasne. Stąd możemy bezpośrednio odnosić się do metody, używając referencji metody jako:

employeeList.sort(Employee::compareByGrade); // method reference

Zgodnie z docs istnieją cztery rodzaje odniesień do metod:

+----+-------------------------------------------------------+--------------------------------------+
|    | 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                       |
+------------------------------------------------------------+--------------------------------------+
 12
Author: i_am_zero,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-03 06:07:23

:: Operator został wprowadzony w Javie 8 dla odniesień do metod. Odniesienie do metody to skrócona składnia wyrażenia lambda, które wykonuje tylko jedną metodę. Oto ogólna składnia odniesienia do metody:

Object :: methodName

Wiemy, że możemy używać wyrażeń lambda zamiast używać anonimowej klasy. Ale czasami wyrażenie lambda jest tak naprawdę tylko wywołaniem jakiejś metody, na przykład:

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

Aby Kod był jaśniejszy, możesz włączyć tę lambdę wyrażenie w odniesieniu do metody:

Consumer<String> c = System.out::println;
 4
Author: Vaibhav9518,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-22 06:18:21

:: jest znany jako odniesienia do metod. Powiedzmy, że chcemy wywołać metodę calculatePrice zakupu klasy. Następnie możemy zapisać go jako:

Purchase::calculatePrice

Może być również postrzegane jako krótka forma zapisu wyrażenia lambda, ponieważ odwołania do metod są przekształcane w wyrażenia lambda.

 3
Author: Sonu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-15 18:45:00

Podczas pracy zachowują się dokładnie tak samo.Kod bajtowy może / nie jest taki sam(dla powyższego,generuje ten sam kod bajtowy (complie above I check javaap-C;))

Podczas pracy zachowują się dokładnie tak samo.metoda (math::max);,generuje tę samą math (complie powyżej i sprawdź javap-C;))

 2
Author: Alfa khatoon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-08 13:11:17

return reduce(Math::max); jest nie równa return reduce(max());

Ale to znaczy, coś takiego:

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

Możesz po prostu zapisać 47 naciśnięć klawiszy jeśli napiszesz tak

return reduce(Math::max);//Only 9 keystrokes ^_^
 2
Author: Jude Niroshan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-23 05:57:09

W Javie-8 Streams Reducer in simple works jest funkcją, która przyjmuje dwie wartości jako dane wejściowe i zwraca wynik po pewnym obliczeniu. wynik ten jest podawany w następnej iteracji.

W przypadku funkcji Math: max, metoda zwraca max z dwóch przekazywanych wartości i w końcu masz największą liczbę w ręku.

 2
Author: Pramod,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-18 09:47:17

Ponieważ wiele odpowiedzi tutaj dobrze wyjaśniło :: zachowanie, dodatkowo chciałbym wyjaśnić, że :: operator nie musi mieć dokładnie tego samego podpisu co referujący interfejs funkcjonalny, jeśli jest używany na przykład zmienne . Załóżmy, że potrzebujemy BinaryOperator, który ma typ TestObject . W tradycyjny sposób jego zaimplementowane w ten sposób:

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

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

            return t;
        }
    };

Jak widać w implementacji anonimowej wymaga dwóch argumentów TestObject i zwraca również obiekt TestObject. Aby spełnić ten warunek za pomocą operatora :: możemy zacząć od statycznej metody:

public class TestObject {


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

A następnie zadzwoń:

BinaryOperator<TestObject> binary = TestObject::testStatic;

Ok to skompilowane dobrze. A jeśli potrzebujemy metody instancyjnej? Lets update TestObject with instance method:

public class TestObject {

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

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

Teraz możemy uzyskać dostęp do instancji jak poniżej:

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

Ten kod kompiluje się dobrze, ale Poniżej Nie:

BinaryOperator<TestObject> binary = TestObject::testInstance;

My eclipse tell me "Cannot make a static reference to the non-static metoda testInstance (TestObject, TestObject) z typu TestObject ..."

W porządku jest to metoda instancji, ale jeśli przeciążamy testInstance jak poniżej:

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

I wywołanie:

BinaryOperator<TestObject> binary = TestObject::testInstance;

Kod po prostu dobrze się skompiluje. Ponieważ wywoła testInstance z pojedynczym parametrem zamiast podwójnego. Ok więc co się stało nasze dwa parametr? Pozwala wydrukować i zobaczyć:

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

Który wyświetli:

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

Ok więc JVM jest wystarczająco inteligentny, aby zadzwonić param1testInstance (param2). Czy możemy używać testInstance z innego zasobu, ale nie TestObject, czyli:

public class TestUtil {

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

I wywołanie:

BinaryOperator<TestObject> binary = TestUtil::testInstance;

Po prostu nie będzie kompilowany, a kompilator powie: "Typ TestUtil nie definiuje testInstance (TestObject, TestObject)" . Kompilator będzie więc szukał statycznego Odniesienia, jeśli nie jest to ten sam typ. A co z polimorfizmem? Jeśli usuniemy ostateczne modyfikatory i dodamy naszą klasę SubTestObject :

public class SubTestObject extends TestObject {

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

}

I wywołanie:

BinaryOperator<TestObject> binary = SubTestObject::testInstance;

Nie będzie kompilowany, kompilator nadal będzie szukał statycznego odniesienia. Ale poniższy kod będzie dobrze kompilowany, ponieważ przechodzi test is-a: {]}

public class TestObject {

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

}

BinaryOperator<TestObject> binary = TestObject::testInstance;

*dopiero się uczę, więc zorientowałem się przez spróbuj i zobacz, nie krępuj się mnie poprawić, jeśli się mylę

 1
Author: HRgiger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-06 13:33:48

Znalazłem to źródło bardzo interesujące.

W rzeczywistości to Lambda zamienia się w dwukropek. Dwukropek jest bardziej czytelny. Wykonujemy następujące kroki:

Krok 1:

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

Krok 2:

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

Krok 3:

// The magic
Comparator c = Comparator.comparing(Person::getAge());
 1
Author: Husam Bdr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-26 05:25:20

W starszych wersjach Javy zamiast "::" lub lambd można użyć:

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

Lub przejście do metody:

public static void doSomething(Action action) {
    action.execute();
}
 1
Author: Kamil Tomasz Jarmusik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-04 15:14:48