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

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



답변

루핑의 세 가지 형태는 거의 동일합니다. 향상된 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).


답변

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

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());
     }
}


답변

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

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

list.get(i)

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


답변

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));
    }
}


답변

에서 자바 (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);


답변

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

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에 익숙하지 않기”때문에 이것들을 언급하는데 이것은 흥미로울 수있다.


답변

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

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

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