Java 향상된 for 문에 대한 고찰

mj·2023년 2월 23일
0
post-thumbnail

자바에는 for-each 문이라고도 불리는 향상된 for문이 있다.
배열이나 컬렉션에 있는 요소들을 순회할 때 사용하는 문법으로, 기존의 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 에 대한 객체가 생성되고 반환이 된다.

일반적인 for문 사용

일반적으로 컬렉션에 있는 데이터를 순회할 때는 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 문 사용

컬렉션에서 향상된 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 문과 향상된 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 문은 내부 동작원리에 차이가 있는 것이 아닌 사용자 편의를 위한 문법적 가미라는 것을 알 수 있다.

profile
사는게 쉽지가 않네요

0개의 댓글