Musisz dokonać rozróżnienia między częstymi wykonaniami tej samej strony wywołań , dla bezstanowych lambda lub stanowych lambd, a częstym użyciem odwołania do metody do tej samej metody (przez różne strony wywołań).
Spójrz na następujące przykłady:
Runnable r1=null;
for(int i=0; i<2; i++) {
Runnable r2=System::gc;
if(r1==null) r1=r2;
else System.out.println(r1==r2? "shared": "unshared");
}
Tutaj ta sama strona wywołania jest wykonywana dwa razy, tworząc bezstanową lambdę, a bieżąca implementacja zostanie wydrukowana "shared".
Runnable r1=null;
for(int i=0; i<2; i++) {
Runnable r2=Runtime.getRuntime()::gc;
if(r1==null) r1=r2;
else {
System.out.println(r1==r2? "shared": "unshared");
System.out.println(
r1.getClass()==r2.getClass()? "shared class": "unshared class");
}
}
W tym drugim przykładzie ta sama witryna wywołania jest wykonywana dwa razy, tworząc lambdę zawierającą odwołanie do Runtimeinstancji, a bieżąca implementacja zostanie wydrukowana "unshared"ale "shared class".
Runnable r1=System::gc, r2=System::gc;
System.out.println(r1==r2? "shared": "unshared");
System.out.println(
r1.getClass()==r2.getClass()? "shared class": "unshared class");
W przeciwieństwie do tego w ostatnim przykładzie są dwie różne witryny wywołań generujące równoważne odwołanie do metody, ale od tego 1.8.0_05momentu zostaną wydrukowane "unshared"i "unshared class".
Dla każdego wyrażenia lambda lub odwołania do metody kompilator wyemituje invokedynamicinstrukcję, która odwołuje się do metody ładowania początkowego dostarczonej przez środowisko JRE w klasie LambdaMetafactoryoraz statycznych argumentów niezbędnych do utworzenia żądanej klasy implementacji lambda. To, co produkuje fabryka meta, pozostawia faktycznemu środowisku JRE, ale jest to określone zachowanie invokedynamicinstrukcji, aby zapamiętać i ponownie użyć CallSiteinstancji utworzonej przy pierwszym wywołaniu.
Bieżące środowisko JRE tworzy obiekt stały ConstantCallSitezawierający a MethodHandledla bezstanowych lambd (i nie ma żadnego powodu, aby robić to inaczej). A odwołania do staticmetod są zawsze bezstanowe. Tak więc w przypadku lambd bezstanowych i witryn z pojedynczym wywołaniem odpowiedź musi brzmieć: nie buforuj, JVM zrobi, a jeśli nie, musi mieć mocne powody, których nie powinieneś przeciwdziałać.
W przypadku lambd mających parametry i this::funcjest to lambda, które ma odniesienie do thisinstancji, sytuacja wygląda nieco inaczej. Środowisko JRE może je buforować, ale oznaczałoby to utrzymywanie pewnego rodzaju Mapmiędzy rzeczywistymi wartościami parametrów a wynikową lambdą, co mogłoby być bardziej kosztowne niż tylko ponowne utworzenie tej prostej strukturalnej instancji lambda. Bieżące środowisko JRE nie buforuje wystąpień lambda mających stan.
Ale to nie znaczy, że klasa lambda jest tworzona za każdym razem. Oznacza to po prostu, że rozwiązana witryna wywołania będzie zachowywać się jak zwykła konstrukcja obiektu tworząca instancję klasy lambda, która została wygenerowana przy pierwszym wywołaniu.
Podobne rzeczy dotyczą odwołań do metod do tej samej metody docelowej utworzonej przez różne witryny wywołań. JRE może współdzielić między sobą jedną instancję lambda, ale w obecnej wersji nie, najprawdopodobniej dlatego, że nie jest jasne, czy utrzymanie pamięci podręcznej się opłaci. Tutaj nawet wygenerowane klasy mogą się różnić.
Zatem buforowanie, jak w twoim przykładzie, może sprawić, że twój program będzie robił inne rzeczy niż bez. Ale niekoniecznie bardziej wydajne. Obiekt buforowany nie zawsze jest bardziej wydajny niż obiekt tymczasowy. O ile naprawdę nie mierzysz wpływu na wydajność spowodowanego przez tworzenie lambda, nie powinieneś dodawać żadnego buforowania.
Myślę, że istnieją tylko specjalne przypadki, w których buforowanie może być przydatne:
- mówimy o wielu różnych witrynach z połączeniami odnoszących się do tej samej metody
- lambda jest tworzona w konstruktorze / klasie inicjalizacji, ponieważ później strona użycia będzie
- być wywoływane przez wiele wątków jednocześnie
- cierpią z powodu słabszej wydajności pierwszej inwokacji