자바에는 for-each 문이라고도 불리는 향상된 for문이 있다.
배열이나 컬렉션에 있는 요소들을 순회할 때 사용하는 문법으로, 기존의 for 문보다 더 간결하게 코드를 작성할 수 있다.
List와 Set 인터페이스에는 Iterator<E> 타입을 반환하는 iterator 추상 메소드가 정의되어 있다.
ArrayList 와 같은 실제 구현 클래스에서는 Itr 이라는 내부 클래스를 구현한 후 아래와 같이 Itr 객체를 반환하는 식으로 iterator() 메소드를 구현한다.
public Iterator<E> iterator() {
return new Itr();
}
ArrayList 내에 구현되는 Itr 클래스의 실제 내용 중 일부는 다음과 같다.
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
// prevent creating a synthetic constructor
Itr() {}
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
...
}
hasNext() 와 next() 메소드를 가지고 있으며 hasNext() 에서는 데이터의 사이트와 현재 데이터를 가리키고 있는 포인터인 cursor 가 동일한지 확인하고 있고, next() 에서는 E 타입의 데이터를 반환하고 있다.
개발자는 ArrayList 에서 iterator() 메소드를 호출할 때 내부 클래스인 Itr 에 대한 객체가 생성되고 반환이 된다.
일반적으로 컬렉션에 있는 데이터를 순회할 때는 Iterator<E> 타입의 이터레이터를 선언한 후 해당 이터레이터에서 next() 메소드를 사용하여 데이터를 가져오게 된다.
import java.util.ArrayList;
import java.util.Iterator;
public class Test {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<String>();
for(int i=0; i<10; i++) {
arrayList.add("element-" + Integer.toString(i));
}
for(Iterator<String> iterator = arrayList.iterator(); iterator.hasNext(); ) {
System.out.println(iterator.next());
}
}
}
$ javac Test.java
$ java Test
element-0
element-1
element-2
element-3
element-4
element-5
element-6
element-7
element-8
element-9
컬렉션에서 향상된 for 문을 사용할 때는 : 을 구분사로 사용하여 왼쪽에는 데이터 타입과 변수명, 오른쪽에는 컬렉션 객체를 명시하면 된다.
import java.util.ArrayList;
import java.util.Iterator;
public class Test {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<String>();
for(int i=0; i<10; i++) {
arrayList.add("element-" + Integer.toString(i));
}
for(String str : arrayList) {
System.out.println(str);
}
}
}
$ javac Test.java
$ java Test
element-0
element-1
element-2
element-3
element-4
element-5
element-6
element-7
element-8
element-9
그럼 일반 for 문과 향상된 for 문의 코드 실행에서의 차이점은 무엇일까?
코드 디스어셈블을 통해 바이트 코드를 확인해보자
//일반 for 문 바이트 코드
{
public Test();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 4: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: new #2 // class java/util/ArrayList
3: dup
4: invokespecial #3 // Method java/util/ArrayList."<init>":()V
7: astore_1
8: iconst_0
9: istore_2
10: iload_2
11: bipush 10
13: if_icmpge 36
16: aload_1
17: iload_2
18: invokestatic #4 // Method java/lang/Integer.toString:(I)Ljava/lang/String;
21: invokedynamic #5, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
26: invokevirtual #6 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
29: pop
30: iinc 2, 1
33: goto 10
36: aload_1
37: invokevirtual #7 // Method java/util/ArrayList.iterator:()Ljava/util/Iterator;
40: astore_2
41: aload_2
42: invokeinterface #8, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z
47: ifeq 68
50: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
53: aload_2
54: invokeinterface #10, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
59: checkcast #11 // class java/lang/String
62: invokevirtual #12 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
65: goto 41
68: return
LineNumberTable:
line 6: 0
line 8: 8
line 9: 16
line 8: 30
line 13: 36
line 14: 50
line 16: 68
StackMapTable: number_of_entries = 4
frame_type = 253 /* append */
offset_delta = 10
locals = [ class java/util/ArrayList, int ]
frame_type = 250 /* chop */
offset_delta = 25
frame_type = 252 /* append */
offset_delta = 4
locals = [ class java/util/Iterator ]
frame_type = 250 /* chop */
offset_delta = 26
}
SourceFile: "Test.java"
InnerClasses:
public static final #70= #69 of #73; // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
0: #30 REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
Method arguments:
#31 element-\u0001
//향상된 for 문 바이트 코드
{
public Test();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 4: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: new #2 // class java/util/ArrayList
3: dup
4: invokespecial #3 // Method java/util/ArrayList."<init>":()V
7: astore_1
8: iconst_0
9: istore_2
10: iload_2
11: bipush 10
13: if_icmpge 36
16: aload_1
17: iload_2
18: invokestatic #4 // Method java/lang/Integer.toString:(I)Ljava/lang/String;
21: invokedynamic #5, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
26: invokevirtual #6 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
29: pop
30: iinc 2, 1
33: goto 10
36: aload_1
37: invokevirtual #7 // Method java/util/ArrayList.iterator:()Ljava/util/Iterator;
40: astore_2
41: aload_2
42: invokeinterface #8, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z
47: ifeq 70
50: aload_2
51: invokeinterface #9, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
56: checkcast #10 // class java/lang/String
59: astore_3
60: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream;
63: aload_3
64: invokevirtual #12 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
67: goto 41
70: return
LineNumberTable:
line 6: 0
line 8: 8
line 9: 16
line 8: 30
line 13: 36
line 14: 60
line 15: 67
line 16: 70
StackMapTable: number_of_entries = 4
frame_type = 253 /* append */
offset_delta = 10
locals = [ class java/util/ArrayList, int ]
frame_type = 250 /* chop */
offset_delta = 25
frame_type = 252 /* append */
offset_delta = 4
locals = [ class java/util/Iterator ]
frame_type = 250 /* chop */
offset_delta = 28
}
SourceFile: "Test.java"
InnerClasses:
public static final #70= #69 of #73; // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
0: #30 REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
Method arguments:
#31 element-\u0001
두 코드의 바이트 코드를 확인해보면 향상된 for 문의 아래 코드부분을 제외하고는 별다른 차이점이 없다.
60: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream;
해당 바이트 코드는 println() 메소드에 변수를 썼냐 안썼냐의 차이로 생성되는 것이므로, 아래와 같이 일반 for 문 코드에 따로 String 타입의 변수를 선언하면 완전히 동일한 바이트 코드가 생성된다.
import java.util.ArrayList;
import java.util.Iterator;
public class Test {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<String>();
for(int i=0; i<10; i++) {
arrayList.add("element-" + Integer.toString(i));
}
for(Iterator<String> iterator = arrayList.iterator(); iterator.hasNext(); ) {
String str = iterator.next() //추가된 코드
System.out.println(str);
}
}
}
컬렉션의 데이터를 순회할 때 겉으로 보기에는 일반적인 for 문과 향상된 for 문이 상당히 다르게 보이지만 실제 내부 동작 원리는 같다는 것을 알 수 있다.
다만 아래와 같이 index 를 사용하는 경우와는 다르게 동작하며, iterator 를 생성하지 않는다.
for (int i = 0; i < list.size(); ++i) {
String var = list.get(i);
}
기본 타입의 배열 같은 경우에도 향상된 for 문 사용이 가능하지만 객체가 아니기 때문에 iterator() 메소드를 가지고 있지 않고, 내부 과정도 컬렉션과 다르게 동작한다.
하지만 기본 타입의 배열 같은 경우에도 형태는 다르지만 내부 과정은 비슷하게 동작하면 아래 두 코드는 거의 비슷한 바이트 코드를 생성한다.
int[] intArray = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
for(int i=0; i<intArray.size(); i++) {
int num = intArray[i];
System.out.println(num);
}
int[] intArray = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
for(int num : intArray) {
System.out.println(num);
}
결론적으로 향상된 for 문은 내부 동작원리에 차이가 있는 것이 아닌 사용자 편의를 위한 문법적 가미라는 것을 알 수 있다.