💁♀️ 스트림(Stream)이란,
컬렉션에 저장한 엘리먼트들을 하나씩 순회하면서 처리할 수 있는 기능.
자바 8 이전의 배열 또는 컬렉션을 다루는 방법은 'for', 'forEach'를 사용하여 엘리먼트를 꺼내서 다루는 방법이었음. 스트림을 이용하면 배열 또는 컬렉션을 함수 여러 개를 사용해서 결과를 쉽게 얻을 수 있으며, 람다식과 함께 사용하면 컬렉션에 담긴 데이터 처리를 간결하게 표현 가능
asList()
List<String> stringList = Arrays.asList("Good", "to", "see", "you"); // asList() : 가변인자 전달 가능
/* 이전의 스트림을 이용하지 않던 방식 */
for(String str : stringList) {
System.out.println(str);
}
/* 스트림을 이용한 방식 */
stringList.forEach(System.out::println); // 더 간결하지만 위와 같은 결과 출력
currentThread()
getName()
forEach()
parallelStream()
public static void main(String[] args) {
/* 스트림의 병렬 처리 확인 */
List<String> stringList = Arrays.asList("Hello", "my", "friend", "Joy");
/* 스트림을 사용하지 않으면 모든 작업은 main 스레드에서 일어남 */
System.out.println("================== forEach");
for(String s : stringList) {
System.out.println(s + " : " + Thread.currentThread().getName()); // currentThread().getName() : 현재 진행 중인 스레드의 이름 반환
}
/* 일반적인 스트림도 main 스레드가 작업을 수행 */
System.out.println("================== normal stream");
stringList.forEach(Application2::printThis);
/* 병렬 스트림(parallelStream())을 사용하면 병렬 처리를 손쉽게 할 수 있음 */
System.out.println("================== parallel stream");
stringList.parallelStream().forEach(Application2::printThis);
stringList.parallelStream().forEach(Application2::printThis);
/* 출력된 순서가 다름 => 병렬적으로 처리되었기 때문 (작업 속도, 성능면에서 훨씬 우수) */
}
private static void printThis(String str) {
System.out.println(str + " : " + Thread.currentThread().getName());
}
stream()
asList()
/* 배열 스트림 생성 */
String[] sarr = {"Oh", "I", "got", "it"};
Stream<String> strStream1 = Arrays.stream(sarr); // 배열을 스트림으로 만들어 forEach로 출력
strStream1.forEach(System.out::println);
Stream<String> strStream2 = Arrays.stream(sarr, 0, 2); // 배열 뿐만 아니라 인덱스 값들을 전달 (start&end)
strStream2.forEach(System.out::println); // 0과 1번 인덱스인 "Oh", "I"만 출력
/* 컬렉션 스트림 생성 */
List<String> stringList = Arrays.asList("Make", "It", "Happen");
Stream<String> strStream3 = stringList.stream(); // 컬렉션을 스트림으로 만들어 forEach로 출력
strStream3.forEach(System.out::println);
/* forEach는 컬렉션에도 작성되어 있어 Stream으로 만들지 않고 사용 가능 */
stringList.forEach(System.out::println); // 위와 같은 결과
range(시작값, 종료값)
: 시작값부터 1씩 증가하는 숫자로 종료값 전까지 범위의 스트림 생성
rangeClosed(시작값, 종료값)
: 시작값부터 1씩 증가하는 숫자로 종료값까지 범위의 스트림 생성
IntStream intStream = IntStream.range(5, 10);
intStream.forEach(value -> System.out.print(value + " ")); // 5 6 7 8 9
System.out.println();
LongStream longStream = LongStream.rangeClosed(5, 10);
longStream.forEach(value -> System.out.print(value + " ")); // 5 6 7 8 9 10
System.out.println();
Wrapper 클래스자료형의 스트림으로 변환이 필요한 경우 boxing 가능
doubles(갯수)
: 난수를 활용한 DoubleStream을 갯수 만큼 생성하여 반환
boxed()
: 기본 타입 스트림인 XXXStream을 박싱하여 Wrapper 타입의 Stream<XXX>으로 반환
Stream<Double> doubleStream = new Random().doubles(5).boxed();
doubleStream.forEach(value -> System.out.print(value + " / "));
System.out.println();
문자열을 intStream으로 변환
chars()
IntStream helloworldChars = "I'm Joy".chars();
helloworldChars.forEach(v -> System.out.print(v + " ")); // 73 39 109 32 74 111 121
System.out.println();
문자열을 split하여 stream으로 생성
Pattern.compile()
splitAsStream()
Stream<String> splitStream = Pattern.compile(", ").splitAsStream("stand, on, my, own");
splitStream.forEach(System.out::println);
Stream.of()
를 이용한 생성
concat()
을 이용하여 두 개의 스트림을 동일 타입 스트림으로 합칠 수 있음
Stream<String> stringStream1 = Stream.of("Cheese", "is", "on", "fire🔥");
Stream<String> stringStream2 = Stream.of("He", "made", "it", "🌟");
// stringStream1.forEach(System.out::println);
// stringStream2.forEach(System.out::println);
/* 위 코드를 주석하지 않을 시, IllegalStateException 발생 */
Stream<String> concatStream = Stream.concat(stringStream1, stringStream2);
concatStream.forEach(v -> System.out.print(v + " "));
public class StudentDTO {
private int no;
private int classNo;
private String name;
private int score;
/* 생성자, getter&setter, toString */
}
💁♀️ 중간 연산 ?
스트림을 전달 받아 또 다른 스트림을 반환하는 연산으로 연속으로 연결해서(chaining) 사용할 수 있음
- Filtering, Mapping, Sorting 등의 가공 작업을 말함
filter()
: 조건에 맞지 않는 요소 제거
IntStream stream1 = IntStream.rangeClosed(1, 10);
System.out.println("===== 짝수 값만 필터링 된 스트림 결과 출력 =====");
stream1.filter(i -> i % 2 == 0).forEach(System.out::print); // 246810
System.out.println();
map()
: 스트림 요소 변환
Stream<String> stream2 = Stream.of("WOW", "It", "is", "difficult");
System.out.println("===== 문자열을 문자열의 길이로 변환한 스트림 결과 출력 =====");
stream2.map(s -> s.length()).forEach(System.out::print); // 3229
System.out.println();
sorted()
: 정렬 Comparator를 전달하여 스트림 요소 정렬, Comparator를 전달하지 않을 시 기본 정렬(Comparable)로 정렬
Stream<String> stream3 = Stream.of("Cream", "Cheese", "is", "amazingly", "YUM!");
System.out.println("===== 정렬 된 문자 스트림 결과 출력 =====");
// stream3.sorted().forEach(System.out::println); // 기본 정렬
// stream3.sorted(Comparator.reverseOrder()).forEach(System.out::println); // 정렬 역순
stream3.sorted(Comparator.comparing(String::length)).forEach(System.out::println); // 문자열 길이순 정렬
Stream<StudentDTO> stream4 = Stream.of(new StudentDTO(1, 3, "신짱구", 90), new StudentDTO(2, 1, "흰둥이", 80),
new StudentDTO(3, 1, "신짱아", 85), new StudentDTO(4, 2, "봉미선", 95), new StudentDTO(5, 2, "신형만", 75));
System.out.println("===== 점수 기준으로 정렬 된 학생 스트림 결과 출력 =====");
stream4.sorted(Comparator.comparing(StudentDTO::getScore)).forEach(System.out::println);
💁♀️ 최종 연산 ?
중간 연산을 통해 변환 된 스트림은 마지막으로 최종 연산을 통해 각 요소를 소모하여 결과를 표시. 지연 되었던 모든 중간 연산이 최종 연산 시에 수행.
forEach()
: 스트림의 모든 요소를 소모하여 지정 된 작업을 수행 보통 스트림의 모든 요소를 출력하는 용도로 많이 사용
System.out.println("===== 스트림 요소 출력 =====");
IntStream.range(1, 10).forEach(System.out::println);
System.out.println();
reduce()
: 스트림의 요소를 하나씩 줄여가며 누적 연산 수행
System.out.println("===== 요소 소모 결과 출력 =====");
IntStream stream1 = IntStream.rangeClosed(1, 50);
int sum = stream1.reduce(0, (a, b) -> a + b); // 1~50을 더한 결과
System.out.println(sum); // 1275
Stream<String> stream2 = Stream.of("apple", "banana", "cat", "dog");
/* java.util.Optional<T> 'T' 타입의 객체를 포장해주는 래퍼 클래스(Wrapper Class)로 모든 타입의 참조 변수를 저장 가능 */
Optional<String> result1 = stream2.reduce((s1, s2) -> s1 + " * " + s2);
/*
* Optional 래퍼 클래스는 예상하지 못한 NullPoiniterException을 회피하기 위한 메소드를 제공
* ifPresent()는 Optional 객체가 값을 가지고 있으면 실행하고 null인 경우 실행 X
*/
result1.ifPresent(System.out::println); // value가 있을 때만 실행
/* 인수로 초기 값을 전달하는 reduce 메소드의 반환 타입은 Optional<T>가 아니라 T 타입 */
Stream<String> stream3 = Stream.of("peach", "grape", "rabbit", "tiger");
String sum2 = stream3.reduce("⚡초기 값⚡", (s1, s2) -> s1 + " * " + s2);
System.out.println(sum2);
// 가장 스코어가 높은 학생 찾기
Stream<StudentDTO> stream4 = Stream.of(new StudentDTO(1, 3, "짱구", 90), new StudentDTO(2, 1, "철수", 80),
new StudentDTO(3, 1, "유리", 85), new StudentDTO(4, 2, "훈이", 95), new StudentDTO(5, 2, "맹구", 75));
StudentDTO student = stream4.reduce((a, b) -> (a.getScore() > b.getScore()) ? a : b).get(); // a, b 매개변수를 통해 학생들이 한 명씩 걸러짐
System.out.println("가장 스코어가 높은 학생 : " + student);
count()
: 요소의 개수 long 타입으로 반환
sum()
: 요소의 합계 반환
IntStream stream5 = IntStream.of(99, 44, 55, 88, 77);
System.out.println(stream5.count()); // 5
IntStream stream6 = IntStream.of(99, 44, 55, 88, 77);
System.out.println(stream6.sum()); // 363