병렬 스트림
💡 그럼 모든 Stream을 병렬 스트림으로 사용하면 되는가?
List<String> nameList = Arrays.asList("Dinesh", "Ross", "Kagiso", "Steyn");
Stream<String> stream = nameList.stream();
stream.forEach(System.out::println);
stream.forEach(System.out::println);
//Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closedStream.of() 를 사용할 수 있습니다.Stream<String> stream = Stream.of("code", "chacha", "blog", "example");Stream.empty() 는 어떤 요소도 갖고 있지 않는 Stream 객체를 생성합니다.Stream<String> stream = Stream.empty();stream을 통해 스트림을 생성합니다.List<String> list = Arrays.asList("a1", "a2", "b1", "b2", "c2", "c1");
Stream<String> stream = list.stream();Arrays.stream() 을 이용하여 Stream을 생성할 수 있습니다.String[] array = new String[]{"a1", "a2", "b1", "b2", "c2", "c1"};
Stream<String> stream = Arrays.stream(array);generate 메소드를 이용하면 Supplier<T> 에 해당하는 람다로 값을 넣을 수 있습니다.public static<T> Stream<T> generate(Supplier<T> s) { ... }Stream<String> generatedStream = Stream.generate(() -> "gen").limit(5); // 5개의 “gen” 이 들어간 스트림이 생성됩니다.Stream.iterate() 도 generate() 와 유사합니다. 하지만 Stream.iterate() 은 두 개의 인자를 받습니다. 첫번째 인자는 초기값, 두번째 인자는 함수입니다. 이 함수는 1개의 인자를 받고 리턴 값이 있습니다.Stream<Integer> stream = Stream.iterate(0, n -> n + 2).limit(5);코드 예제
Stream<String> stream2 = Stream.of("1", "1", "1", "2", "3", "4");
stream2.distinct().filter(i -> i.equals("4")).forEach(System.out::print); //4
코드 예제
Stream<String> stream1 = Stream.of("1", "2", "3", "4", "5", "6","7","8","9","10");
stream1.skip(3).limit(5).forEach(System.out::print); //45678
코드 예제
Member[] members = {
new Member("김"),
new Member("최"),
new Member("박"),
new Member("이")
};
Stream<Member> memberStream = Stream.of(members);
memberStream.map(Member::getLastName)
.forEach(System.out::print); //김최박이
}
코드 예제(map과 flatMap 차이 비교)
Stream<String[]> stream1 = Stream.of(
new String[]{"Python", "Java", "C"},
new String[]{"PHP", "JavaScript", "Kotlin"}
);
Stream<String[]> stream2 = Stream.of(
new String[]{"Python", "Java", "C"},
new String[]{"PHP", "JavaScript", "Kotlin"}
);
System.out.println("flatMap 예시");
stream1.flatMap(Arrays::stream)
.forEach(System.out::println);
System.out.println("map 예시");
stream2.map(Arrays::stream)
.forEach(System.out::println);
}
/**
* flatMap 예시
* Python
* Java
* C
* PHP
* JavaScript
* Kotlin
*
* map 예시
* java.util.stream.ReferencePipeline$Head@4517d9a3
* java.util.stream.ReferencePipeline$Head@372f7a8d
*/
중간 연산은 모두 스트림을 반환한다.
코드 예제
IntStream stream1 = IntStream.of(30, 90, 70, 10);
IntStream stream2 = IntStream.of(30, 90, 70, 10);
IntStream stream3 = IntStream.of(30, 90, 70, 10);
System.out.println(stream1.allMatch(n -> n > 80)); // false
System.out.println(stream2.anyMatch(n -> n > 80)); // true
System.out.println(stream3.noneMatch(n -> n > 80)); // false
코드 예제
IntStream stream1 = IntStream.of(30, 90, 70, 10);
IntStream stream2 = IntStream.of(30, 90, 70, 10);
System.out.println(stream1.findAny().getAsInt()); // 30
System.out.println(stream2.findFirst().getAsInt()); // 30
코드 예제
Stream<String> stream1 = Stream.of("일","이","삼","사");
Stream<String> stream2 = Stream.of("일","이","삼","사");
Optional<String> result1 = stream1.reduce((a1, a2) -> a1 + " + " + a2);
String result2 = stream2.reduce("영",(a1,a2) -> a1 + " + " + a2);
System.out.println(result1.get()); // ((일 + 이) + 삼) + 사
System.out.println(result2); // 영 + 일 + 이 + 삼 + 사
Stream<String> stream = Stream.of("넷", "둘", "하나", "셋");
List<String> list = stream.collect(Collectors.toList());
System.out.println(list); //[넷, 둘, 하나, 셋]
Optional 클래스란?
Optional 클래스는 값의 존재나 여부를 표현하는 컨테이너 클래스입니다.
isPresent() 는 Optional이 값을 포함하면 참을 반환합니다.iterate나 generate를 이용하여 스트림을 생성할 때 제한을 두지 않으면 무한 스트림이 생성됩니다.
또는 코드의 의도에 맞지 않는 순서로 스트림을 작성할 경우에도 무한 스트림이 생성될 수 있습니다.
// 의도치 않게 생성된 무한 스트림
IntStream.iterate(0, i -> ( i + 1 ) % 2)
.distinct()
.limit(10)
.forEach(System.out::println);
System.out.println("complete");
// limit(10)을 했으나, distinct가 먼저 실행되어 0과 1만 무한히 반복된다.
public class StreamAnagrams {
public static void main(String[] args) throws IOException {
Path dictionary = Paths.get(args[0]);
int minGroupSize = Integer.parseInt(args[1]);
try (Stream<String> words = Files.lines(dictionary)) {
words.collect(
groupingBy**(word -> word.chars().sorted()
.collect(StringBuilder::new,
(sb, c) -> sb.append((char) c),
StringBuilder::append).toString()))**
.values().stream()
.filter(group -> group.size() >= minGroupSize)
**.map(group -> group.size() + ": " + group)**
.forEach(System.out::println);
}
}
}
import static java.util.stream.Collectors.groupingBy;
// 코드 45-3 스트림을 적절히 활용하면 깔끔하고 명료해진다. (Effective Java 271쪽)
public class HybridAnagrams {
public static void main(String[] args) throws IOException {
Path dictionary = Paths.get(args[0]);
int minGroupSize = Integer.parseInt(args[1]);
//파일 내용은 java 문자열 stream 으로 생성
try (Stream<String> words = Files.lines(dictionary)) {
words.collect(groupingBy(word -> **alphabetize(word))**)
.values().stream()
.filter(group -> group.size() >= minGroupSize)
.forEach(g -> System.out.println(g.size() + ": " + g));
}
}
private static String **alphabetize**(String s) {
char[] a = s.toCharArray();
Arrays.sort(a);
return new String(a);
}
}
Q1. ForkJoinPool을 사용해 커스텀 쓰레드를 만들었을 때 단점은 없을까요? 실제로 병렬처리가 어떤 식으로 일어나는 지도 조사가 된다면 좋을 것 같아요
A1.
ForkJoinPool의 단점으로는
1. ForkJoinPool은 recursive하게 작업되기 때문에 너무 많이 재귀적으로 분할되는 경우, 스레드 간의 작업 스케줄링이 불필요하게 발생하여 성능이 저하될 수 있습니다.
2. ForkJoinPool은 작업이 동시에 실행되기 때문에 작업의 순서를 제어하기 어렵습니다.
3. ForkJoinPool은 작업이 완료될 때까지 기다려야 할 경우, 작업들 간의 동기화 오버헤드가 발생할 수 있습니다.
병렬 스트림은 포크/조인 프레임워크를 아래의 그림과 같은 방식으로 활용하게 됩니다. 포크/조인 프레임워크에서는 서브 테스크를 스레드 풀 (ForkJoinPool)의 작업자 스레드에 분산 할당하는 ExecutorService 인터페이스를 구현합니다.
포크/조인 프레임워크는 Spliterator를 통해 데이터를 포함하는 스트림을 병렬화합니다.
public interface Spliterator<T> {
boolean tryAdvance(Consumer<? super T> action);
Spliterator<T> trySplit();
long estimateSize();
int characteristics();
}
// T는 Spliterator에서 탐색하는 요소의 형식을 가리킨다.
// tryAdvance 메서드는 Spilterator의 요소를 하나씩 순차적으로 소비하며 탐색해야할 요소가 남아 있으면 True를 리턴한다.
// trySplit 메서드는 Spliterator의 일부 요소를 분할해서 두번째 Spilterator를 생성하는 메서드이다.
// estimateSize로 탐색해야 할 요소의 갯수를 구할수 있다.
Q2. Lazy Evaluation의 구체적인 예시가 함께 있으면 이해하기 좋을 것 같아요.
A2. 1~10까지의 정수를 갖는 List에서 6보다 작고, 짝수인 요소를 찾아 10배 시킨 리스트를 출력하는 코드를 예시로 생각해보면 좋습니다.
final List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
System.out.println(
list.stream()
.filter(i -> {
System.out.println("i < 6");
return i<6;
})
.filter(i -> {
System.out.println("i%2 == 0");
return i%2==0;
})
.map(i -> {
System.out.println("i = i*10");
return i*10;
})
.collect(Collectors.toList())
);
위의 코드를 실행한 결과는 아래와 같습니다.
i < 6
i%2 == 0
i < 6
i%2 == 0
i = i*10
i < 6
i%2 == 0
i < 6
i%2 == 0
i = i*10
i < 6
i%2 == 0
i < 6
i < 6
i < 6
i < 6
i < 6
[20, 40]
위의 결과로 알 수 있는 점은 Lazy Evaluation은 Eager Evaluation(조급한 연산)과 달리 순차적으로 모두 연산하는 것이 아닌 필요할 때만 Stream의 요소를 평가하여 연산하게 됩니다. 위의 예시를 요약하면
이와 같이 정리할 수 있습니다.
Q3. 내부 반복과 외부 반복이 어떻게 다른지 조금 더 자세히 설명해주면 좋을 것 같아요.
A3.
외부반복: 컬렉션의 요소를 반복문으로 직접 탐색하는 것
예)
List<String> names = new ArrayList<>();
for(Dish dish: menu){
names.add(dish.getName());
}
//메뉴 리스트를 명시적으로 순차 반복한다.
내부반복: 람다 표현식을 인수로 받아, 어떤 작업을 수행할지만 지정하면 모든 것이 알아서 처리됨.
예)
List<String> names = menu.stream()
.map(Dish::getName)
.collect(toList());
//메뉴리스트를 내부적으로 순차 반복한다
Q4. 빈 Stream 객체를 어떤 경우에 사용되는지 궁금합니다.
A4. 제가 찾아본 바로는 실무에서 데이터베이스에서 조회한 결과를 스트림으로 처리하는 상황에 사용할 수 있다고 합니다. 예를 들어, 데이터베이스 테이블에서 특정 조건을 만족하는 데이터를 조회하여 스트림으로 처리하고 싶을 때 빈 스트림을 활용할 수 있다고 합니다.
개발자로서 배울 점이 많은 글이었습니다. 감사합니다.