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::max
coś jest jak wskaźnik metody? W jaki static
sposób konwertowana jest normalna metoda IntBinaryOperator
?
TestingLambda$$Lambda$2/8460669
iTestingLambda$$Lambda$3/11043253
zostały utworzone na podstawie dwóch wywołań.Odpowiedzi:
Zwykle
reduce
metodę wywołuje sięMath.max(int, int)
w następujący sposób: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:Jak to działa? Kompilator Java „wykrywa”, że chcesz zaimplementować metodę, która akceptuje dwa
int
si zwraca jedenint
. Jest to równoważne z parametrami formalnymi jedynej metody interfejsuIntBinaryOperator
(parametr metody,reduce
któ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 formalneIntBinaryOperator
, 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: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
IntBinaryOperator
powyższej implementacji!(Zobacz także Tłumaczenie wyrażeń lambda )
źródło
::
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:
square
mogą 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 jakstatic
te. Na przykład:Function
powyż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
,Callable
iActionListener
.Function
Powyższe jest funkcjonalny interfejs z jednym sposobem:apply
. Zajmuje jeden argument i daje wynik.Powodem, dla którego
::
s są niesamowite, jest to, że :Np. Zamiast pisać ciało lambda
Możesz po prostu zrobić
W czasie wykonywania te dwie
square
metody 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:
square
oczekuje argumentu i zwraca adouble
.get
Metoda 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:
accept
metoda w Consumer pobiera dane wejściowe, ale niczego nie zwraca.Powyżej
getRandom
nie bierze argumentu i zwraca adouble
. Można więc użyć dowolnego interfejsu funkcjonalnego, który spełnia kryteria: nie argumentuj i zwracajdouble
.Inny przykład:
W przypadku sparametryzowanych typów :
Odnośniki do metod mogą mieć różne style, ale zasadniczo wszystkie one oznaczają to samo i mogą być po prostu zwizualizowane jako lambdas:
ClassName::methName
)instanceRef::methName
)super::methName
)ClassName::methName
)ClassName::new
)TypeName[]::new
)Więcej informacji można znaleźć na stronie http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html .
źródło
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:
źródło
method(Math::max);
to wywołanie, a definicja metody byłaby podobnapublic static void method(IntBinaryOperator op){System.out.println(op.applyAsInt(1, 2));}
. Tak to jest używane.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ń.
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:
gdzie getDummyEmployee () to jakaś metoda, ponieważ:
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ę,
@FunctionalInterface
więc funkcjonalny interfejs może mieć tylko jedną metodę abstrakcyjną. Możemy użyć wyrażenia lambda jako:Wydaje się, że wszystko dobrze, ale co, jeśli klasa
Employee
zapewnia również podobną metodę: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:
Zgodnie z dokumentami istnieją cztery rodzaje odniesień do metod:
źródło
::
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:
W przypadku odwoływania się do metod niestatycznych składnia to
I
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
źródło
To jest odwołanie do metody w Javie 8. Dokumentacja wyroczni znajduje się tutaj .
Jak stwierdzono w dokumentacji ...
źródło
:: 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:
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:
Aby kod był bardziej przejrzysty, możesz zmienić to wyrażenie lambda w odwołanie do metody:
źródło
:: jest znane jako odniesienia do metod. Powiedzmy, że chcemy nazwać metodę wyliczenia klasy zakupu klasy. Następnie możemy napisać to jako:
Można to również postrzegać jako krótką formę pisania wyrażenia lambda, ponieważ odniesienia do metod są konwertowane na wyrażenia lambda.
źródło
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:
KROK 2:
KROK 3:
źródło
Person::getAge()
powinno byćPerson::getAge
.return reduce(Math::max);
to nie równa się doreturn reduce(max());
Ale to oznacza coś takiego:
Możesz po prostu zapisać 47 naciśnięć klawiszy, jeśli piszesz w ten sposób
źródło
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: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:a następnie zadzwoń:
Ok, wszystko dobrze skompilowane. A jeśli potrzebujemy metody instancji? Pozwala zaktualizować TestObject za pomocą metody instancji:
Teraz możemy uzyskać dostęp do instancji, jak poniżej:
Ten kod kompiluje się dobrze, ale poniżej jednego nie:
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
testInstance
jak poniżej:I zadzwoń:
Kod po prostu dobrze się skompiluje. Ponieważ będzie wywoływał
testInstance
z pojedynczym parametrem zamiast podwójnym. Ok, więc co się stało z naszymi dwoma parametrami? Pozwala wydrukować i zobaczyć:Co da wynik:
Ok, więc JVM jest wystarczająco inteligentny, aby wywołać param1.testInstance (param2). Czy możemy korzystać
testInstance
z innego zasobu, ale nie TestObject, tj .:I zadzwoń:
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 :
I zadzwoń:
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:
źródło
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.
źródło
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;))
źródło
W starszych wersjach Java zamiast „::” lub lambd możesz użyć:
Lub przejście do metody:
źródło
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 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:
albo to
lub bardziej ogólne:
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:
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ę:
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:
i to jest równoważne z tym:
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
źródło
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
ArrayList
Z i BEZ użycia::
odwołania do metody. Wyjaśnienia znajdują się w komentarzach poniżej.BEZ użycia
::
Przy użyciu
::
źródło
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))
źródło