Мне интересно, что компилятор делает за сценой, когда я пишу for loop
как это
1.
for(ChildObject child : parentObject.getChildObjects()){
//do something
}
вместо
2.
List<ChildObject> myList = parentObject.getChildObjects();
for(ChildObject child : myList){
//do something
}
Оператор parentObject.getChildObjects()
является инструкцией JPA, а тип выборки LAZY.
ПРИМЕЧАНИЕ. Я прочитал java-документ for-each
цикла (http://docs.oracle.com/javase/1.5.0/docs/guide/language/foreach.html), однако они не упомянули случай, как указано выше,
Поэтому я знаю, как работает цикл, но не знаю, что происходит с списком, возвращаемым из parentObject.getChildObjects()
.
Мои сомнения:
В первом случае это компилятор, parentObject.getChildObjects()
список, возвращаемый из parentObject.getChildObjects()
в некоторой временной переменной, которая скрыта от программиста или вызывает вызов каждый раз?
Если он вызывает оператор parentObject.getChildObjects()
, каждый раз; Как он отслеживает следующие элементы?
То, что упомянуто выше, что именно происходит за сценой. Чтобы добавить к его ответу:
В обоих case 1 & 2
компилятор создаст iterator
и сохранит его.
Разница между двумя подходами такова:
Дело 1:
for loop
преобразуется в
for(Iterator<ChildObject> it = parentObject.getChildObjects.iterator; it.hasNext();){
}
ПРИМЕЧАНИЕ: parentObject.getChildObjects
, возвращает List
который реализует интерфейс Iterable
. Ни ParentObject
ни ChildObject
реализует Iterable
.
Это важно знать, как в примере "dit", его класс реализует интерфейс Iterable.
On Stack: сохраняется только
Iterator
.
Случай 2:
for loop
преобразуется в
List<ChildObject> myList = parentObject.getChildObjects;
for(Iterator<ChildObject> it = myList.iterator; it.hasNext();){
}
On Stack:
iterator
сохраняется, иmyList
сохраняется.
Вывод:
Случай 1 лучше, чем 2, если считать memory usage
, в противном случае оба подхода одинаковы.
Поскольку проблемы, связанные с использованием памяти, были затронуты, я думаю, стоит упомянуть, что на практике маловероятно, что вы когда-нибудь заметите разницу, так как дополнительные astore
и aload
которые может быть aload
компилятором, будут в основном оптимизированы во время выполнения.
Разница в том, что в случае 2 вы можете сделать что-то вроде этого:
List<ChildObject> myList = parentObject.getChildObjects;
for(Iterator<ChildObject> it = myList.iterator; it.hasNext();){
...
}
...
ChildObject ob = myList.get(0);
Теперь это не то, что вы должны делать, и почему случай 1 почти всегда предпочтительнее. Более подробно об этом можно прочитать в разделе 45 (свести к минимуму объем локальных переменных) во втором выпуске "Эффективная Java".
For-Each Loop работает со всеми классами, реализующими Iterable- интерфейс, для чего требуется iterator()
- операция для преобразования вашего синтаксиса for-each в цикл итератора:
public class ChildObject {
[...]
}
public class ParentObject implements Iterable<ChildObject> {
private List<ChildObject> childObjects;
[...]
@Override
public Iterator<ChildObject> iterator() {
return childObjects.iterator();
}
}
public static void main(String[] args){
ParentObject p = new ParentObject();
[...]
for(ChildObject child : p){
System.out.println(child);
}
}
Так
for(ChildObject child : p)
будет преобразовано в:
for(Iterator<ChildObject> it = p.iterator(); it.hasNext();){
ChildObject child = it.next();
[...]
}
Вот почему это не имеет значения, как вы кодируете свой первый цикл.
PS, если вы перебираете примитивный массив, такой как массив int[] array
, компилятор преобразует ваш код в простую итерацию по контуру массива
for(int i = 0; i < array.length; i++)
Для получения дополнительной информации проверьте комментарии к своему вопросу.
parentObject.getChildObjects()
преобразуется в Iterator<childObject> it = parentObject.getChildObjects().iterator
? ... Итак, вкратце, мы сохраняем итератор, а не весь список ... Поэтому я могу сказать, что подход 1 лучше, чем 2, если мы рассмотрим использование памяти?
list.iterator()
.
javap
для декомпиляции вашего класса, чтобы увидеть разницу.