1. 개요

Java에서 데이터를 반복하는 동안 데이터 소스에서 현재 항목과 해당 위치에 모두 액세스할 수 있습니다.

이것은 위치가 일반적으로 루프 계산의 초점인 고전적인 for 루프 에서 달성하기가 매우 쉽지만  for each 루프 또는 스트림과 같은 구성을 사용할 때 약간 더 많은 작업이 필요합니다.

이 짧은 사용방법(예제)에서는 각 작업 대해 카운터를 포함할 수 있는 몇 가지 방법을 살펴보겠습니다 .

2. 카운터 구현

간단한 예부터 시작하겠습니다. 영화의 순서가 지정된 List을 가져와 순위와 함께 출력합니다.

List<String> IMDB_TOP_MOVIES = Arrays.asList("The Shawshank Redemption",
  "The Godfather", "The Godfather II", "The Dark Knight");

2.1. 루프를 위해

에 대한 루프 는 데이터 List에서 인덱스 모두에서 작동 할 수있는 쉬운 방법, 그래서 현재의 항목을 참조 할 카운터를 사용합니다 :

List rankings = new ArrayList<>();
for (int i = 0; i < movies.size(); i++) {
    String ranking = (i + 1) + ": " + movies.get(i);
    rankings.add(ranking);
}

List 은 아마도  ArrayList 이므로  get 작업이 효율적이고 위의 코드는 우리 문제에 대한 간단한 솔루션입니다.

assertThat(getRankingsWithForLoop(IMDB_TOP_MOVIES))
  .containsExactly("1: The Shawshank Redemption",
      "2: The Godfather", "3: The Godfather II", "4: The Dark Knight");

그러나 Java의 모든 데이터 소스를 이 방법으로 반복할 수 있는 것은 아닙니다. 때때로  get 은 시간 집약적인 작업이거나 Stream 또는  Iterable을 사용하여 데이터 소스의 다음 요소만 처리할 수 있습니다 .

2.2. 에 대한 각 루프

영화 List을 계속 사용하지만 각 구성에 대해 Java를 사용하여 반복할 수만 있다고 가정해 보겠습니다.

for (String movie : IMDB_TOP_MOVIES) {
   // use movie value
}

여기에서 현재 인덱스를 추적하기 위해 별도의 변수를 사용해야 합니다. 루프 외부에서 구성하고 내부에서 증가시킬 수 있습니다.

int i = 0;
for (String movie : movies) {
    String ranking = (i + 1) + ": " + movie;
    rankings.add(ranking);

    i++;
}

루프 내에서 사용한 후에 카운터를 증가 시켜야 한다는 점에 유의해야 합니다.

3. 기능 에 대한

필요할 때마다 카운터 확장을 작성하면 코드가 중복될 수 있고 카운터 변수를 업데이트할 때와 관련된 실수로 버그가 발생할 위험이 있습니다. 따라서 우리는 Java의 기능적 인터페이스를 사용하여 위의 내용을 일반화할 수 있습니다 .

먼저 루프 내부의 동작을 컬렉션의 항목과 인덱스 모두의 소비자로 생각해야 합니다. 이것은 두 개의 매개변수를 취하는 수락 함수를 정의하는  BiConsumer를 사용하여 모델링할 수 있습니다.

@FunctionalInterface
public interface BiConsumer<T, U> {
   void accept(T t, U u);
}

루프 내부는 두 개의 값을 사용하는 것이므로 일반적인 루프 연산을 작성할 수 있습니다. 각 루프가 실행될 소스 데이터 의  Iterable 과  각 항목 및 해당 인덱스에서 수행할 작업에 대해 BiConsumer를 사용할 수 있습니다. 유형 매개변수 T 를 사용하여 이것을 제네릭으로 만들 수 있습니다  .

static <T> void forEachWithCounter(Iterable<T> source, BiConsumer<Integer, T> consumer) {
    int i = 0;
    for (T item : source) {
        consumer.accept(i, item);
        i++;
    }
}

BiConsumer에 대한 구현을 람다로 제공하여 영화 순위 예제에서 이것을 사용할 수 있습니다  .

List rankings = new ArrayList<>();
forEachWithCounter(movies, (i, movie) -> {
    String ranking = (i + 1) + ": " + movies.get(i);
    rankings.add(ranking);
});

4.에 카운터를 추가 대해 forEach스트림

자바  스트림 API는 우리의 데이터가 필터와 변환을 통과하는 방법을 표현하는 우리를 할 수 있습니다. 또한 forEach 기능을 제공  합니다. 카운터를 포함하는 작업으로 변환해 보겠습니다.

스트림의 foreach 함수는 소요  소비자  다음 항목을 처리 할 수 있습니다. 그러나 카운터를 추적하고 항목을 BiConsumer 에 전달하기 위해 해당 Consumer생성할 수 있습니다 .

public static <T> Consumer<T> withCounter(BiConsumer<Integer, T> consumer) {
    AtomicInteger counter = new AtomicInteger(0);
    return item -> consumer.accept(counter.getAndIncrement(), item);
}

이 함수는 새로운 람다를 반환합니다. 해당 람다는 AtomicInteger 개체를 사용하여  반복 중에 카운터를 추적합니다. getAndIncrement의 기능은 새로운 항목이있을 때마다 호출된다.

이 함수로 생성된 람다는 전달된 BiConsumer에 위임 하므로 알고리즘이 항목과 해당 인덱스를 모두 처리할 수 있습니다.

의에 대한 우리의 영화 순위 예에서 사용이 보자 스트림 라는 영화 :

List rankings = new ArrayList<>();
movies.forEach(withCounter((i, movie) -> {
    String ranking = (i + 1) + ": " + movie;
    rankings.add(ranking);
}));

forEach 내부에는  개수를 추적 하고  forEach 작업이 해당 값을 전달 하는 소비자 역할을 하는 개체를 만들기 위한 withCounter 함수에  대한 호출이  있습니다.

5. 결론

이 짧은 기사에서는 각 작업 대해 카운터를 Java 연결하는 세 가지 방법을 살펴보았습니다 .

루프에 대한 각 구현에서 현재 항목의 인덱스를 추적하는 방법을 보았습니다 . 그런 다음 이 패턴을 일반화하고 스트리밍 작업에 추가하는 방법을 살펴보았습니다.

항상 그렇듯이 이 기사의 예제 코드는 GitHub에서 사용할 수 있습니다 .

Junit footer banner