Java에서 목록을 반복하는 방법


576

Java 언어에 다소 익숙하지 않기 때문에 목록 (또는 다른 컬렉션)과 각각의 장단점을 반복 할 수있는 모든 방법 (또는 적어도 비 병리학 적 방법)에 익숙해 지려고합니다.

List<E> list객체가 주어지면 모든 요소를 ​​반복하는 다음과 같은 방법을 알고 있습니다.

기본 for 루프 (물론 동등한 while/ do while루프도 있습니다)

// Not recommended (see below)!
for (int i = 0; i < list.size(); i++) {
    E element = list.get(i);
    // 1 - can call methods of element
    // 2 - can use 'i' to make index-based calls to methods of list

    // ...
}

참고 : @amarseillan가 지적한 바와 같이,이 양식을 통해 반복에 대한 빈약 한 선택 List의 실제 구현하기 때문에의이 get방법은 사용시 효율적으로하지 않을 수 있습니다 Iterator. 예를 들어, LinkedList구현에서는 i 번째 요소를 가져 오려면 i 앞에있는 모든 요소를 ​​통과해야합니다.

위의 예제에서 List구현이 미래의 반복을보다 효율적으로 만들기 위해 "위치를 저장" 할 방법이 없습니다 . 들어 ArrayList복잡성 / 비용이 있기 때문에, 실제로 문제가되지 않는 get시간 상수는 (O (1))을가 반면, LinkedList그리스트의 크기에 비례한다 (O (N)).

내장 Collections구현 의 계산 복잡성에 대한 자세한 내용은 이 질문을 확인하십시오 .

향상된 for 루프 ( 이 질문에 잘 설명 되어 있음 )

for (E element : list) {
    // 1 - can call methods of element

    // ...
}

반복자

for (Iterator<E> iter = list.iterator(); iter.hasNext(); ) {
    E element = iter.next();
    // 1 - can call methods of element
    // 2 - can use iter.remove() to remove the current element from the list

    // ...
}

ListIterator

for (ListIterator<E> iter = list.listIterator(); iter.hasNext(); ) {
    E element = iter.next();
    // 1 - can call methods of element
    // 2 - can use iter.remove() to remove the current element from the list
    // 3 - can use iter.add(...) to insert a new element into the list
    //     between element and iter->next()
    // 4 - can use iter.set(...) to replace the current element

    // ...
}

기능성 Java

list.stream().map(e -> e + 1); // Can apply a transformation function for e

Iterable.forEach , Stream.forEach , ...

