:: operator (dwukropek) w Javie 8


956

Eksplorowałem źródło Java 8 i bardzo zaskoczyłem tę część kodu:

//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 Math::maxcoś jest jak wskaźnik metody? W jaki staticsposób konwertowana jest normalna metoda IntBinaryOperator?


60
Cukier syntaktyczny polega na tym, że kompilator automatycznie generuje implementacje interfejsu oparte na funkcji, którą udostępniasz (aby cała lambda była łatwiejsza w użyciu z istniejącymi bazami kodu).
Neet

4
java.dzone.com/articles/java-lambda-expressions-vs może pomóc, nie zagłębiałem się w temat
Pontus Backlund

8
@ Uwaga: to nie jest dokładnie „cukier składniowy”, chyba że możesz powiedzieć za co. tzn. „x jest cukrem syntaktycznym dla y”.
Ingo

6
@ Ingo tworzy nowy obiekt lambda za każdym razem, gdy go używam. TestingLambda$$Lambda$2/8460669i TestingLambda$$Lambda$3/11043253zostały utworzone na podstawie dwóch wywołań.
Narendra Pathai,

13
Lambda i odniesienia do metod nie są „zwykłymi starymi anonimowymi klasami wewnętrznymi”. Zobacz programmers.stackexchange.com/a/181743/59134 . Tak, jeśli to konieczne, nowe klasy i instancje są tworzone w locie, jeśli to konieczne, ale tylko w razie potrzeby.
Stuart Marks

Odpowiedzi:


1022

Zwykle reducemetodę wywołuje się Math.max(int, int)w następujący sposób:

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

Wymaga to dużej składni do samego wywołania Math.max. Właśnie tutaj pojawiają się wyrażenia lambda. Od wersji Java 8 można robić to samo w znacznie krótszy sposób:

reduce((int left, int right) -> Math.max(left, right));

Jak to działa? Kompilator Java „wykrywa”, że chcesz zaimplementować metodę, która akceptuje dwa intsi zwraca jeden int. Jest to równoważne z parametrami formalnymi jedynej metody interfejsu IntBinaryOperator(parametr metody, reducektórą chcesz wywołać). Kompilator zajmie się resztą za Ciebie - po prostu zakłada, że ​​chcesz zaimplementować IntBinaryOperator.

Ale ponieważ Math.max(int, int)samo spełnia wymogi formalne IntBinaryOperator, można z niego korzystać bezpośrednio. Ponieważ Java 7 nie ma żadnej składni, która pozwalałaby na przekazanie samej metody jako argumentu (możesz przekazywać tylko wyniki metody, ale nigdy nie odwołuje się do metody), ::składnia została wprowadzona w Javie 8, aby odwoływać się do metod:

reduce(Math::max);

Pamiętaj, że zostanie to zinterpretowane przez kompilator, a nie przez JVM w czasie wykonywania! Chociaż generuje różne kody bajtów 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 IntBinaryOperatorpowyższej implementacji!

(Zobacz także Tłumaczenie wyrażeń lambda )


489

::nazywa się Method Reference. Jest to w zasadzie odniesienie do pojedynczej metody. To znaczy odnosi się do istniejącej metody według nazwy.

Krótkie wyjaśnienie :
Poniżej znajduje się przykład odwołania 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);

squaremogą być przekazywane tak jak odniesienia do obiektów i uruchamiane w razie potrzeby. W rzeczywistości można go równie łatwo wykorzystać jako odniesienie do „normalnych” metod obiektów jak staticte. 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);

Functionpowyżej jest funkcjonalny interfejs . Aby w pełni zrozumieć ::, ważne jest również zrozumienie interfejsów funkcjonalnych. Mówiąc wprost, interfejs funkcjonalny to interfejs z jedną tylko metodą abstrakcyjną.

Przykłady interfejsów funkcyjnych obejmują Runnable, Callablei ActionListener.

FunctionPowyższe jest funkcjonalny interfejs z jednym sposobem: apply. Zajmuje jeden argument i daje wynik.


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

Odwołania do metod to wyrażenia, które są traktowane tak samo jak wyrażenia lambda (...), ale zamiast udostępnienia treści metody, odwołują się do istniejącej metody według nazwy.

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 squaremetody zachowują się dokładnie tak samo. Kod bajtowy może być lub nie być taki sam (choć w powyższym przypadku generowany jest ten sam kod bajtowy; skompiluj powyższy i sprawdź za pomocą javap -c).

