Czy wyrażenie lambda tworzy obiekt na stercie za każdym razem, gdy jest wykonywany?

182

Gdy iteruję kolekcję przy użyciu nowego cukru syntaktycznego Java 8, takiego jak

myStream.forEach(item -> {
  // do something useful
});

Czy nie jest to równoważne z fragmentem „starej składni” poniżej?

myStream.forEach(new Consumer<Item>() {
  @Override
  public void accept(Item item) {
    // do something useful
  }
});

Czy to oznacza, że Consumerza każdym razem, gdy iteruję kolekcję, tworzony jest nowy anonimowy obiekt? Ile miejsca to zajmuje? Jakie ma to wpływ na wydajność? Czy to oznacza, że ​​powinienem raczej używać starego stylu dla pętli podczas iteracji w dużych wielopoziomowych strukturach danych?

Bastian Voigt
źródło
48
Krótka odpowiedź: nie. W przypadku bezpaństwowych lambd (tych, które nie wychwytują niczego ze swojego kontekstu leksykalnego), tylko jedno wystąpienie zostanie utworzone (leniwie) i zapisane w pamięci podręcznej w miejscu przechwytywania. (Tak działa implementacja; specyfikacja została starannie napisana, aby zezwolić na to podejście, ale nie wymagać).
Brian Goetz,

Odpowiedzi:

158

Jest równoważny, ale nie identyczny. Mówiąc prosto, jeśli wyrażenie lambda nie przechwytuje wartości, będzie to singleton, który jest ponownie wykorzystywany przy każdym wywołaniu.

Zachowanie nie jest dokładnie określone. JVM ma dużą swobodę w implementacji. Obecnie JVM Oracle tworzy (przynajmniej) jedną instancję na wyrażenie lambda (tzn. Nie dzieli instancji między różnymi identycznymi wyrażeniami), ale tworzy singletony dla wszystkich wyrażeń, które nie przechwytują wartości.

Możesz przeczytać tę odpowiedź, aby uzyskać więcej informacji. Tam podałem nie tylko bardziej szczegółowy opis, ale także przetestowałem kod, aby zaobserwować obecne zachowanie.


Jest to objęte specyfikacją języka Java®, rozdział „ 15.27.4. Ocena wyrażeń lambda w czasie wykonywania

Streszczony:

Reguły te mają na celu zapewnienie elastyczności implementacji języka programowania Java, ponieważ:

  • Nowy obiekt nie musi być przydzielany przy każdej ocenie.

  • Obiekty wytworzone przez różne wyrażenia lambda nie muszą należeć do różnych klas (na przykład jeśli ciała są identyczne).

  • Każdy obiekt wytworzony przez ocenę nie musi należeć do tej samej klasy (na przykład przechwycone zmienne lokalne mogą być wstawiane).

  • Jeśli dostępna jest „istniejąca instancja”, nie musiała zostać utworzona podczas poprzedniej oceny lambda (mogła zostać przydzielona na przykład podczas inicjalizacji klasy obejmującej).

Holger
źródło
24

Kiedy instancja reprezentująca lambda jest tworzona z wyczuciem, zależy od dokładnej zawartości twojego ciała lambda. Mianowicie, kluczowym czynnikiem jest to, co lambda wychwytuje ze środowiska leksykalnego. Jeśli nie przechwytuje żadnego stanu, który jest zmienny od stworzenia do stworzenia, instancja nie zostanie utworzona za każdym razem, gdy zostanie wprowadzona pętla dla każdego z nich. Zamiast tego w czasie kompilacji zostanie wygenerowana metoda syntetyczna, a witryna użytkowania lambda otrzyma po prostu obiekt singletonu, który deleguje tę metodę.

Ponadto należy pamiętać, że ten aspekt jest zależny od implementacji i można oczekiwać przyszłych udoskonaleń i ulepszeń HotSpot w kierunku większej wydajności. Istnieją ogólne plany np. Stworzenia lekkiego obiektu bez pełnej odpowiedniej klasy, która ma wystarczającą ilość informacji do przekazania do jednej metody.

Oto dobry, dostępny dogłębny artykuł na ten temat:

http://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood

Marko Topolnik
źródło
0

Przekazujesz nową instancję do forEachmetody. Za każdym razem, gdy to robisz, tworzysz nowy obiekt, ale nie jeden dla każdej iteracji pętli. Iteracja odbywa się w forEachmetodzie przy użyciu tej samej instancji obiektu „callback”, dopóki nie zostanie wykonana w pętli.

Tak więc pamięć używana przez pętlę nie zależy od wielkości kolekcji.

Czy to nie jest równoważne fragmentowi „starej składni”?

Tak. Ma niewielkie różnice na bardzo niskim poziomie, ale nie sądzę, żebyś się nimi przejmował. Wyrażenia Lamba używają funkcji invokedynamic zamiast klas anonimowych.

aalku
źródło
Czy masz dokumentację, która to określa? To całkiem interesująca optymalizacja.
A. Rama,
Dzięki, ale co, jeśli mam kolekcję kolekcji kolekcji, na przykład podczas głębokiego przeszukiwania struktury danych drzewa?
Bastian Voigt,
2
@ A.Rama Przepraszamy, nie widzę optymalizacji. Tak samo jest z lambdas lub bez i z lub bez pętli forEach.
aalku
1
To nie jest tak naprawdę to samo, ale w każdym momencie potrzebny będzie najwyżej jeden obiekt na poziom zagnieżdżenia, co jest znikome. Każda nowa iteracja wewnętrznej pętli utworzy nowy obiekt, najprawdopodobniej przechwytując bieżący element z zewnętrznej pętli. Stwarza to pewną presję GC, ale wciąż nie ma się czym martwić.
Marko Topolnik,
4
@aalku: „ Za każdym razem, gdy tworzysz nowy obiekt ”: Nie zgodnie z odpowiedzią Holgera i komentarzem Briana Goetza .
Lii