iterator_stream
Iterator
Iterator는 컬렉션의 데이터를 순회하고 접근하기 위한 인터페이스로, 컬렉션 구현체의 내부 구조를 알지 못해도, 일관된 방식으로 요소에 접근할 수 있게 해준다.
Iterator 인터페이스의 주요 메서드
Iterator 인터페이스는 다음과 같은 메서드를 가지고 있다.
boolean hasNext(): 다음 요소가 존재하는지 확인하는 메서드로, 다음 요소가 있으면true를 반환하고, 더 이상 순회할 요소가 없으면false를 반환E next(): 다음 요소를 반환(호출할 다음 요소가 없을 경우NoSuchElementException예외가 발생)
public class IteratorExample {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.add("Charlie");
// Iterator 생성
Iterator<String> iterator = names.iterator();
// 순회하면서 출력
while (iterator.hasNext()) {
String name = iterator.next();
System.out.println(name);
}
}
}스트림
자바 8부터 도입된 Stream은 데이터 소스를 추상화하고, 데이터를 처리하는 데 유용한 메서드들을 정의한 인터페이스다.
Iterator vs Stream
Iterator가 데이터를 어떻게 처리할지 명시하는 명령형imperative 방식이라면, Stream은 무엇을 할지만 선언하는 선언형 방식이다.
컬렉션 요소를 람다식을 활용하여 처리 가능하며, Iterator보다 많은 연산을 지원
데이터 소스(배열, 컬렉션 등)가 무엇이든 동일한 방식으로 데이터를 처리하여 코드의 재사용성 향상
public static void main(String[] args) {
String[] array = {"a", "b", "c", "d", "e"};
List<String> list = Arrays.asList(array);
// 스트림 생성
Stream<String> arrayStream = Arrays.stream(array);
Stream<String> listStream = list.stream();
// 두 스트림의 소스는 다르지만, 동일한 방식으로 처리
arrayStream.sorted().forEach(System.out::println);
listStream.sorted().forEach(System.out::println);
}스트림의 특징
1. 데이터 소스 불변
스트림은 데이터 소스를 변경하지 않고, 연산 결과로 새로운 스트림을 반환하거나 최종 결과를 생성한다.
public static void main(String[] args) {
List<String> list = Arrays.asList("a", "c", "d", "b", "e");
// list.stream().sorted()는 list 원본을 정렬하지 않음
List<String> sortedList = list
.stream()
.sorted()
.collect(Collectors.toList()); // 정렬된 새 리스트가 생성
}2. 내부 반복
스트림을 사용하면 반복문을 메서드 호출을 통해 요소를 처리할 수 있다.
class Example {
public static void main(String[] args) {
List<String> list = Arrays.asList("a", "b", "c", "d", "e");
// 외부 반복
for (String string : list) {
System.out.println(string);
}
// 내부 반복
list.stream().forEach(System.out::println);
}
}3. 중간 연산과 최종 연산
스트림의 연산은 파이프라인(pipeline)을 구성하며, 두 가지 유형으로 나뉜다.
중간 연산: 스트림을 반환하는 연산으로, 스트림에 연속해서 여러 개의 중간 연산을 호출 가능
최종 연산: 스트림을 반환하지 않는 연산으로, 스트림에 최종 연산을 호출하면 스트림이 소모
소모하게 되면, 더 이상 스트림을 사용할 수 없음
public static void main(String[] args) {
List<String> list = Arrays.asList("a", "b", "c", "d", "e");
Stream<String> stream = list.stream();
stream.sorted(); // 중간 연산
stream.forEach(System.out::println); // 최종 연산
}4. 지연 연산
스트림의 중간 연산은 최종 연산이 호출되기 전까지는 실제로 수행되지 않고, 최종 연산이 호출되면 파이프라인이 동작하기 시작한다.
스트림 생성
대부분의 데이터 소스는 스트림으로 변환할 수 있다.
Collection인터페이스:stream()메서드가 정의되어 있어 컬렉션을 스트림으로 변환(자손 클래스 모두 사용 가능)Arrays클래스: 배열을 스트림으로 변환하는stream()메서드가 정의되어 있어 배열을 스트림으로 변환Map인터페이스:keySet(),values(),entrySet()메서드를 사용하여 스트림으로 변환Stream클래스(static 메서드)Stream.of(1, 2, 3)Stream.empty()Stream.concat(stream1, stream2)
무한 스트림
Stream.iterate(1, n -> n + 2)Stream.generate(Math::random)
기본형 스트림
IntStream.range(1, 10)IntStream.rangeClosed(1, 10)
스트림의 중간 연산
public static void main(String[] args) {
// skip()와 limit(): 요소 건너뛰기와 제한
Stream.of(1, 2, 3, 4, 5)
.skip(2)
.limit(2)
.forEach(System.out::println);
// filter()와 distinct(): 요소 걸러내기와 중복 제거
Stream.of(1, 2, 3, 4, 5, 1, 2, 3)
.filter(i -> i % 2 == 0)
.distinct()
.forEach(System.out::println);
// sorted(): 요소 정렬
Stream.of(5, 2, 1, 4, 3)
.sorted()
.forEach(System.out::println);
// map(): 요소 변환
Stream.of(1, 2, 3, 4, 5)
.map(i -> i * 2)
.forEach(System.out::println);
// peek(): 요소 확인 (소비하지 않음)
Stream.of(1, 2, 3, 4, 5)
.peek(System.out::println)
.forEach(System.out::println);
// mapToInt(), mapToLong(), mapToDouble(): 기본형 스트림으로 변환
Stream.of(1, 2, 3, 4, 5)
.mapToInt(Integer::intValue)
.forEach(System.out::println);
// flatMap(): 스트림의 요소를 다른 스트림으로 변환하는 연산
String[] lineArr = {
"Believe or not It is true",
"Do or do not There is no try"
};
Stream<String> lineStream = Stream.of(lineArr);
Stream<Stream<String>> strArrStream = lineStream.map(line -> Stream.of(line.split(" +")));
strArrStream.map(strStream -> Arrays.toString(strStream.toArray(String[]::new)))
.forEach(System.out::println);
// [[Believe, or, not, It, is, true], [Do, or, do, not, There, is, no, try]]
lineStream = Stream.of(lineArr);
Stream<String> stream = lineStream.flatMap(line -> Stream.of(line.split(" +")));
stream.forEach(System.out::println);
// [Believe, or, not, It, is, true, Do, or, do, not, There, is, no, try]
}스트림의 최종 연산
public static void main(String[] args) {
// forEach(): 스트림 요소를 소모하면서 작업 수행
Stream.of(1, 2, 3, 4, 5).forEach(System.out::println);
// allMatch(), anyMatch(), noneMatch(): 요소에 대해 지정된 조건에 맞는지 검사하는 연산으로 boolean 값을 반환
Stream.of(1, 2, 3, 4, 5).allMatch(i -> i > 0); // true
Stream.of(1, 2, 3, 4, 5).anyMatch(i -> i > 2); // true
Stream.of(1, 2, 3, 4, 5).noneMatch(i -> i > 0); // false
// findFirst()와 findAny(): 스트림의 요소 중에서 찾은 첫 번째 요소를 `Optional` 객체로 반환(찾지 못하면 empty 상태의 `Optional` 객체 반환)
Stream.of(1, 2, 3, 4, 5).findFirst(); // Optional[1]
Stream.of(1, 2, 3, 4, 5).findAny(); // Optional[1]
// reduce(): 스트림의 요소를 하나씩 소모하면서 지정된 람다식을 수행하는 연산으로 `Optional` 객체를 반환
// reduce()를 사용하면 sum(), max(), min() 등의 집계 연산을 구현할 수 있다.
Stream.of(1, 2, 3, 4, 5).reduce((a, b) -> a + b); // Optional[15]
// count(), max(), min(): 스트림의 요소를 소모하면서 지정된 연산을 수행하고, 최종 결과를 반환
Stream.of(1, 2, 3, 4, 5).count(); // 5
Stream.of(1, 2, 3, 4, 5).max(Integer::compareTo); // Optional[5]
Stream.of(1, 2, 3, 4, 5).min(Integer::compareTo); // Optional[1]
// collect(): 스트림의 요소를 수집하는 연산으로, `Collector`를 매개변수로 받아 받은 타입으로 변환하여 반환
List<Integer> list = Stream.of(1, 2, 3, 4, 5).collect(Collectors.toList());
Set<Integer> set = Stream.of(1, 2, 3, 4, 5).collect(Collectors.toSet());
}findFirst() vs findAny()
findFirst()와 findAny() 두 메서드 모두 찾은 첫 번째 요소를 Optional 객체로 반환하지만, 병렬 환경에서 동작이 다르다.
findFirst(): 여러 요소가 조건에 부합해도 Stream의 순서를 고려하여 첫 번째 요소를 반환findAny(): Multiple Thread에서 여러 요소를 처리하다가 가장 먼저 찾은 요소를 반환
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Optional<Integer> findFirst = list.stream().parallel().filter(i -> i % 2 == 0).findFirst();
Optional<Integer> findAny = list.stream().parallel().filter(i -> i % 2 == 0).findAny();
System.out.println(findFirst.get()); // 2
System.out.println(findAny.get()); // 2, 4, 6, 8, 10 중 하나
}기본형 스트림 (Primitive Stream)
Stream<Integer>, Stream<Double> 등을 사용하면 기본형과 객체 간의 변환(박싱, 언박싱) 오버헤드가 발생할 수 있기 때문에, 기본형을 직접 다루는 스트림을 사용하는 것이 좋다.
Stream<T>.mapToInt(ToIntFunction<T> mapper):Stream<T>를IntStream으로 변환한IntStream.sum(),IntStream.average(): 기본형에 특화된 집계 메서드 제공IntStream.boxed():IntStream을 다시Stream<Integer>로 변환IntStream,LongStream,DoubleStream사용 가능
참고자료
Last updated
Was this helpful?