(Java 8 's Stream API의 맵 메소드 (@i_am_zero의 답변 참조)

구현하는 Java 8 콜렉션 클래스 Iterable(예 : all List)에는 이제 위에서 설명한 for 루프 명령문forEach 대신 사용할 수 있는 메소드가 있습니다 . (다음은 좋은 비교를 제공하는 또 다른 질문 입니다.)

Arrays.asList(1,2,3,4).forEach(System.out::println);
// 1 - can call methods of an element
// 2 - would need reference to containing object to remove an item
//     (TODO: someone please confirm / deny this)
// 3 - functionally separates iteration from the action
//     being performed with each item.

Arrays.asList(1,2,3,4).stream().forEach(System.out::println);
// Same capabilities as above plus potentially greater
// utilization of parallelism
// (caution: consequently, order of execution is not guaranteed,
// see [Stream.forEachOrdered][stream-foreach-ordered] for more
// information about this).

다른 방법이 있다면 무엇입니까?

(BTW, 저의 관심은 전혀 성능최적화 하려는 욕구에서 비롯되지 않습니다 . 개발자로서 어떤 형식을 사용할 수 있는지 알고 싶습니다.)


1
그것들은 병리학 적이 아닌 것들이지만, 콜렉션을 처리하기 위해 여러 기능 스타일 라이브러리를 사용할 수도 있습니다.
Dave Newton

List인터페이스 에 대한 질문이 구체적으로 있습니까?
Sotirios Delimanolis

@SotiriosDelimanolis는 모든 의도와 목적을 위해 <code> List </ code>에만 해당하지만 <code> Collection </ code>과 함께 작동하는 다른 흥미로운 방법이 있다면 알고 싶어요
iX3

@DaveNewton, 아이디어 주셔서 감사합니다. 나는 그런 것을 사용한 적이 없다. 수정 된 질문을 살펴보고 무슨 말인지 이해했는지 알려주세요.
iX3

2
@sdasdadas 님, 완료 : stackoverflow.com/questions/18410035/…
iX3

답변:


265

루핑의 세 가지 형태는 거의 동일합니다. 향상된 for루프 :

for (E element : list) {
    . . .
}

에 따르면, Java 언어 사양 , 동일한 전통과 반복자의 명시 적 사용에 효과 for루프. 세 번째 경우에는 현재 요소를 제거하여 목록 내용 만 수정 한 다음 remove반복자 자체 의 방법을 통해 목록 요소를 수정하는 경우에만 목록 내용을 수정할 수 있습니다. 인덱스 기반 반복을 사용하면 어떤 방식 으로든 목록을 자유롭게 수정할 수 있습니다. 그러나 현재 색인 앞에 오는 요소를 추가하거나 제거하면 루프에서 요소를 건너 뛰거나 동일한 요소를 여러 번 처리 할 위험이 있습니다. 그러한 변경을 할 때 루프 인덱스를 올바르게 조정해야합니다.

모든 경우에 element실제 목록 요소에 대한 참조입니다. 반복 방법 중 어느 것도 목록의 내용을 복사하지 않습니다. 내부 상태에 대한 변경 사항 element은 항상 목록에서 해당 요소의 내부 상태에 표시됩니다.

기본적으로 목록을 반복하는 두 가지 방법은 인덱스를 사용하거나 반복자를 사용하는 것입니다. 향상된 for 루프는 반복자를 명시 적으로 정의하는 것을 피하기 위해 Java 5에 도입 된 구문 바로 가기입니다. 두 스타일 모두 for, while또는 do while블록을 사용하여 본질적으로 사소한 변형을 만들 수 있지만 모두 동일한 것 (또는 오히려 두 가지)으로 요약됩니다.

편집 : @ iX3이 주석에서 지적한 것처럼 a를 사용하여 ListIterator반복 할 때 목록의 현재 요소를 설정할 수 있습니다 . 루프 변수를 초기화하는 List#listIterator()대신 사용해야 List#iterator()합니다 (확실히 a ListIterator대신 선언해야 합니다 Iterator).


고마워,하지만 반복자가 실제로 실제 요소 (사본이 아닌)에 대한 참조를 반환하는 경우 목록에서 값을 변경하는 데 어떻게 사용할 수 있습니까? e = iterator.next()그런 다음 사용 하면 값 을 검색 한 실제 저장소를 변경하지 않고 참조 e = somethingElse하는 객체를 e변경합니다 iterator.next().
iX3

@ iX3-인덱스 기반 반복에서도 마찬가지입니다. 새 개체를 할당 e해도 목록의 내용은 변경되지 않습니다. 전화해야합니다 list.set(index, thing). (예 :) 의 내용 을 변경할 수 있지만 반복 할 때 목록에 저장된 요소를 변경하려면 인덱스 기반 반복을 사용해야합니다. ee.setSomething(newValue)
Ted Hopp

그 설명에 감사드립니다. 나는 당신이 지금 말하는 것을 이해하고 그에 따라 내 질문 / 의견을 업데이트 할 것이라고 생각합니다. "복사"자체는 없지만, 내용을 변경하기 위해 언어 디자인으로 인해 할당이 포인터를 변경하기 때문에의 메소드 e중 하나를 호출해야합니다 e(내 C를 용서합니다).
iX3

불변의 유형 목록이 좋아하는 위해 이렇게 Integer하고 String그것을 사용하여 내용을 변경하지 못할 것 for-each또는 Iterator요소를 대체 할 목록 개체 자체를 조작해야 할 것입니다 - 방법을. 맞습니까?
iX3

@ iX3-맞습니다. 세 번째 형식 (명시 적 반복자)으로 요소를 제거 할 수 있지만 인덱스 기반 반복을 사용하는 경우에만 목록에 요소를 추가하거나 요소를 바꿀 수 있습니다.
Ted Hopp

46

질문에 나열된 각 종류의 예 :

ListIterationExample.java

import java.util.*;

public class ListIterationExample {

     public static void main(String []args){
        List<Integer> numbers = new ArrayList<Integer>();

        // populates list with initial values
        for (Integer i : Arrays.asList(0,1,2,3,4,5,6,7))
            numbers.add(i);
        printList(numbers);         // 0,1,2,3,4,5,6,7

        // replaces each element with twice its value
        for (int index=0; index < numbers.size(); index++) {
            numbers.set(index, numbers.get(index)*2); 
        }
        printList(numbers);         // 0,2,4,6,8,10,12,14

        // does nothing because list is not being changed
        for (Integer number : numbers) {
            number++; // number = new Integer(number+1);
        }
        printList(numbers);         // 0,2,4,6,8,10,12,14  

        // same as above -- just different syntax
        for (Iterator<Integer> iter = numbers.iterator(); iter.hasNext(); ) {
            Integer number = iter.next();
            number++;
        }
        printList(numbers);         // 0,2,4,6,8,10,12,14

        // ListIterator<?> provides an "add" method to insert elements
        // between the current element and the cursor
        for (ListIterator<Integer> iter = numbers.listIterator(); iter.hasNext(); ) {
            Integer number = iter.next();
            iter.add(number+1);     // insert a number right before this
        }
        printList(numbers);         // 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15

        // Iterator<?> provides a "remove" method to delete elements
        // between the current element and the cursor
        for (Iterator<Integer> iter = numbers.iterator(); iter.hasNext(); ) {
            Integer number = iter.next();
            if (number % 2 == 0)    // if number is even 
                iter.remove();      // remove it from the collection
        }
        printList(numbers);         // 1,3,5,7,9,11,13,15

        // ListIterator<?> provides a "set" method to replace elements
        for (ListIterator<Integer> iter = numbers.listIterator(); iter.hasNext(); ) {
            Integer number = iter.next();
            iter.set(number/2);     // divide each element by 2
        }
        printList(numbers);         // 0,1,2,3,4,5,6,7
     }

     public static void printList(List<Integer> numbers) {
        StringBuilder sb = new StringBuilder();
        for (Integer number : numbers) {
            sb.append(number);
            sb.append(",");
        }
        sb.deleteCharAt(sb.length()-1); // remove trailing comma
        System.out.println(sb.toString());
     }
}

23

목록의 구현을 모르므로 기본 루프는 권장되지 않습니다.

그것이 LinkedList 인 경우, 각 호출은

list.get(i)

목록을 반복하여 N ^ 2 시간 복잡성을 초래합니다.


1
옳은; 그것은 좋은 지적이며, 질문의 예를 업데이트 할 것입니다.
iX3

이 게시물에 따르면, 당신의 진술은 정확하지 않습니다 : 어느 것이 더 효과적입니까, for-each 루프 또는 반복자입니까?
Hibbem

5
그 게시물에서 읽은 내용은 내가 말한 내용과 정확히 일치합니다.
amarseillan

20

JDK8 스타일 반복 :

public class IterationDemo {

    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3);
        list.stream().forEach(elem -> System.out.println("element " + elem));
    }
}

