01.JAVA/Java2008. 11. 12. 16:19
반응형

향상된 루프문

Java SE 2005/07/04 10:24 Posted by Sun

J2SE 5.0의 새로운 언어 기능으로 소개된 향상된 루프문(enhenced for loop)은 Iterator를 생성하거나 카운터 변수의 시작과 끝 상태를 계산할 필요 없이 콜렉션을 반복할 수 있게 해준다. 향상된 루프문은 J2SE 5.0의 새로운 기능들 중 사용자의 코드에 즉시 적용시킬 수 있는 가장 쉬운 기능이다. 이번 테크팁에서는 향상된 루프문으로 콜렉션의 엘리먼트에 순차적으로 액세스하는 이전 방법을 대체하는 법에 대해서 배워보자.

향상된 루프문은 어떻게 생겼는가? RecentTips이라고 불리는 TechTip 오브젝트들의 콜렉션이 있다고 치자. 이 콜렉션에 다음과 같이 향상된 루프문을 사용할 수 있다.

   for (TechTip tip: RecentTips)

"for each TechTip in RecentTips(RecentTips안에 각각의 TechTip)"이라고 읽는다. 여기서 콜렉션 내의 TechTip의 현재 인스턴스를 가리키기 위해 tip 변수가 사용되었다. "for each" 단어 때문에, 향상된 생성문은 for-each 생성문으로도 불린다.

콜렉션을 반복하기 위해 사용한 기존 방법과 향상된 루프문을 비교한다면, for each loop이 더 간단하고 사용자의 코드를 보다 읽기 쉽게 만들어준다는 것을 쉽게 알 수 있다.

또한 향상된 루프문은 제너릭 작업을 간단히 할 수 있게 디자인되었다. 이번 테그팁에서 제너릭에 향상된 루프문을 사용한 두가지 예를 보여주겠지만, 이번 테크팁에서 다루고자 하는 부분은 아니다. 대신, for each loop 를 사용하여 코드에 줄 수 있는 좀 더 기본적인 변화에 대해서 보여주고자 한다.

먼저, Array 의 엘리먼트들을 반복하기 위해 루프문을 어떻게 사용하고자 하는 지 생각해보자. 간단히 하기 위해, 0부터 5까지 int의 제곱을 나타내는 6개의 int로 이루어진 배열을 로딩한다.

   for (int i=0; i< squares.length; i++)

이 라인은 루프구문의 전통적인 사용법을 보여준다. 한 개 이상 카운터의 초기값을 지정하고, 완료 상태를 설정하고 어떻게 카운터가 증가하는지 보여준다.

Here is a short program, OldForArray, that uses the for loop.

   public class OldForArray {

     public static void main(String[] args){
       int[] squares = {0,1,4,9,16,25};
       for (int i=0; i< squares.length; i++){
         System.out.printf("%d squared is %d.\n",i, squares[i]);
       }
     }
   }

OldForArray 프로그램을 컴파일하고 구동하면 다음이 나타난다.

   0 squared is 0.
   1 squared is 1.
   2 squared is 4.
   3 squared is 9.
   4 squared is 16.
   5 squared is 25.

향상된 루프문을 사용하도록 테스크 프로그램을 변경하려면, 관련 변수와 변수들이 나타나는 콜렉션을 지정한다. 다음은 향상된 루프문이다.

   for (int i : squares)
이 라인은 "iterate on elements from the collection named squares. (squere라는 이름의 콜렉션의 엘리먼트 반복) "로 읽을 수 있다.리먼트는 int i로 나타날 것이다.

루핑 전에 배열에 있어야할 엘리먼트의 수를 결정할 필요는 없다. 또한 현재 상태에서 어떻게 증가할지도 지정할 필요가 없다. "내부적" 배열에 대한 향상된 루프문은 이전에 보여준 루프문과 동일하다.

테스트 프로그램인 NewForArray에서는 OldForArray에서와 같은 결과가 나타난다.

    public class NewForArray {

     public static void main(String[] args) {
       int j = 0;
       int[] squares = {0, 1, 4, 9, 16, 25};
       for (int i : squares) {
         System.out.printf("%d squared is %d.\n", j++, i);
       }
     }
   }

배열은 배열이 선언될 때 지정되는 싱글 타입 엘리먼트들의 색인화된 콜렉션이다. ArrayList와 같은 좀 더 일반적인 콜렉션에서는 엘리먼트가 Object로 저장된다.다음과 같이 콜렉션을 반복하는 C 스타일 루프문을 사용할 수 있다.

   for (int i = 0; i < list.size(); i++)

그 후 llist.get(i)를 사용하여 현재 엘리먼트를 레퍼런스할 수 있다. 예를 들어, 다음의 프로그램 OldForArrayList에서는 ArrayList을 채워서 ArrayList에서 읽기 위해 오토박싱을 사용한다.

   import java.util.ArrayList;
   import java.util.List;

   public class OldForArrayList {
     private static List squares = new ArrayList();

     private static void fillList() {
       for (int i = 0; i < 6; i++) {
         squares.add(i * i);
       }
     }

     private static void outputList() {
       for (int i = 0; i < squares.size(); i++) {
         System.out.printf("%d squared is %d.\n",
           i, squares.get(i));
       }
     }

     public static void main(String args[]) {
       fillList();
       outputList();
     }
   }

