Является ли лямбда-выражение созданием объекта в куче каждый раз, когда он выполняется?

Когда я перебираю коллекцию с использованием нового синтаксического сахара Java 8, например

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

Разве это не эквивалентно фрагменту "старого синтаксиса" ниже?

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

Означает ли это, что новый анонимный объект Consumer создается в куче каждый раз, когда я перебираю коллекцию? Сколько занимает куча пространства? Какие последствия у него есть? Означает ли это, что я должен использовать старый стиль для циклов при итерации над большими многоуровневыми структурами данных?

Ответ 1

Он эквивалентен, но не идентичен. Проще говоря, если выражение лямбда не фиксирует значения, это будет одноэлемент, который будет использоваться повторно при каждом вызове.

Поведение не точно определено. JVM получает большую свободу, как ее реализовать. В настоящее время JVM Oracles создает (по крайней мере) один экземпляр для каждого лямбда-выражения (т.е. Не передает экземпляр между разными идентичными выражениями), но создает singleton для всего выражения, которое не записывает значения.

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


Это охватывается Спецификацией языка Java®, главой " 15.27.4. Оценка времени лямбда-выражений во время исполнения"

Обобщенная:

Эти правила призваны обеспечить гибкость реализации Java-языка программирования, в том что:

  • При каждой оценке не нужно выделять новый объект.

  • Объекты, созданные разными лямбда-выражениями, не должны принадлежать к разным классам (если тела идентичны, например).

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

  • Если доступен "существующий экземпляр", он не должен быть создан при предыдущей оценке лямбда (например, он мог быть выделен во время инициализации закрывающего класса).

Ответ 2

Когда экземпляр, представляющий лямбду, создается чутко зависит от точного содержимого вашего тела лямбды. А именно, ключевым фактором является то, что лямбда захватывает лексическую среду. Если он не фиксирует какое-либо состояние, которое является переменным от создания к созданию, то экземпляр не создается каждый раз, когда вводится каждый цикл. Вместо этого во время компиляции будет создан синтетический метод, и сайт лямбда-использования просто получит объект singleton, который делегирует этот метод.

Обратите внимание, что этот аспект зависит от реализации, и вы можете ожидать будущих усовершенствований и улучшений в HotSpot для повышения эффективности. Существуют общие планы, например, сделать легкий объект без полного соответствующего класса, который имеет достаточно информации для пересылки на один метод.

Вот хорошая, доступная подробная статья по теме:

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

Ответ 3

Вы передаете новый экземпляр методу forEach. Каждый раз, когда вы делаете это, вы создаете новый объект, но не один для каждой итерации цикла. Итерация выполняется внутри метода forEach, используя тот же экземпляр объекта обратного вызова, пока это не будет выполнено с помощью цикла.

Таким образом, память, используемая циклом, не зависит от размера коллекции.

Разве это не эквивалентно фрагменту "старого синтаксиса"?

Да. У этого есть небольшие различия на очень низком уровне, но я не думаю, что Вы должны заботиться о них. В выражениях Lamba вместо анонимных классов используется функция invokedynamic.