고마워, 나는 결국 Java 8 솔루션으로 목록을 업데이트하려고합니다.
iX3

1
@ eugene82이 방법이 훨씬 더 효율적입니까?
nazar_art

1
@nazar_art 매우 큰 컬렉션에서 parallelStream을 사용하지 않으면 다른 것보다 크게 개선되지 않을 것입니다. 실제로는 사소한 부분 만 치유한다는 점에서 더 효율적입니다.
Rogue

7

에서 자바 (8) 우리는 컬렉션 클래스 반복하는 여러 가지 방법이있다.

Iterable forEach 사용하기

구현하는 컬렉션 Iterable(예 : 모든 목록)에 forEach메서드가 있습니다. Java 8에 도입 된 메소드 참조를 사용할 수 있습니다 .

Arrays.asList(1,2,3,4).forEach(System.out::println);

forEach 및 forEachOrdered에 스트림 사용

Stream 을 다음과 같이 사용하여 목록을 반복 할 수도 있습니다 .

Arrays.asList(1,2,3,4).stream().forEach(System.out::println);
Arrays.asList(1,2,3,4).stream().forEachOrdered(System.out::println);

우리는 좋아한다 forEachOrdered동안 forEach의 행동이 있기 때문에 forEach명시 적으로 비 결정적인 곳으로 forEachOrdered수행 스트림이 정의 된 만남 순서가있는 경우 스트림의 만남 순서에서이 스트림의 각 요소에 대한 작업. 따라서 forEach는 주문 유지를 보장하지 않습니다.

