Zwykle część czasu wykonywania implementacji lambda generuje klasę, która zasadniczo składa się z pojedynczej metody implementacji. Informacje potrzebne do wygenerowania takiej klasy są podane przez wywołanie metody bootstrap do LambdaMetafactory.metafactory
w czasie wykonywania.
Po włączeniu opcji Serializacja rzeczy stają się bardziej skomplikowane. Po pierwsze, skompilowany kod użyje alternatywnej metody bootstrapowej, która zapewnia większą elastyczność po cenie konieczności analizowania parametrów varargs zgodnie z flagami określonymi w tablicy parametrów.
Następnie generowane klasy lambda musi mieć writeReplace
metody (patrz druga połowa Serializable
documentation), która ma utworzyć i powrotu wystąpienie SerializedLambda
zawierający wszystkie informacje wymagane do odtworzenia wystąpienie lambda. Ponieważ metoda pojedynczej implementacji klasy lambda składa się tylko z prostego wywołania delegowania, ta metoda writeReplace
i związana z nią stała informacja będą zwielokrotniać rozmiar wygenerowanej klasy.
Warto również zauważyć, że klasa tworzenia tej Serializable instancji lambda będzie miał syntetyczna metoda $deserializeLambda$
(porównaj class documentation of SerializedLambda
jako odpowiednik procesu lambda na writeReplace
. To zwiększy wykorzystanie klas dysku i czas ładowania (ale nie wpływ na ocenę wyrażeń lambda).
W przykładzie kodu, obie metody mogą być naruszone przez tę samą ilość czasu jak ładowaniem i generowania klasy zdarza się tylko raz w wyrażeniu lambda. na kolejnych ocenach, the class generated on the first evaluation will be re-used and only a new instance created (if not even the instance is re-used) Mówimy tu o jednorazowym obciążeniu, nawet gdy baranek Wyrażenie da znajduje się w pętli, wpływa tylko na pierwszą iterację.
Pamiętaj, że jeśli masz wyrażenia lambda w pętli, nie może być nowy instancja stworzony dla każdej iteracji mając ją na zewnątrz pętli będzie na pewno mieć jedno wystąpienie podczas całej pętli. Ale to zachowanie nie zależy od tego, czy interfejs docelowy to Serializable
. Zależy tylko od tego, czy wyrażenie zawiera wartość (porównaj z this answer).
Zauważ, że jeśli napisał
final long toAdd = 1;
MyFunction<Long, Long> adder = (number) -> number + toAdd;
w drugiej metodzie (zanotować wyraźny final
modyfikator) wartość toAdd
byłaby stała kompilacji-czas i wyrażenie zostanie skompilowany jak gdybyś napisał (number) -> number + 1
, tzn. nie będzie więcej przechwytywać wartości. Wtedy otrzymalibyśmy tę samą instancję lambda w każdej iteracji pętli (z aktualną wersją JVM Oracle). Zatem pytanie, czy nowa instancja jest tworzona, czasami zależy od małych bitów kontekstu. Ale zazwyczaj wpływ na wydajność jest raczej niewielki.
Dziękuję za miłe wyjaśnienie. –
To wyjaśnienie nie stanowi problemu. Deserializujące obiekty będą zawsze używać kosztownego Odbicia niezależnie od tego, czy używasz wyrażeń lambda, czy nie. Ale kod pytania nie deserializuje niczego. – Holger
Pytanie dotyczyło serializacji i używania serializowanych lambd, w przeciwieństwie do innych typów lamdbas. OP pytał, która część cyklu życia tych obiektów jest mniej wydajna niż normalne lambdy. Jego kod odróżnia się poprawnie między inwokacją a instancją, a jego pytanie bezpośrednio to prosi. Jak wskazano w odpowiedzi, dotyczy to dokładnie jednego z jego przypadków. Deserializowanie lambdas ma/bardzo różne rzeczy/niż deserializowanie innych obiektów ("specjalne odbicie") i jest z natury droższe. – BadZen