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.