Jedynym ważnym kryterium do spełnienia jest: podana metoda powinna mieć podobną sygnaturę jak metoda interfejsu funkcjonalnego, którego używasz jako odwołania do obiektu .

Poniższe jest nielegalne:

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

squareoczekuje argumentu i zwraca a double. getMetoda Dostawcę zwraca wartość, ale nie pobiera argumentu. To powoduje błąd.

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

Kilka innych przykładów: acceptmetoda 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 getRandomnie bierze argumentu i zwraca a double. Można więc użyć dowolnego interfejsu funkcjonalnego, który spełnia kryteria: nie argumentuj i zwracajdouble .

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 sparametryzowanych typów :

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;

Odnośniki do metod mogą mieć różne style, ale zasadniczo wszystkie one oznaczają to samo i mogą być po prostu zwizualizowane jako lambdas:

  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. Odwołanie do konstruktora klasy ( ClassName::new)
  6. Odwołanie do konstruktora tablic ( TypeName[]::new)

Więcej informacji można znaleźć na stronie http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html .


6
Dziękuję za wyjaśnienie. Podsumowując: „::” użyj do wyodrębnienia metody spełniającej interfejs FunctionalInterface (lambda): ClassX :: staticMethodX lub instanceX :: instanceMethodX ”
jessarah,

55

Tak, to prawda. Do ::odniesienia do metody służy operator. Można więc wyodrębnić metody statyczne z klas, używając go lub metod z obiektów. Ten sam operator może być używany nawet w przypadku konstruktorów. Wszystkie wymienione tutaj przypadki są zilustrowane w poniższym przykładzie kodu.

Oficjalną dokumentację Oracle można znaleźć tutaj .

Możesz mieć lepszy przegląd zmian JDK 8 w tym artykule. W sekcji Odwołanie do metody / konstruktora podano również przykład kodu:

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

dobrym wyjaśnieniem jest to, które można znaleźć tutaj: doanduyhai.wordpress.com/2012/07/14/…
Olimpiu POP

1
@RichardTingle method(Math::max);to wywołanie, a definicja metody byłaby podobna public static void method(IntBinaryOperator op){System.out.println(op.applyAsInt(1, 2));}. Tak to jest używane.
Narendra Pathai

2
Dla osób zaznajomionych z C # jest podobnie z DelegateType d = new DelegateType (MethodName);
Adrian Zanescu

27

Wydaje się, że jest trochę późno, ale oto moje dwa centy. Wyrażenie lambda służy do tworzenia metod anonimowych. Wywołuje tylko istniejącą metodę, ale wyraźniej jest odwoływać się do metody bezpośrednio po nazwie. I odniesienie metoda pozwala nam zrobić za pomocą operatora metoda-odniesienia ::.

Rozważ następującą prostą klasę, w której każdy pracownik ma nazwisko 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 jakąś metodą i chcemy posortować pracowników według ich oceny. Wiemy, że możemy skorzystać z anonimowej klasy 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 () to jakaś metoda, ponieważ:

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

Teraz wiemy, że komparator to interfejs funkcjonalny. Funkcjonalny interfejs jest jednym z dokładnie jednym abstrakcyjnej metody (choć może zawierać jeden lub więcej domyślnych lub statycznych metod). Wyrażenie Lambda zapewnia implementację, @FunctionalInterfacewięc funkcjonalny interfejs może mieć tylko jedną metodę abstrakcyjną. Możemy 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 Employeezapewnia 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 takim przypadku sama nazwa metody będzie bardziej przejrzysta. W związku z tym możemy bezpośrednio odwoływać się do metody za pomocą odwołania do metody jako:

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

Zgodnie z dokumentami 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                       |
+------------------------------------------------------------+--------------------------------------+

25

::to nowy operator zawarty w Javie 8, który służy do odwoływania się do metody istniejącej klasy. Możesz odwoływać się do metod statycznych i metod niestatycznych klasy.

W przypadku odwoływania się do metod statycznych składnia jest następująca:

ClassName :: methodName 

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

objRef :: methodName

I

ClassName :: methodName

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

Odwołania do metod, gdy zostaną ocenione, tworzą instancję interfejsu funkcjonalnego.

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


22

To jest odwołanie do metody w Javie 8. Dokumentacja wyroczni znajduje się tutaj .

Jak stwierdzono w dokumentacji ...

Odwołanie do metody Person :: CompareByAge to odwołanie do metody statycznej.

Poniżej znajduje się przykład odwołania do metody instancji określonego obiektu:

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

