- Java 1부터 지원
public static void main(String[] args) {
List<Integer> list = List.of(1, 2, 3, 4, 5);
for (int i = 0;i < list.size();i++) {
System.out.println(list.get(i));
}
}
- Java 5부터 지원
- 가독성이 좋고 OutOfIndex error에 안정적이다.
- .class파일로 될 때 전통적인 for문으로 변경된다.
public static void main(String[] args) {
List<Integer> list = List.of(1, 2, 3, 4, 5);
for (Integer i : list) {
System.out.println(list.get(i));
}
}
- Java 8부터 지원
- Stream 생성 -> 중간 연산 -> 최종 연산
public static void main(String[] args) {
List<Integer> list = List.of(1, 2, 3, 4, 5);
list.stream()
.filter(number -> number > 5)
.map(Distance::new)
.collect(Collectors.toList());
}
함수 객체 vs 코드 블록
- for 문법은 코드블록으로 표현
- stream 문법은 함수 객체으로 표현
stream의 제약
아래와 같은 코드에서 stream은 람다식으로 표현
자바 람다 표현식은 final 또는 **사실상 final**인 변수를 읽을 수 있다.
그러므로 아래 코드와 같이 baseNumber를 수정할 수 없다.
public int subtractByStream(int baseNumber, List<Integer> numbers) {
numbers.stream()
.filter(number -> number % 2 == 0)
.forEach(number -> baseNumber -= number); // 컴파일 에러
return baseNumber;
}
또 continue, break 로직을 stream에 사용할 수 없다.
외부 반복 vs 내부 반복
for 문에서는 중복된 수를 제거하기 위해 중복을 허용하지 않는 set을 사용하고 size를 통해 중복되지 않은 수를 리턴한다.
이는 구체적인 로직이 외부에 노출되는 외부반복이다.
public int byFor(List<Integer> numbers) {
Set<Integer> duplicationRemoved = new HashSet<>();
for (int number : numbers) {
duplicationRemoved.add(number);
}
return duplicationRemoved.size();
}
반면 stream은 로직이 노출되지 않는 내부반복이다.
public int byStream(List<Integer> numbers) {
return (int) numbers.stream()
.distinct()
.count();
}
디버깅
만약 아래와 같이 dividedNumber에 0이 들어오는 경우 에러(Division by zero)가 발생하게 되는데
이때 Stream은 내부적으로 수행되는 작업이 많기 때문에 Stack Trace가 굉장 복작하게 출력된다.
또 Stream은 지연 연산으로 수행되기 때문에 실제 라인과는 다른 순서로 Stack Trace를 출력하기 때문에 디버깅에 어려움이 있을 수 있다.
private double divideByStream(List<Integer> divided, int dividedNumber) {
return IntStream.range(0, divided.size())
.mapToDouble(index -> divided.get(index) / (double) dividedNumber)
.sum();
}
병렬 처리
public long iterativeSum(List<Integer> numbers) {
long sum = 0;
for (int number : numbers) {
sum += number;
}
return sum;
}
위 코드와 같이 반복문을 많이 수행할 때 Thread를 이용한 병렬처리를 고려해 볼 수 있다.
Stream을 사용하지 않으면 직접 구현해 주어야 한다.
이럴 때 병렬 Stream을 사용해서 처리해 주게 된다면 이를 내부적으로 처리해 준다.
private long sumByStream(List<Integer> numbers) {
return numbers.parallelStream()
.mapToLong(Long::valueOf)
.sum();
}
성능
for은 Stream에 비해 배열 더하기, 배열 최댓값 구하기에서 좋은 성능을 보여준다. (ArrayList에서는 비슷)
이는 JVM에서 최적화를 해주기 때문이다. (Stream은 Java 8부터 출시 해서 아직 최적화가 충분히 진행 안됨)
또한 Stream은 생성 시 객체를 생성해야 하므로 오버헤드가 발생한다.
하지만 for문은 인덱스를 통해 직접 접근하므로 오버헤드 stream에 비해 발생하지 않는다.
하지만 이는 HW의 발달로 크게 차이 나기 않으므로 성능이 매우 중요한 프로그램이 아닌 이상 고려할 부분이 아니다.
결론
가독성은 개인의 차이가 있지만 Stream이 좋은 경우가 많다. (하지만 취향차이)
디버깅에서는 stream이 for문에 비해 어렵다.
참고 : https://www.youtube.com/watch?v=by8hb75i9X4&list=WL&index=98&t=73s
'자바' 카테고리의 다른 글
Record란 (0) | 2024.06.07 |
---|---|
[자바] Reflection (0) | 2024.05.23 |