그러나 ArrayList가 콜렉션 프레임의 일부이기 때문에, 다음의 패턴에서 Iterator를 사용하여 이를 반복하는 것이 좀 더 일반적이다.

   while( iterator.hasNext()) {
     doSomethingWith (iterator.next());
   }

다음의 프로그램 IteratorForArrayList에 나타난 것처럼 이를 루프문에 번들할 수 있다.

   import java.util.ArrayList;
   import java.util.List;
   import java.util.Iterator;

   public class IteratorForArrayList {

     private static List squares = new ArrayList();

      private static void fillList() {
       for (int i = 0; i < 6; i++) {
         squares.add(i * i);
       }
     }

     private static void outputList() {
       Iterator iterator = squares.iterator();
       int j=0;
       for (; iterator.hasNext();) {
         System.out.printf("%d squared is %d.\n",
                            j++, iterator.next());
       }
     }

     public static void main(String args[]) {
       fillList();
       outputList();
     }
   }

루프문에 첫번째 혹은 세번째 매개변수가 없다는 것이 좀 이상해보일지 모른다. 초기 상태가 없고, List에서의 포지션 증가가 루프문의 본문에서 iterator.next()를 호출하여 수행된다.

향상된 루프문은 불필요한 반복문을 명확하게한다. ArrayList에 대한 Iterator 오브젝트를 생성하여 루프문에 반목문을 사용하는 대신, 다음을 이용한다.

  for ( Integer square : squares)

이는 콜렉션의 이름이 squares이고, 현재 참조된 아이템이 Integer타입이며, 변수 square로 참조되고 있음을 가리키고 있다.

ArrayList의 컨텐츠가 Integer 타입임을 알 수 있는 방법이 없으므로 이 코드는 컴파일되지 않을 것이다. 이를 수정하기 위해서, J2SE 5.0에 소개된 또다른 기능인, 제너릭을 사용해야한다. Integer 타입의 엘리먼트만을 저장할 수 있는 것으로 squares의 정의와 선언문을 정의해주어야한다. 다음과 같이 하면 된다.

   private static List<Integer> squares
                               = new ArrayList<Integer>();

다음의 프로그램, NewArrayList은 향상된 루프문과 제너릭을 함께 사용하는 법을 보여준다.

   import java.util.List;
   import java.util.ArrayList;

   public class NewArrayList {
     private static List<Integer> squares
                               = new ArrayList<Integer>();

      private static void fillList() {
       for (int i = 0; i < 6; i++) {
         squares.add(i * i);
       }
     }

     private static void outputList() {
       int j=0;
       for (Integer square : squares) {
         System.out.printf("%d squared is %d.\n",
                         j++, square);
       }
     }

     public static void main(String args[]) {
       fillList();
       outputList();
     }
   }

NewArrayList 예제는 극단적으로 간단한 것이지만, 루프 일반문과 향상된 루프문 사이의 구문적 차이점을 보여주고 있다. 다음은 루프문들간의 구문적 차이를 보여주는 또다른 예제이다. 이 예제는 2004년 JavaOne 컨퍼런스에서 Joshua Bloch and Neil Gafter의 담화를 발췌한 것이다. 이 예제에서, 메소드는 콜렉션의 각 엘리먼트에 사용된다. 처음 시작할 때, 이 예제에서는 다음과 같이 Iterator를 사용한다.

   void cancelAll (Collection c) {
     for (Iterator i = c.iterator(); i.hasNext(); ) {
       TimerTask tt = (TimerTask) i.next();
       tt.cancel();
     }
   }

다음으로, 향상된 루프문이 도입되어 Iterator의 사용을 삭제한다.

   void cancelAll( Collection c ) {
     for (Object o : c)
       ( (TimerTask) o). cancel();
   }

콜렉션의 엘리먼트를 Object 타입으로 취급해서 TimerTask 타입으로 캐스팅된다는 문제가 남아있다. 이 문제는 다음과 같이 제너릭을 도입하면 해결된다.

   void cancelAll( Collection c ) {
     for (TimerTask task : c)
       task.cancel();
   } 

향상된 루프문이 모든 곳에 사용될 수는 없다는 것을 알아두길 바란다. 다음과 같은 상황에서는 사용할 수 없다.

  • 콜렉션을 순회하면서 엘리먼트를 삭제할 때
  • 배열이나 리스트에서 현재 슬롯을 수정할 때
  • 다중 콜렉션이나 배열을 반복할 때

그러나, 위의 경우들을 제외하고는 향상된 루프문의 사용하여 코드를 좀 더 간단히 하기 바란다.

새로운 것들이 언제나 그렇듯이, 향상된 루프문은 조금 낯설고 읽기 어렵게 느껴질 수도 있다. 이전에는 오랫동안 루프문에 C 스타일을 사용했을 것이다. 그러나, 카운터 변수나 Iterator를 갖지 않는 것이 더 읽기 쉽다. 또한 초기 값과 루프 종료 상태를 설정하는데 있어 사용자의 콜렉션이 어디에서 시작하고 끝나는지 걱정할 필요가 없다.

향상된 루프문에 대한 좀 더 자세한 정보는 The For-Each Loop를 참고하기 바란다.

"Java SE" 카테고리의 다른 글

Posted by 1010