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에서 사용할 수 있습니다 .