스트림의 장점은 필요한 경우 병렬 스트림을 사용할 수도 있다는 것입니다. 주문과 상관없이 항목을 인쇄하는 것이 목적이라면 병렬 스트림을 다음과 같이 사용할 수 있습니다.

Arrays.asList(1,2,3,4).parallelStream().forEach(System.out::println);

5

나는 당신이 병리학 적이라고 생각하는 것을 모르지만 이전에는 볼 수 없었던 대안을 제공하겠습니다.

List<E> sl= list ;
while( ! sl.empty() ) {
    E element= sl.get(0) ;
    .....
    sl= sl.subList(1,sl.size());
}

또는 재귀 버전 :

void visit(List<E> list) {
    if( list.isEmpty() ) return;
    E element= list.get(0) ;
    ....
    visit(list.subList(1,list.size()));
}

또한 고전의 재귀 버전 for(int i=0...:

void visit(List<E> list,int pos) {
    if( pos >= list.size() ) return;
    E element= list.get(pos) ;
    ....
    visit(list,pos+1);
}

나는 당신이 "약간 Java에 익숙하지 않기"때문에 이것들을 언급하는데 이것은 흥미로울 수있다.


1
언급 해 주셔서 감사합니다. 나는 subList 메소드를 본 적이 없다. 예, 아마도 난독 화 대회와는 별도로 사용하는 것이 유리한 상황을 알지 못하므로 병리학 적이라고 생각합니다.
iX3

3
확인. 나는 "병리학 적"이라고 불리우는 것을 좋아하지 않기 때문에 여기에 하나의 설명이 있습니다. 하나 더? 일부 Lisp 프로그램을 Java로 번역하는 동안 Lisp 정신을 잃고 싶지 않았으며 동일한 작업을 수행했습니다. 이것이 비 병리학 적으로 유효하다고 생각되면 의견을 찬성하십시오. 나는 그룹 포옹이 필요합니다! :-)
Mario Rossi

나무의 경로가 목록과 다릅니 까? BTW, 나는 당신이나 다른 사람을 "병리학 적"이라고 부를 의도는 아니었다. 나는 단지 내 질문에 대한 어떤 대답은 이해할 수 없을 정도로 비현실적이거나 훌륭한 소프트웨어 엔지니어링 실무에서 가치를 가질 가능성이 거의 없다는 것을 의미한다.
iX3

