Чи створює лямбда-вираз об'єкт на купі кожен раз, коли він виконується?


182

Коли я повторюю колекцію, використовуючи новий синтаксичний цукор Java 8, наприклад

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

Хіба це не еквівалент фрагменту "старого синтаксису" нижче?

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

Чи означає це, що новий анонімний Consumerоб’єкт створюється на купі кожного разу, коли я повторюю колекцію? Скільки місця займає купа? Які наслідки це має? Чи означає це, що я повинен використовувати старий стиль для циклів під час ітерації над великими багаторівневими структурами даних?


48
Коротка відповідь: ні. Для лямбдатів без громадянства (тих, що нічого не фіксують зі свого лексичного контексту), колись буде створено (ліниво) лише один екземпляр та кеш на місці захоплення. (Ось як працює реалізація; специфікація була ретельно написана, щоб дозволити, але не вимагати такого підходу.)
Брайан Гец

Відповіді:


158

Вона рівнозначна, але не тотожна. Простіше кажучи, якщо лямбда-вираз не фіксує значення, це буде синглтон, який повторно використовується при кожному виклику.

Поведінка точно не вказана. JVM надає велику свободу щодо її реалізації. В даний час JVM Oracle створює (принаймні) один екземпляр на лямбда-вираз (тобто не поділяє екземпляр між різними однаковими виразами), але створює одинакові для всіх виразів, які не фіксують значення.

Ви можете прочитати цю відповідь для отримання більш детальної інформації. Там я не тільки дав більш детальний опис, але і код тестування, щоб спостерігати за поточною поведінкою.


Це охоплено специфікацією мови Java®, розділ “ 15.27.4. Оцінка часу лямбда-виразів »

Узагальнено:

Ці правила мають на меті запропонувати гнучкість для реалізації мови програмування Java таким чином:

  • Новий об'єкт не повинен виділятися при кожному оцінюванні.

  • Об'єкти, що утворюються за допомогою різних лямбдаських виразів, не повинні належати до різних класів (якщо тіла, наприклад, однакові).

  • Кожен об'єкт, отриманий за допомогою оцінки, не повинен належати до одного класу (наприклад, захоплені локальні змінні можуть бути накреслені).

  • Якщо "наявний екземпляр" доступний, він не повинен бути створений при попередній оцінці лямбда (він може бути виділений під час ініціалізації класу, що вкладається, наприклад).


24

Коли екземпляр, що представляє лямбда, створюється чутливо, залежить від точного вмісту тіла вашої лямбда. А саме, ключовим фактором є те, що лямбда захоплює з лексичного середовища. Якщо він не фіксує жодного стану, який є змінним від створення до створення, то екземпляр не буде створюватися щоразу, коли буде введено цикл для кожного. Натомість синтетичний метод буде створений під час компіляції, і сайт використання лямбда просто отримає однотонний об'єкт, який делегує цей метод.

Далі зауважте, що цей аспект залежить від впровадження, і ви можете очікувати майбутніх вдосконалень та вдосконалення HotSpot для підвищення ефективності. Існують загальні плани, наприклад, зробити легкий об’єкт без повного відповідного класу, який має достатньо інформації для переходу до одного методу.

Ось хороша, доступна поглиблена стаття на тему:

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


0

Ви передаєте forEachметоду новий екземпляр . Кожен раз, коли ви робите це, ви створюєте новий об'єкт, але не один для кожної ітерації циклу. Ітерація проводиться всередині forEachметоду, використовуючи той самий екземпляр об'єкта 'callback', поки це не робиться з циклом.

Отже, пам'ять, що використовується циклом, не залежить від розміру колекції.

Хіба це не еквівалент фрагменту "старого синтаксису"?

Так. Він має незначні відмінності на дуже низькому рівні, але я не думаю, що вам слід їх дбати. Ламб-вирази використовують анкетінамічну функцію замість анонімних класів.


Чи є у вас документи, які це вказують? Це досить цікава оптимізація.
А. Рама

Дякую, але що робити, якщо у мене є колекція колекцій колекцій, наприклад, коли я виконую глибокий пошук по структурі даних дерева?
Bastian Voigt

2
@ A.Rama Вибачте, я не бачу оптимізації. Це те саме з лямбдами або без них і з або без циклу для кожного.
aalku

1
Це насправді не те саме, але все ж максимум один об’єкт на рівні гніздування буде потрібен у будь-який час, що є незначним. Кожна нова ітерація внутрішнього циклу створюватиме новий об’єкт, швидше за все, захоплюючи поточний елемент із зовнішнього циклу. Це створює певний тиск GC, але все-таки нема чого турбуватися.
Марко Топольник

4
@aalku: " Кожен раз, коли ви робите це, створюєте новий об'єкт ": Не відповідно до цієї відповіді Холгера та цього коментаря Брайана Геца .
Лій
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.