Odwołanie do metody myComparisonProvider :: CompareByName wywołuje metodę compareByName, która jest częścią obiektu myComparisonProvider. Środowisko JRE podaje argumenty typu metody, które w tym przypadku to (Osoba, Osoba).


2
ale metoda „CompareByAge” nie jest statyczna.
abbas

3
@abbas Nor is CompareByName. W związku z tym uzyskuje się dostęp do tych metod niestatycznych za pośrednictwem operatora odniesienia za pomocą obiektu. Gdyby były statyczne, można użyć nazwy klasy, takiej jak ComparisionProvider :: someStaticMethod
Seshadri R

6

:: Operator został wprowadzony w java 8 dla referencji metod. Odwołanie do metody to skrócona składnia wyrażenia lambda, które wykonuje tylko JEDEN sposób. Oto ogólna składnia odwołania do metody:

Object :: methodName

Wiemy, że możemy używać wyrażeń lambda zamiast 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ł bardziej przejrzysty, możesz zmienić to wyrażenie lambda w odwołanie do metody:

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

3

:: jest znane jako odniesienia do metod. Powiedzmy, że chcemy nazwać metodę wyliczenia klasy zakupu klasy. Następnie możemy napisać to jako:

Purchase::calculatePrice

Można to również postrzegać jako krótką formę pisania wyrażenia lambda, ponieważ odniesienia do metod są konwertowane na wyrażenia lambda.


Czy mogę tworzyć zagnieżdżone odniesienia do metod? np. groupingBy (Order :: customer :: name)

nie można w ten sposób utworzyć odniesienia do metody zagnieżdżonej
Kirby

3

Uważam to źródło za bardzo interesujące.

W rzeczywistości to Lambda zamienia się w dwukropek . Dwukropek jest bardziej czytelny. Postępujemy zgodnie z tymi krokami:

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 using method reference
Comparator c = Comparator.comparing(Person::getAge);

3
Wydaje się, że Person::getAge()powinno być Person::getAge.
Qwertiy,

2

return reduce(Math::max);to nie równa się doreturn reduce(max());

Ale to oznacza 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 piszesz w ten sposób

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

2

Ponieważ wiele odpowiedzi tutaj dobrze wyjaśniało ::zachowanie, dodatkowo chciałbym wyjaśnić, że :: operator nie musi mieć dokładnie takiej samej sygnatury jak odsyłający interfejs funkcjonalny, jeśli jest używany do zmiennych przykładowych . Załóżmy, że potrzebujemy BinaryOperator, który ma typ TestObject . W tradycyjny sposób zaimplementowano go w następujący sposób:

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

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

            return t;
        }
    };

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

public class TestObject {


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

a następnie zadzwoń:

BinaryOperator<TestObject> binary = TestObject::testStatic;

Ok, wszystko dobrze skompilowane. A jeśli potrzebujemy metody instancji? Pozwala zaktualizować TestObject za pomocą metody instancji:

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 jednego nie:

BinaryOperator<TestObject> binary = TestObject::testInstance;

Moje zaćmienie mówi mi: „Nie można dokonać statycznego odwołania do metody niestatystycznej testInstance (TestObject, TestObject) z typu TestObject ...”

Słusznie jest to metoda instancji, ale jeśli przeciążymy testInstancejak 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 zadzwoń:

BinaryOperator<TestObject> binary = TestObject::testInstance;

Kod po prostu dobrze się skompiluje. Ponieważ będzie wywoływał testInstancez pojedynczym parametrem zamiast podwójnym. Ok, więc co się stało z naszymi dwoma parametrami? 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;
    }
}

Co da wynik:

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

Ok, więc JVM jest wystarczająco inteligentny, aby wywołać param1.testInstance (param2). Czy możemy korzystać testInstancez innego zasobu, ale nie TestObject, tj .:

public class TestUtil {

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

I zadzwoń:

BinaryOperator<TestObject> binary = TestUtil::testInstance;

Po prostu się nie skompiluje, a kompilator powie: „Typ TestUtil nie definiuje testInstance (TestObject, TestObject)” . Tak więc kompilator będzie szukał odniesienia statycznego, jeśli nie jest tego samego typu. Ok, a co z polimorfizmem? Jeśli usuniemy końcowe modyfikatory i dodamy naszą klasę SubTestObject :

public class SubTestObject extends TestObject {

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

}

I zadzwoń:

BinaryOperator<TestObject> binary = SubTestObject::testInstance;

Nie będzie się również kompilował, kompilator będzie nadal szukał statycznego odniesienia. Ale poniższy kod skompiluje się dobrze, ponieważ jego przekazanie jest testem:

public class TestObject {

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

}

BinaryOperator<TestObject> binary = TestObject::testInstance;

* Właśnie studiuję, więc zrozumiałem, próbując zobaczyć, nie krępuj się mnie poprawić, jeśli się mylę


2

W java-8 Streams Reducer w prostych pracach jest funkcją, która przyjmuje dwie wartości jako dane wejściowe i zwraca wyniki po pewnych obliczeniach. ten wynik jest wprowadzany w następnej iteracji.

w przypadku funkcji Math: max metoda zwraca maksymalnie dwie przekazane wartości, a na końcu masz największą liczbę w ręce.


1

W środowisku wykonawczym zachowują się dokładnie tak samo. Kod bajtowy może / nie być taki sam (dla powyższego Incase generuje ten sam kod bajtowy (przestrzegaj powyżej i sprawdź javaap -c;))

W czasie wykonywania zachowują się dokładnie tak samo. Metoda (matematyka :: maks.) ;, generuje tę samą matematykę (przestrzegaj wyżej i sprawdź javap -c;))


1

W starszych wersjach Java zamiast „::” lub lambd możesz 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

Więc widzę tu mnóstwo odpowiedzi, które są szczerze zbyt skomplikowana, a to za mało powiedziane.

Odpowiedź jest dość prosta: :: nazywa się to Referencje metod https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html

Więc nie będę kopiować-wkleić, w linku można znaleźć wszystkie informacje, jeśli przewiniesz w dół do tabeli.


Teraz rzućmy okiem na to, co to jest Odniesienia do metod:

A :: B nieco zastępuje następujące wbudowane wyrażenie lambda : (parametry ...) -> AB (parametry ...)

Aby skorelować to z pytaniami, konieczne jest zrozumienie wyrażenia java lambda. Co nie jest trudne.

Wbudowane wyrażenie lambda jest podobne do zdefiniowanego interfejsu funkcjonalnego (który jest interfejsem, który ma nie więcej i nie mniej niż 1 metodę) . Zobaczmy, co mam na myśli:

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

InterfaceX musi być interfejsem funkcjonalnym. Każdy interfejs funkcjonalny, jedyne, co jest ważne w InterfaceX dla tego kompilatora, to zdefiniowanie formatu:

InterfaceX może być dowolnym z tych:

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

albo to

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

lub bardziej ogólne:

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

Weźmy pierwszy przedstawiony przypadek i wbudowane wyrażenie lambda, które zdefiniowaliśmy wcześniej.

Przed wersją Java 8 można było to zdefiniować w następujący sposób:

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

Funkcjonalnie jest to to samo. Różnica polega bardziej na tym, jak postrzega to kompilator.

Teraz, gdy przyjrzeliśmy się wbudowanemu wyrażeniu lambda, wróćmy do Referencji metod (: :). Powiedzmy, że masz taką klasę:

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

Ponieważ metoda anyFunctions ma te same typy co InterfaceX callMe , możemy je zrównoważyć za pomocą metody Reference.

Możemy to napisać w ten sposób:

InterfaceX o =  Q::anyFunction; 

i to jest równoważne z tym:

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

Fajną rzeczą i zaletą odwołań do metod jest to, że na początku, dopóki nie przypiszesz ich do zmiennych, są one bez typu. Możesz więc przekazać je jako parametry do dowolnego funkcjonalnie wyglądającego interfejsu (o takich samych zdefiniowanych typach). Co dokładnie dzieje się w twoim przypadku


1

Poprzednie odpowiedzi są dość kompletne w odniesieniu do tego, co ::robi odwołanie do metody. Podsumowując, zapewnia sposób odwoływania się do metody (lub konstruktora) bez jej wykonywania, a po ocenie tworzy instancję interfejsu funkcjonalnego zapewniającego kontekst typu docelowego.

Poniżej znajdują się dwa przykłady znalezienia obiektu o maksymalnej wartości w ArrayListZ i BEZ użycia ::odwołania do metody. Wyjaśnienia znajdują się w komentarzach poniżej.


BEZ użycia ::

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

Przy użyciu ::

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

Podwójny dwukropek tj. ::Operator jest wprowadzony w Javie 8 jako odniesienie do metody . Odwołanie do metody jest formą wyrażenia lambda, która służy do odwoływania się do istniejącej metody według jej nazwy.

nazwa klasy :: nazwa metody

dawny:-

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

Korzystając z Double Colon ::

  • stream.forEach(System.out::println(element))
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.