@ iX3 걱정하지 마십시오. 그냥 농담 한거야. 경로는 "루트에서 시작"(명백), "두 번째 자식으로 이동", "첫 번째 자식으로 이동", "4 번째 자식으로 이동"의 목록입니다. "중지". 또는 짧게는 [2,1,4]입니다. 이것은 목록입니다.
Mario Rossi

나는 목록의 사본을 만들고 사용하는 것을 선호합니다 while(!copyList.isEmpty()){ E e = copyList.remove(0); ... }. 그것은 첫 번째 버전보다 효과적입니다;).
AxelH

2

Java 8부터 forEach 를 사용할 수 있습니다 .

 List<String> nameList   = new ArrayList<>(
            Arrays.asList("USA", "USSR", "UK"));

 nameList.forEach((v) -> System.out.println(v));

0

뒤로 검색하려면 다음을 사용해야합니다.

for (ListIterator<SomeClass> iterator = list.listIterator(list.size()); iterator.hasPrevious();) {
    SomeClass item = iterator.previous();
    ...
    item.remove(); // For instance.
}

위치를 알고 싶다면 iterator.previousIndex ()를 사용하십시오. 또한 목록에서 두 위치를 비교하는 내부 루프를 작성하는 데 도움이됩니다 (반복자가 동일하지 않음).


0

많은 대안이 나열되어 있습니다. 가장 쉽고 깨끗한 것은 다음과 같이 향상된 for명령문을 사용하는 것 입니다. 는 Expression반복 가능한 일부 타입이다.

for ( FormalParameter : Expression ) Statement

예를 들어 List <String> id를 반복하기 위해 간단히 다음과 같이 할 수 있습니다.

for (String str : ids) {
    // Do something
}

3
그는 질문에 설명 된 것 이외의 다른 방법이 있는지 묻습니다 (이것은 당신이 언급 한 것을 포함합니다)!
ericbn

0

에서 java 8당신은 사용할 수 List.forEach()와 방법을 lambda expression목록을 반복.

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

public class TestA {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("Apple");
        list.add("Orange");
        list.add("Banana");
        list.forEach(
                (name) -> {
                    System.out.println(name);
                }
        );
    }
}

멋있는. 당신이 어떻게 다른지 설명 할 수 eugene82의 대답i_am_zero의 대답은 ?
iX3

@ iX3 아아. 전체 답변 목록을 읽지 못했습니다. 도움을 주려고 노력했습니다 .. 답변을 삭제 하시겠습니까?
검색 결과 웹 결과 Pi

나에게 중요하지는 않지만 다른 기술과 비교하고 대조하여 향상시킬 수 있습니다. 또는 새로운 기술을 보여주지는 않지만 다른 기술에 대한 참조로 여전히 유용 할 것으로 생각되는 경우이를 설명하는 메모를 추가 할 수 있습니다.
iX3

-2

while 루프와 조금 더 많은 코드를 사용하여 첫 번째와 세 번째 예제를 항상 전환 할 수 있습니다. 이렇게하면 do-while을 사용할 수 있다는 이점이 있습니다.

int i = 0;
do{
 E element = list.get(i);
 i++;
}
while (i < list.size());

물론 list.size ()가 0을 반환하면 NullPointerException이 발생할 수 있습니다. 항상 한 번 이상 실행되기 때문입니다. 이것은 속성 / 메소드를 사용하기 전에 element가 null인지 테스트하여 해결할 수 있습니다. 여전히 for 루프를 사용하는 것이 훨씬 간단하고 쉽습니다.


사실 ... 기술적으로 다르다고 생각하지만 목록이 비어있는 경우에만 동작이 다릅니다.
iX3

@ ix3 예, 그 경우에는 동작이 더 나쁩니다. 나는 이것을 좋은 대안이라고 부르지 않을 것이다.
CPerkins

물론, 공정성에서이 질문은 "좋은"것이 아니라 "가능한"것에 관한 것이기 때문에 여전히이 답변에 감사합니다.
iX3
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.