June 11, 2023
표준화된 방법? 이제까지는 Collection FrameWork(List, Set, Map)을 이용해서 다뤄온 것
컬렉션(List, Set, Map) 과 배열을 다 Stream으로 변환할 수 있다.
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> intStream1 = list.stream(); // 컬렉션, Collections.stream() 이라는 메서드 있음.(Stream 반환)
Stream<String> strStream = Stream.of(new String[]{"a", "b", "c"}); // 배열
Stream<Integer> evenStream = Stream.iterate(0, n->n+2); // 0, 2, 4, 6, ...
Stream<Double> randomStream = Stream.generate(Math::random); // 람다식
IntStream intStream2 = new Random().ints(5); // 난수 스트림(크기가 5);
중간 연산
최종 연산
stream.distinct().limit(5).sorted().forEach(System.out::println)
distinct()
limit(5)
sorted()
forEach(System.out::println)
String[] strArr = {"dd", "aaa", "CC", "cc", "b"};
Stream<String> stream = Stream.of(strArr); // 문자열 배열이 소스인 스트림
Stream<String> filteredStream = stream.filter(); // 걸러내기 (중간 연산)
Stream<String> distinctedStream = stream.distinct(); // 중복제거 (중간 연산)
Stream<String> sortedStream = stream.sort(); // 정렬 (중간 연산)
Stream<String> limitedStream = stream.limit(5); // 스트림 자르기 (중간 연산)
int total = stream.count(); // 요소 개수 세기(최종연산)
List<Integer> list = Arrays.asList(3, 1, 5, 4, 2);
List<Integer> sortedList = list.stream().sorted() // list를 정렬해서
.collect(Collectors.toList()); // 새로운 List에 저장
System.out.println(list); // [3, 1, 5, 4, 2]; // 변경없음!
System.out.println(sortedList); // [1, 2, 3, 4, 5];
strStream.forEach(System.out::println); // 모든 요소를 화면에 출력(최종연산), forEach 가 최종연산(요소를 소모)
int numOfStr = strStream.count(); // 에러. 스트림이 이미 닫혔음. count()가 최종연산. 중간연산도 할 수 없음
로또 번호 출력하기
IntStream intStream = new Random().ints(1, 46); // 1~45 범위의 무한 스트림(유한 스트림도 존재). 무한스트림이라서 스트림의 갯수가 없다. 즉 난수를 끝도 없이 줌.
intStream.distinct().limit(6).sorted() // 중간 연산(중복제거, 자르기, 정렬)
.forEach(i -> System.out.print(i + ",")); // 최종 연산
for (String str : strList)
System.out.println(str);
stream.forEach(System.out::println); // forEach는 최종 연산이다.
이유는 forEach의 내부 코드에서 찾을 수 있다.
void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action); // 매개변수의 널 체크
for(T t : src) // 내부 반복(for문을 메서드 안으로 넣음)
action.accept(T);
}
Stream<String> strStream = Stream.of("dd", "aaa", "CC", "cc", "b");
int sum = strStream.parallel() // 병렬 스트림으로 전환(속성만 변경)
.mapToInt(s -> s.length()).sum(); // 모든 문자열의 길이의 합
오토박싱 & 언박싱의 비효율이 제거됨(Stream<Integer>
대신 IntStream 사용)
<T>
이므로 기본형이 아니라 T부분에 참조형이 들어가야 한다. 그래서 만약 {1, 2, 3}을 스트림으로 변환하게 되면 오토박싱이 되어 new Integer(1) 이런식으로 들어가게 되며, 반대로 실제로 연산이 이루어 질 때는 언박싱이 이루어지게 된다. 그런데 기본형 스트림을 사용하게 되면 오토박싱 & 언박싱 과정이 이루어지지 않는다.숫자와 관련된 유용한 메서드를 Stream<T>
보다 더 많이 제공
<T>
같은 경우에는 숫자외에도 여러 타입의 스트림이 가능해야 하므로 count()정도의 메서드 밖에 제공하지 않는데 기본스트림 같은 경우에는 애초부터 숫자이기 때문에 count(), sum(), average() 등의 함수가 다 제공된다.스트림을 만드는 3단계에서 1단계에 대한 내용
스트림 생성
Collection 인터페이스의 stream()으로 컬렉션을 스트림으로 변환
Stream<E> stream() // Collection 인터페이스의 메서드
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> intStream = list.stream(); // list를 데이터 소스로 하는 새로운 스트림 생성
// 스트림의 모든 요소를 출력
intStream.forEach(System.out::print); // 12345, forEach() 최종연산
intStream.forEach(System.out::print); // 에러. 스트림이 이미 닫혔다.
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> intStream = list.stream(); // list를 데이터 소스로 하는 새로운 스트림 생성
intStream.forEach(System.out::print); // 12345, forEach() 최종연산
intStream = list.stream(); // 재생성 (stream은 1회용) - stream에 대해 최종연산을 수행하면 stream이 닫힌다.
intStream.forEach(System.out::print); // 12345, forEach() 최종연산
Stream<T> Stream.of(T... values) // 가변인자
Stream<T> Stream.of(T[])
Stream<T> Arrays.stream(T[])
Stream<T> Arrays.stream(T[] array, int startInclusive, int endExclusive) // 마지막 index는 포함안됨
Stream<String> strStream = Stream.of("a", "b" ,"c"); // 가변인자
Stream<String> strStream = Stream.of(new String[]{"a", "b", "c"});
Stream<String> strStream = Arrays.stream(new String[]{"a", "b", "c"});
Stream<String> strStream = Arrays.stream(new String[]{"a", "b", "c"}, 0, 3);
IntStream IntStream.of(int... values) // Stream이 아니라 IntStream
IntStream IntStream.of(int[])
IntStream Arrays.stream(int[])
IntStream Arrays.stream(int[] array, int startInclusive, int endExclusive)
// 기본형 스트림 생성하기
int[] intArr = {1, 2, 3, 4, 5};
IntStream intStream = Arrays.stream(intArr);
// intStream.forEach(System.out::println); // 가능
// System.out.println("count = " + intStream.count()); // 가능. 최종연산
// System.out.println("sum = " + intStream.sum()); // 가능. 최종연산
System.out.println("average = " + intStream.average()); // 가능. 최종연산
// 위를 아래의 Stream으로도 할 수 있다.
Integer[] intArr = {1, 2, 3, 4, 5}; // 자동으로 AutoBoxing이 됨
Stream<Integer> intStream = Arrays.stream(intArr);
// intStream.forEach(System.out::println); // 가능
Systme.out.println("count = " + intStream.count()); // count = 5 나온다 . 그런데 sum(), average() 같은 함수가 없다.
IntStreamintStream = new Random().ints(); // 무한 스트림
intStream.limit(5).forEach(System.out::println); // 5개의 요소만 출력한다.
IntStream intStream = new Random().ints(5); // 크기가 5인 난수 스트림을 반환
Integer.MIN_VALUE <= ints() <= Integer.MAX_VALUE
LONG.MIN_VALUE <= longs() <= LONG.MAX_VALUE
0.0 <= doubles() <= 1.0
// 무한 스트림
IntStream ints(int begin, int end)
LongStream longs(long begin, long end)
DoubleStream doubles(double begin, double end)
// 유한 스트림
IntStream ints(long streamSize, int begin, int end)
LongStream longs(long streamSize, long begin, long end)
DoubleStream doubles(long streamSize, double begin, double end)
IntStream intStream = new Random().ints(); // 무한스트림
intStream
.limit(5) // 5개만 자르기. 이게 없으면 무한으로 계속 반환함.
.forEach(System.out::println);
IntStream intStream1 = new Random().ints(5); // 유한스트림
intStream1
.forEach(System.out::println); // 5개 아무거나 출력
IntStream intStream2 = new Random().ints(5, 10); // 유한스트림, 범위 줘서
intStream2
.limit(10)
.forEach(System.out::println); // 5 ~ 10 사이에 있는 수 아무거나 10개 반환
IntStream intStream3 = new Random().ints(10, 5, 10); // 유한스트림, 범위 줘서
intStream3
.forEach(System.out::println); // 5 ~ 10 사이에 있는 수 아무거나 10개 반환
IntStream IntStream.range(int begin, int end) // end 제외
IntStream IntStream.rangeClosed(int begin, int end) // end 포함
IntStream intStream = IntStream.range(1, 5); // 1, 2, 3, 4
IntStream intStream = IntStream.rangeClsoed(1, 5); // 1, 2, 3, 4, 5
람다식을 소스로 하는 스트림 생성하기
static <T> Stream<T> iterate(T seed, UnaryOperator<T> f) // 이전 요소에 종속적, UnaryOperator는 단항 연산자. 즉, 하나를 넣으면 결과 하나 나옴
static <T> Stream<T> generate(Supplier<T> s) // 이전 요소에 독립적, Supplier는 주기만 하는 것. 즉, 입력은 없고 출력은 있다.
iterate()
는 이전 요소를 seed로 해서 다음 요소를 계산한다.(종속적)
Stream<Integer> evenStream = Stream.iterate(0, n -> n+2); // 0, 2, 4, 6, ...
n -> n + 2
----------
0 -> 0 + 2
2 -> 2 + 2
4 -> 4 + 2
...
Stream<Integer> intStream = Stream.iterate(0, n -> n + 2);
intStream.forEach(System.out::println); 무한으로 +2 더한값들이 생성
Stream<Integer> intStream = Stream.iterate(0, n -> n + 2);
intStream.limit(10).forEach(System.out::println); // 10개만 출력 0, 2, 4, 6, ...
Stream<Integer> intStream = Stream.iterate(1, n -> n + 2);
intStream.limit(10).forEach(System.out::println); // 1부터 홀수 10개 1, 3, 5, 7, ...
generate()
는 seed를 사용하지 않는다.(독립적)
Stream<Double> randomStream = Stream.generate(Math::random); // () -> Math.random(); 즉, 메서드 호출한 값을 계속 생성해 나가는 무한 스트림
Stream<Integer> oneStream = Stream.generate(() -> 1); // 1, 1, 1, 1... 입력은 없고 계속 결과 1만 반환하는 무한 스트림
Stream<Integer> oneStream = Stream.generate(() -> 1);
oneStream
.limit(10) // limit(10) 이 없다면 1 무한으로 찍힘
.forEach(System.out::println); // 1, 1, 1 .. 총 10개
Stream<Path> Files.list(Path dir) // Path는 파일 또는 디렉토리
Stream<String> Files.lines(Path path) // 파일 내용을 라인 단위로. log파일 분석 같은 경우 유리
Stream<String> Files.lines(Path path, Charset cs)
Stream<String> lines() // BufferedReader클래스의 메서드
Stream emptyStream = Stream.empty(); // empty()는 빈 스트림을 생성해서 반환한다.
long count = emptyStream.count(); // count의 값은 0
스트림
인 연산. 반복적으로 적용가능stream.distinct().limit(5).sorted().forEach(System.out::println)
distinct()
limit(5)
sorted()
forEach(System.out::println)
String[] strArr = {"dd", "aaa", "CC", "cc", "b"};
Stream<String> stream = Stream.of(strArr); // 문자열 배열이 소스인 스트림
Stream<String> filteredStream = stream.filter(); // 걸러내기 (중간 연산)
Stream<String> distinctedStream = stream.distinct(); // 중복제거 (중간 연산)
Stream<String> sortedStream = stream.sort(); // 정렬 (중간 연산)
Stream<String> limitedStream = stream.limit(5); // 스트림 자르기 (중간 연산)
int total = stream.count(); // 요소 개수 세기(최종연산)
limit(5)
skip(3)
peek
sorted()
sorted(Comparator<T>
comparator)
map, flatMap
void forEachOrderd(Consumer<? super T> action)
병렬스트림으로 사용할 때
만 작업순서를 유지하고 싶을 때 사용Optional<T>
findAny(), Optional<T>
findFirst()
reduce()와 collect()
skip()
limit()
distinct()
filter()
sorted()
Stream<T> skip(long n) // 앞에서부터 n개 건너뛰기
Stream<T> limit(long maxSize) // maxSize 이후의 요소는 잘라냄
IntStream intStream = IntStream.rangeClosed(1, 10); // 1 2 3 4 5 6 7 8 9 10
intStream.skip(3).limit(5).forEach(System.out::print); // 4 5 6 7 8, skip(3) 으로 1 2 3 건너뛰고 그 다음 45678 5개 선택된 것
Stream<T> filter(Predicate<? supter T> predicate) // 조건에 맞지 않는 요소 제거
Stream<T> distinct()
IntStream intStream = IntStream.of(1, 2, 2, 3, 3, 3, 4, 5, 5, 6);
intStream.distinct().forEach(System.out::print); // 1 2 3 4 5 6
IntStream intStream = IntStream.rangeClosed(1, 10); // 1 2 3 4 5 6 7 8 9 10
intStream.filter(i -> i%2 == 0).forEach(System.out::println); // 2의 배수만 출력 2 4 6 8 10
IntStream intStream = IntStream.rangeClosed(1, 10); // 1 2 3 4 5 6 7 8 9 10
intStream.filter(i -> i%2 != 0 && i % 3 != 0).forEach(System.out::println); // 1 5 7
intStream.filter(i -> i%2 != 0).filter(i -> i%3 != 0).forEach(System.out::println); // 1 5 7, filter()는 중간연산이고 stream을 반환하므로 여러번 사용해도 된다. 홀수 중에서 3의 배수가 아닌 것들
Stream<T> sorted() // 스트림 요소의 기본 정렬(Comparable)로 정렬, 특정한 기준을 주지 않을 시.
Stream<T> sorted(Comparator<? super T> comparator) // 지정된 Comparator로 정렬, 특정한 기준을 준 것.
<String>
naturalOrder().reversed()) 와 결과는 같음. naturalOrder() 정상순의 다시 reverse() 역순이므로.strStream.sorted(String.CASEINSENSITIVEORDER)
static Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator();
가 있다. CASEINSENSITIVEORDER가 Comparator인데 이 말은 String클래스가 Comparator를 이미 만들어서 갖고 있다는 것. 자주 사용하니까 이미 만들어 둔 것임.strStream.sorted(Comparator.comparing(String::length))
comparing(Function<T, U> keyExtractor)
comparing(Function<T, U> keyExtractor, Comparator<U> keyComparator)
Stream<T> sorted(Comparator comparator)
므로 파라미터로 Comparator를 받으니까, comparing() 의 반환타입도 Comparator니까 맞음.studentStream.sorted(Comparator.comparing(Student::getBan)) // 반별로 정렬
.forEach(System.out::println);
thenComparing(Comparator<T> other)
thenComparing(Function<T, U> keyExtractor)
thenComparing(Function<T, U> keyExtractor, Compartor<U> keyComp)
studentStream.sorted(Comparator.comparing(Student::getBan) // 반별로 정렬
.thenComparing(Student::getTotalScore) // 총점별로 정렬
.thenComparing(Student::getName)) // 이름별로 정렬
.forEach(System.out::println);)
class Student implements Comparable<Student> {
String name;
int ban;
int totalScore;
Student(String name, int ban, int totalScore) {
this.name = name;
this.ban = ban;
this.totalScore = totalScore;
}
public String toString() {
return String.format("[%s, %d, %d]", name, ban, totalScore);
}
String getName() { return name;}
int getBan() { return ban;}
int getTotalScore() { return totalScore;}
// 총점 내림차순을 기본 정렬로 한다. 300 200 100..
@Override
public int compareTo(Student s) {
return s.totalScore - this.totalScore;
}
}
public class Test {
public static void main(String[] args) {
Stream<Student> studentStream = Stream.of(
new Student("이자바", 3, 300),
new Student("김자바", 1, 200),
new Student("안자바", 2, 100),
new Student("박자바", 2, 150),
new Student("소자바", 1, 200),
new Student("나자바", 3, 290),
new Student("김자바", 3, 180)
);
studentStream.sorted(Comparator.comparing(Student::getBan) // 1. 반별 정렬
.thenComparing(Comparator.naturalOrder())) // 2. 기본정렬
.forEach(System.out::println);
}
}
[김자바, 1, 200]
[소자바, 1, 200]
[박자바, 2, 150]
[안자바, 2, 100]
[이자바, 3, 300]
[나자바, 3, 290]
[김자바, 3, 180]
studentStream.sorted(Comparator.comparing(Student::getBan).reversed() // 1. 반별 정렬
.thenComparing(Comparator.naturalOrder()).reversed()) // 2. 기본정렬
.forEach(System.out::println);
특정 값을 기준으로 정렬
// 생략. StockService.class 임
public List<Stock> getStockListSorted() {
// repository 에서 stockList 들고옴.
stockList = stockList.stream()
.sorted(Comparator.comparingInt(StockService::getOrderNumber))
.collect(Collectors.toList());
return stockList;
}
public static int getOrderNumber(Stock stockList) {
byte status = stock.getStatus();
if (status == 1) {
return -1;
} else if (status == 2) {
return 0;
} else {
return 1;
}
}
map()
peek()
flatmap()
Stream<R> map(Function<? super T, ? extends R> mapper) // Stream<T> -> Stream<R>
Stream<File> fileStream = Stream.of(new File("Ex1.java"), new File("Ex1")),
new File("Ex1.bak"), new File("Ex2.java"), new File("Ex1.txt"));
Stream<String> filenameStream = fileStream.map(File::getName); // map()을 이용해서 결국 File -> String 스트림으로 변환하는 것.
filenameStream.forEach(System.out::println); // 스트림의 모든 파일의 이름을 출력
<File>
)에서 파일 확장자(대문자)를 중복없이 뽑아내기fileStream.map(File::getName) // Stream<File> -> Stream<String>
.filter(s->s.indexOf('.')!=-1) // 확장자가 없는 것은 제외
.map(s->s.substring(s.indexOf('.')+1)) // Stream<String> -> Stream<String>, 확장자만 뽑아낸 것. Ex1.bak -> bak
.map(String::toUpperCase) // Stream<String> -> Stream<String>, 대문자로 변경
.distinct() // 중복 제거
.forEach(System.out::println); // JAVABAKTXT
public class Test2 {
public static void main(String[] args) {
File[] fileArr = { new File("Ex1.java"), new File("Ex1.bak"),
new File("Ex2.java"), new File("Ex1"), new File("Ex1.txt")
};
Stream<File> fileStream = Stream.of(fileArr);
// map()으로 Stream<File>을 Stream<String>으로 변환
Stream<String> filenameStream = fileStream.map(File::getName);
filenameStream.forEach(System.out::println); // 모든 파일의 이름을 출력(최종연산이라 다 소모함)
fileStream = Stream.of(fileArr); // 스트림을 다시 생성
fileStream.map(File::getName) // Stream<File> -> Stream<String>
.filter(s -> s.indexOf('.')!=-1) // 확장자가 없는 것은 제외
.map(s -> s.substring(s.indexOf('.') + 1)) // 확장자만 추출
.map(String::toUpperCase) // 모두 대문자로 변환
.distinct() // 중복 제거
.forEach(System.out::println); // JAVABAKTXT
System.out.println();
}
}
디버깅 용도
Stream<T> peek(Consumer<? super T> action) // 중간 연산(스트림의 요소를 소비x)
void forEach(Consumer<? super T> action) // 최종 연산(스트림의 요소를 소비O)
fileStream.map(File::getName) // Stream<File> -> Stream<String>
.filter(s -> s.indexOf('.')!=-1) // 확장자가 없는 것은 제외
.peek(s -> System.out.printf("filename=%s%n", s)) // 파일명을 출력한다.(중간작업결과 확인)
.map(s -> s.substring(s.indexOf('.')+1)) // 확장자만 추출
.peek(s -> System.out.printf("extension=%s%n", s)) // 확장자를 출력한다.(중간작업결과 확인)
.forEach(System.out::println); // 최종연산 스트림을 소비
public class Test {
public static void main(String[] args) {
File[] fileArr = { new File("Ex1.java"), new File("Ex1.bak"),
new File("Ex2.java"), new File("Ex1"), new File("Ex1.txt")
};
Stream<File> fileStream = Stream.of(fileArr);
// map()으로 Stream<File>을 Stream<String>으로 변환
Stream<String> filenameStream = fileStream.map(File::getName);
filenameStream.forEach(System.out::println); // 모든 파일의 이름을 출력(최종연산이라 다 소모함)
fileStream = Stream.of(fileArr); // 스트림을 다시 생성
fileStream.map(File::getName) // Stream<File> -> Stream<String>
.filter(s -> s.indexOf('.')!=-1) // 확장자가 없는 것은 제외
.peek(s -> System.out.printf("filename=%s%n", s)) // 중간 확인
.map(s -> s.substring(s.indexOf('.') + 1)) // 확장자만 추출
.peek(s -> System.out.printf("extension=%s%n", s)) // 중간 확인
.map(String::toUpperCase) // 모두 대문자로 변환
.distinct() // 중복 제거
.forEach(System.out::println); // JAVABAKTXT
System.out.println();
}
}
Stream<String[]> strArrStrm = Stream.of(new String[]{"abc", "def", "ghi"},
new String[]{"ABC", "GHI", "JKLMN"});
Stream<Stream<String>> strStrStrm = strArrStrm.map(Arrays::stream);
Stream<String> strStrStrm = strArrStrm.flatMap(Arrays::stream); // Arrays.stream(T[])
public class Test {
public static void main(String[] args) {
Stream<String[]> strArrStrm = Stream.of(
new String[]{"abc", "def", "jkl"},
new String[]{"ABC", "GHI", "JKL"}
);
// Stream<Stream<String>> strStrmStrm = strArrStrm.map(Arrays::stream); // 이렇게 하면 Stream 안에 Stream이 존재하게 되어 우리가 원하는 결과가 나오지 않는다.
Stream<String> strStrm = strArrStrm.flatMap(Arrays::stream);
strStrm.map(String::toLowerCase) // 스트림의 요소를 모두 소문자로 변경
.distinct() // 중복제거
.sorted() // 정렬
.forEach(System.out::println);
System.out.println();
String[] lineArr = {
"Believe or not It is true",
"Do or do not There is no try"
};
Stream<String> lineStream = Arrays.stream(lineArr); // 두 문장에 있는 단어 하나하나(String)를 Stream의 요소로 만들고 싶은 것
lineStream.flatMap(line -> Stream.of(line.split(" +")))
.map(String::toLowerCase)
.distinct()
.sorted()
.forEach(System.out::println);
System.out.println();
}
}
(위 코드) lineStream.flatMap(line -> Stream.of(line.split(" +")))
<String>
---map()을 사용하면—> Stream<Stream<String>>
이 되버린다.<String>
---flatMap()을 사용하면—> Stream<String>
이 된다.<String>
이 나온다.void forEach()(Consumer<? super T> action) // 병렬스트림인 경우 순서가 보장되지 않음, 꼭 순서를 유지하지 않아도 되는 경우에는 forEachOrdered 보다 더 빠름
void forEachOrdered(Consumer<? super T> action) // 병렬스트림인 경우에도 순서가 보장됨
sequential()
IntStream.range(1, 10).sequential().forEach(System.out::print); // 123456789
IntStream.range(1, 10).sequential().forEachOrdered(System.out::print) // 123456789
parellel()
IntStream.range(1, 10).parellel().forEach(System.out::print); // 523857172, 섞여서 나옴. 여러 쓰레드가 나눠서 처리하므로 순서보장x
IntStream.range(1, 10).parellel().forEachOrdered(System.out::print) // 123456789, 병렬처리함에도 순서보장이 됨
boolean allMatch (Predicate<? super T> predicate) // 모든 요소가 조건을 만족시키면 true
boolean anyMatch (Predicate<? super T> predicate) // 한 요소라도 조건을 만족시키면 true
boolean noneMatch (Predicate<? super T> predicate) // 모든 요소가 조건을 만족시키지 않으면 true
Stream<Student> stuStream = new ~~(생략)~~
boolean hasFailedStu = stuStream.anyMatch(s -> s.getTotalScore() <= 100); // 한 명이라도 있으면 true
<T>
로 반환Optional<T> findFirst() // 첫 번째 요소를 반환. 순차 스트림에 사용. 찾다가 처음으로(first) 조건에 맞는 것을 반환.
Optional<T> findAny() // 아무거나 하나를 반환. 병렬 스트림에 사용. 여러 쓰레드들 중에서 조건을 먼저 발견한 쓰레드가 결과를 반환. 조건에 맞는 것들이 여러개 있다고 해도 각각에 해당하는 쓰레드들 중에서 무엇이 반환할지 모르니까 any.
Stream<Student> stuStream = new ~~(생략)~~
Optional<Student> result = stuStream.filter(s -> s.getTotalScore() <= 100).findFirst(); // 낙제자 중 첫번째 요소 반환
Optional<Student> result = parallelStream.filter(s -> s.getTotalScore() <= 100).findAny(); // 병렬스트림일 경우 여러 요소중에 먼저 발견한 쓰레드가 반환하는 요소 반환
스트림의 최종 연산은 모두 reduce()로 만들어져 있다. ex) count(), max(), min(), collect() 등
Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity, BinaryOperator<T> accumulator)
U reduce(U identity, BiFunction<U,T,U> accumulator, BinaryOperator<U> combiner)
identity
accumulator
combiner
Optional<T> reduce(BinaryOperator<T> accumulator)
<T>
T reduce(T identity, BinaryOperator<T> accumulator)
// int reduce(int identity, IntBinaryOperator op)
int count = intStream.reduce(0, (a, b) -> a + 1);
int sum = intStream.reduce(0, (a, b) -> a + b);
int a = identity; // 초기값이자 누적결과를 저장할 변수
for (int b : stream)
a = a + b; // sum(), 이곳에 연산식이 들어간다. min()이든, count()든, 새로운 식이든
int max = intStream.reduce(Integer.MIN_VALUE, (a, b) -> a > b ? a : b); // max()
int min = intStream.reduce(Integer.MAX_VALUE, (a, b) -> a < b ? a : b); // min()
public class Test {
public static void main(String[] args) {
String[] strArr = {
"Inheritance", "Java", "Lambda", "stream",
"OptionalDouble", "IntStream", "count", "sum"
};
Stream.of(strArr)
.parallel() // 병렬로 처리하면 출력 순서 보장X. 실행시마다 다름
// .forEach(System.out::println);
.forEachOrdered(System.out::println); // 병렬로 처리하면서 순서도 유지하고자 한다면
boolean noEmptyStr = Stream.of(strArr).noneMatch(s -> s.length() == 0);
Optional<String> sWord = Stream.of(strArr)
.parallel() // 랜덤으로 처리
.filter(s -> s.charAt(0) == 's')
// .findFirst(); // "stream" 나옴
.findAny();
System.out.println("noEmptyStr = " + noEmptyStr); // noEmptyStr = true
System.out.println("sWord = " + sWord.get()); // sWord = stream (parallel() 때문에 sum, stream 중 랜덤으로 나옴, 병렬로 처리하니깐 어떤 것이 발견될지 모름)
// Stream<String>을 Stream<Integer>으로 변환, 각 요소를 객체로 다룸. (s) -> s.length()
Stream<Integer> intStream0 = Stream.of(strArr).map(String::length); // IntStream {11, 4, 6, 6, 14, 9, 5, 3} 생성
// Stream<String >을 IntStream으로 변환. IntStream기본형 스트림. (성능 때문에). 각 요소를 기본형으로 다룸
IntStream intStream1 = Stream.of(strArr).mapToInt(String::length);
IntStream intStream2 = Stream.of(strArr).mapToInt(String::length);
IntStream intStream3 = Stream.of(strArr).mapToInt(String::length);
IntStream intStream4 = Stream.of(strArr).mapToInt(String::length);
int count = intStream1.reduce(0, (a, b) -> a + 1); // count = 8, 요소 수
int sum = intStream2.reduce(0, (a, b) -> a + b); // sum = 58, 단어수 합
// OptionalInt max = intStream3.reduce(Integer::max); // 초기값이 없어 산출값이 없을수도 있으므로 Optional로 반환
OptionalInt max = IntStream.empty().reduce(Integer::max); // 초기값이 없어 산출값이 없을수도 있으므로 Optional로 반환
OptionalInt min = intStream3.reduce(Integer::min);
System.out.println("count = " + count);
System.out.println("sum = " + sum);
// System.out.println("min = " + max.getAsInt()); // 안에 요소 없으면 에러 반환
System.out.println("max = " + max.orElse(0)); // 그래서 요소가 없으면 0을 반환하는 것으로 수정
System.out.println("min = " + min.getAsInt()); // 안에 요소가 없으면 에러 반환
}
}
Object collect(Collector collector) // Collector를 구현한 클래스의 객체를 매개변수로
object collect(Supplier supplier, BiConsumer accumulator, BiConsumer combiner) // 잘 안 쓰임
reduce() 와 collect() 의 차이는?
supplier() 와 accumulator()가 핵심
Student[] stuNames = studentStream.toArray(Student[]::new); // OK
Student[] stuNames = studentStream.toArray(); // 에러, 반환타입이 Object임.
Student[] stuNames = (Student[])studentStream.toArray(); // OK. 자동 형변환이 안된다는 뜻
Object[] stuNames = studentStream.toArray(); // OK
갯수
long count = stuStream.count();
long count = stuStream.collect(counting()); // Collectors.counting(), 즉 static import 한 것.
합계
long totalScore = stuStream.mapToInt(Student::getTotalScore).sum(); // IntStream의 sum()
long totalScore = stuStream.collect(summingInt(Student::getTotalScore));
최대값
OptionalInt topScore = studentStream.mapToInt(Student::getTotalScore).max();
Optional<Student> topStudent = stuStream
.max(Comparator.comparingInt(Student::getTotalScore));
Optional<Studnet> topStudent = stuStream
.collect(maxBy(Comparator.comparingInt(Student::getTotalScore)));
reduce()와 기능이 같다.
Collector reducing(BinaryOperator<T> op)
Collector reducing(T identity, BinaryOperator<T> op) // T identity 는 초기화, BinaryOperator<T> op 은 누적작업을 의미
Collector reducing(U identity, Function<T, U> mapper, BinaryOperator<U> op) // map + reduce. 리듀싱하기 전에 변환(ex. map)이 필요할 경우 사용
Collector reducing(T identity, BinaryOperator<T> op)
를 대부분 사용한다.IntStream intStream = new Random().ints(1, 46).distinct().limit(6);
OptionalInt max = intStream.reduce(Integer::max); // 전체 리듀싱
Optional<Integer> max = intStream.boxed().collect(reducing(Integer::max)); // 그룹별 리듀싱 가능
long sum = intStream.reduce(0, (a, b) -> a + b);
long sum = intStream.boxed().collect(reducing(0, (a, b) -> a + b));
int grandTotal = stuStream.map(Student::getTotalScore).reduce(0, Integer::sum);
int grandTotal = stuStream.collect(reducing(0, Student::getTotalScore, Integer::sum));
String studentNames = stuStream.map(Student::getName).collect(joining()); // Stream<Studnet> -> Stream<String>.
String studentNames = stuStream.map(Student::getName).collect(joining(",")); // 구분자임. 김자바, 이자바, 박자바, ...
String studentNames = stuStream.map(Student::getName).collect(joining(",", "[", "]")); // ,는 구분자, [와 ]는 앞뒤로. 그래서 [김자바, 이자바, 박자바, ...]
String studentInfo = stuStream.collect(joining(",")); // Student의 toString()으로 결합
Collector partitioningBy(Predicate predicate)
Colelctor partitioningBy(Predicate predicate, Collector downstream)
Map<Boolean, List<Student>> stuBySex = stuStream
.collect(partitioningBy(Student::isMale)); // 학생들을 성별로 분할, 남/여로 나눠진다 (2분할). 여기서 사실 groupingBy를 사용해도 되지만 굳이. 그리고 2분할이라면 partitioningBy가 더 빠르다.
List<Student> maleStudent = stuBySex.get(true); // Map에서 남학생 목록을 얻는다.
List<Student> maleStudent = stuBySex.get(false); // Map에서 여학생 목록을 얻는다.
분할(남/녀)한 것을 count
Map<Boolean, Long> stuNumBySex = stuStream
.collect(partitioningBy(Student::isMale, counting())); // 분할 + 통게
System.out.println("남학생 수 :" + stuNumBySex.get(true)); // 남학생 수 : 8
System.out.println("여학생 수 :" + stuNumBySex.get(false)); // 여학생 수 : 10
학생을 성별로 나누고 최대값
Map<Boolean, Optional<Student>> topScoreBySex = stuStream
.collect(partitioningBy(Student::isMale, maxBy(comparingInt(Student::getScore))));
System.out.println("남학생 1등 : " + topScoreBySex.get(true)); // 남학생 1등 : Optional[[나바자, 남, 1, 1, 300]]
System.out.println("여학생 1등 : " + topScoreBySex.get(false)); // 여학생 1등 : Optional[[김지미, 여, 1, 1, 250]]
Map<Boolean, Map<Boolean, List<Student>>> failedStuBySex = stuStream // 다중 분할 (학생을 남/녀로 나누고 각각에서 합/불로 또 나눔)
.collect(partitioningBy(Student::isMale // 1. 성별로 분할(남/녀)
, partitioningBy(s -> s.getScore() < 150))); // 2. 성적으로 분할(불합격/합격)
List<Student> failedMaleStu = failedStuBySex.get(true).get(true); // 불합격이 true. 위에서 불합격조건을 주었기 때문에.(150점 밑)
List<Student> failedFemaleStu = failedStuBySex.get(false).get(true);
class Student2 {
String name;
boolean isMale; // 성별
int hak; // 학년
int ban; // 반
int score;
Student2(String name, boolean isMale, int hak, int ban, int score) {
this.name = name;
this.isMale = isMale;
this.hak = hak;
this.ban = ban;
this.score = score;
}
String getName() { return name; }
boolean isMale() { return isMale; }
int getHak() { return hak; }
int getBan() { return ban; }
int getScore() { return score; }
public String toString() {
return String.format("[%s, %s, %d학년 %d반, %3d점]",
name, isMale ? "남" : "여", hak, ban, score);
}
// groupingBy()에서 사용
enum Level {HIGH, MId, LOW} // 성적을 상, 중, 하 세 단계로 분류
}
class Test {
public static void main(String[] args) {
Student2[] stuArr = {
new Student2("나자바", true, 1, 1, 300),
new Student2("김지미", false, 1, 1, 250),
new Student2("김자바", true, 1, 1, 200),
new Student2("이지미", false, 1, 2, 150),
new Student2("남자바", true, 1, 2, 100),
new Student2("안지미", false, 1, 2, 50),
new Student2("황지미", false, 1, 3, 100),
new Student2("강지미", false, 1, 3, 150),
new Student2("이자바", true, 1, 3, 200),
new Student2("나자바", true, 2, 1, 300),
new Student2("김지미", false, 2, 1, 250),
new Student2("김자바", true, 2, 1, 200),
new Student2("이지미", false, 2, 2, 150),
new Student2("남자바", true, 2, 2, 100),
new Student2("안지미", false, 2, 2, 50),
new Student2("황지미", false, 2, 3, 100),
new Student2("강지미", false, 2, 3, 150),
new Student2("이자바", true, 2, 3, 200)
};
System.out.printf("1. 단순분할(성별로 분할)%n");
Map<Boolean, List<Student2>> stuBysex = Stream.of(stuArr)
.collect(partitioningBy(Student2::isMale));
List<Student2> maleStudent = stuBysex.get(true);
List<Student2> femaleStudent = stuBysex.get(false);
for(Student2 s : maleStudent) System.out.println(s);
for(Student2 s : femaleStudent) System.out.println(s);
System.out.printf("%n2. 단순분할 + 통계(성별 학생수)%n");
Map<Boolean, Long> stuNumBySex = Stream.of(stuArr)
.collect(partitioningBy(Student2::isMale, counting()));
System.out.println("남학생 수 : " + stuNumBySex.get(true));
System.out.println("여학생 수 : " + stuNumBySex.get(false));
System.out.printf("%n3. 단순분할 + 통계(성별 1등)%n");
Map<Boolean, Optional<Student2>> topScoreBySex = Stream.of(stuArr)
.collect(partitioningBy(Student2::isMale,
maxBy(comparingInt(Student2::getScore))
));
System.out.println("남학생 1등 : " + topScoreBySex.get(true));
System.out.println("여학생 1등 : " + topScoreBySex.get(false));
Map<Boolean, Student2> topScoreBySex2 = Stream.of(stuArr)
.collect(partitioningBy(Student2::isMale,
collectingAndThen(
maxBy(comparingInt(Student2::getScore)), Optional::get
)
));
System.out.println("남학생 1등 : " + topScoreBySex2.get(true));
System.out.println("여학생 1등 : " + topScoreBySex2.get(false));
System.out.printf("%n4. 다중분할(성별 불합격자, 100점 이하)%n");
Map<Boolean, Map<Boolean, List<Student2>>> failedStuBySex =
Stream.of(stuArr).collect(partitioningBy(Student2::isMale, // 성별로 먼저 나누고
partitioningBy(s -> s.getScore() <= 100)) // 100점 이하인지 또 나누고
);
List<Student2> failedMaleStu = failedStuBySex.get(true).get(true); // 남자 & 100점 이하
List<Student2> failedFemaleStu = failedStuBySex.get(false).get(true); // 여자 & 100점 이하
for(Student2 s : failedMaleStu) System.out.println(s);
for(Student2 s : failedFemaleStu) System.out.println(s);
}
}
1. 단순분할(성별로 분할)
[나자바, 남, 1학년 1반, 300점]
[김자바, 남, 1학년 1반, 200점]
[남자바, 남, 1학년 2반, 100점]
[이자바, 남, 1학년 3반, 200점]
[나자바, 남, 2학년 1반, 300점]
[김자바, 남, 2학년 1반, 200점]
[남자바, 남, 2학년 2반, 100점]
[이자바, 남, 2학년 3반, 200점]
[김지미, 여, 1학년 1반, 250점]
[이지미, 여, 1학년 2반, 150점]
[안지미, 여, 1학년 2반, 50점]
[황지미, 여, 1학년 3반, 100점]
[강지미, 여, 1학년 3반, 150점]
[김지미, 여, 2학년 1반, 250점]
[이지미, 여, 2학년 2반, 150점]
[안지미, 여, 2학년 2반, 50점]
[황지미, 여, 2학년 3반, 100점]
[강지미, 여, 2학년 3반, 150점]
2. 단순분할 + 통계(성별 학생수)
남학생 수 : 8
여학생 수 : 10
3. 단순분할 + 통계(성별 1등)
남학생 1등 : Optional[[나자바, 남, 1학년 1반, 300점]]
여학생 1등 : Optional[[김지미, 여, 1학년 1반, 250점]]
남학생 1등 : [나자바, 남, 1학년 1반, 300점]
여학생 1등 : [김지미, 여, 1학년 1반, 250점]
4. 다중분할(성별 불합격자, 100점 이하)
[남자바, 남, 1학년 2반, 100점]
[남자바, 남, 2학년 2반, 100점]
[안지미, 여, 1학년 2반, 50점]
[황지미, 여, 1학년 3반, 100점]
[안지미, 여, 2학년 2반, 50점]
[황지미, 여, 2학년 3반, 100점]
Collector groupingBy(Function classifier)
Collector groupingBy(Function classifier, Collector downstream)
Collector groupingBy(Function classifier, Supplier mapFactory, Collector downstream)
Map<Integer, List<Student>> stuByBan = stuStream // 학생을 반별로 그룹화
.collect(groupingBy(Student::getBan, toList())); // toList() 생략가능
Map<Integer, Map<Integer, List<Student>>> stuByHakAndBan = stuStream // 다중 그룹화
.collect(groupingBy(Student::getHak, // 1. 학년별 그룹화
groupingBy(Student::getBan // 2. 반별 그룹화
)));
Map<Integer, Map<Integer, Set<Student.Level>>> stuByHakAndBan = stuStream
.collect(
groupingBy(Student::getHak, groupingBy(Student::getBan, // 다중 그룹화(학년별, 반별)
mapping(s -> { // 성적등급(Level)으로 변환. List<Student> -> Set<Student.Level>
if (s.getScore() >= 200) return Student.Level HIGH,
else if (s.getScore() >= 100) return Student.Level MID,
else return Student.Level LOW;
}, toSet()) // mapping() // enum Level {HIGH, MID, LOW}
)) // groupingBy()
); // collect()
class Student3 {
String name;
boolean isMale; // 성별
int hak; // 학년
int ban; // 반
int score;
Student3(String name, boolean isMale, int hak, int ban, int score) {
this.name = name;
this.isMale = isMale;
this.hak = hak;
this.ban = ban;
this.score = score;
}
String getName() { return name; }
boolean isMale() { return isMale; }
int getHak() { return hak; }
int getBan() { return ban; }
int getScore() { return score; }
public String toString() {
return String.format("[%s, %s, %d학년 %d반, %3d점]",
name, isMale ? "남" : "여", hak, ban, score);
}
// groupingBy()에서 사용
enum Level {HIGH, MID, LOW} // 성적을 상, 중, 하 세 단계로 분류
}
class Test {
public static void main(String[] args) {
Student3[] stuArr = {
new Student3("나자바", true, 1, 1, 300),
new Student3("김지미", false, 1, 1, 250),
new Student3("김자바", true, 1, 1, 200),
new Student3("이지미", false, 1, 2, 150),
new Student3("남자바", true, 1, 2, 100),
new Student3("안지미", false, 1, 2, 50),
new Student3("황지미", false, 1, 3, 100),
new Student3("강지미", false, 1, 3, 150),
new Student3("이자바", true, 1, 3, 200),
new Student3("나자바", true, 2, 1, 300),
new Student3("김지미", false, 2, 1, 250),
new Student3("김자바", true, 2, 1, 200),
new Student3("이지미", false, 2, 2, 150),
new Student3("남자바", true, 2, 2, 100),
new Student3("안지미", false, 2, 2, 50),
new Student3("황지미", false, 2, 3, 100),
new Student3("강지미", false, 2, 3, 150),
new Student3("이자바", true, 2, 3, 200)
};
System.out.printf("1. 단순그룹화(반별로 그룹화)%n");
Map<Integer, List<Student3>> stubyBan = Stream.of(stuArr)
.collect(groupingBy(Student3::getBan));
for (List<Student3> ban : stubyBan.values()) {
for (Student3 s : ban) {
System.out.println(s);
}
}
System.out.printf("%n2. 단순그룹화(성적별로 그룹화)%n");
Map<Student3.Level, List<Student3>> stuByLevel = Stream.of(stuArr)
.collect(groupingBy(s -> {
if (s.getScore() >= 200) return Student3.Level.HIGH;
else if(s.getScore() >= 100) return Student3.Level.MID;
else return Student3.Level.LOW;
}));
TreeSet<Student3.Level> keySet = new TreeSet<>(stuByLevel.keySet());
for (Student3.Level key : keySet) {
System.out.println("[" + key + "]");
for (Student3 s : stuByLevel.get(key))
System.out.println(s);
System.out.println();
}
System.out.printf("%n3. 단순그룹화 + 통계(성적별 학생수)%n");
Map<Student3.Level, Long> stuCntByLevel = Stream.of(stuArr)
.collect(groupingBy(s -> {
if (s.getScore() >= 200) return Student3.Level.HIGH;
else if (s.getScore() >= 100) return Student3.Level.MID;
else return Student3.Level.LOW;
}, counting()));
for (Student3.Level key : stuCntByLevel.keySet())
System.out.printf("[%s] - %d명, ", key, stuCntByLevel.get(key));
System.out.println();
System.out.printf("%n4. 다중그룹화(학년별, 반별)");
Map<Integer, Map<Integer, List<Student3>>> stuByHakAndBan =
Stream.of(stuArr)
.collect(groupingBy(Student3::getHak,
groupingBy(Student3::getBan)
));
for (Map<Integer, List<Student3>> hak : stuByHakAndBan.values()) {
for (List<Student3> ban : hak.values()) {
System.out.println();
for (Student3 s : ban)
System.out.println(s);
}
}
System.out.printf("%n5. 다중그룹화 + 통계(학년별, 반별1등)%n");
Map<Integer, Map<Integer, Student3>> topStuByHakAndBan =
Stream.of(stuArr)
.collect(groupingBy(Student3::getHak,
groupingBy(Student3::getBan,
collectingAndThen(
maxBy(comparingInt(Student3::getScore))
, Optional::get
)
)
));
for (Map<Integer, Student3> ban : topStuByHakAndBan.values())
for (Student3 s : ban.values())
System.out.println(s);
System.out.printf("%n6. 다중그룹화 + 통계(학년별, 반별 성적그룹)%n");
Map<String, Set<Student3.Level>> stuByScoreGroup = Stream.of(stuArr)
.collect(groupingBy(s -> s.getHak() + "-" + s.getBan(), // "학년-반" 처럼 문자열을 만들어서 이것을 기준으로 grouping 할 수 있다.
mapping(s -> { // 성적등급(Level) 으로 변환. List<Student> -> Set<Student.Level>
if (s.getScore() >= 200) return Student3.Level.HIGH;
else if (s.getScore() >= 100) return Student3.Level.MID;
else return Student3.Level.LOW;
}, toSet())
));
Set<String> keySet2 = stuByScoreGroup.keySet();
for (String key : keySet2) {
System.out.println("[" + key + "]" + stuByScoreGroup.get(key));
}
}
}
1. 단순그룹화(반별로 그룹화)
[나자바, 남, 1학년 1반, 300점]
[김지미, 여, 1학년 1반, 250점]
[김자바, 남, 1학년 1반, 200점]
[나자바, 남, 2학년 1반, 300점]
[김지미, 여, 2학년 1반, 250점]
[김자바, 남, 2학년 1반, 200점]
[이지미, 여, 1학년 2반, 150점]
[남자바, 남, 1학년 2반, 100점]
[안지미, 여, 1학년 2반, 50점]
[이지미, 여, 2학년 2반, 150점]
[남자바, 남, 2학년 2반, 100점]
[안지미, 여, 2학년 2반, 50점]
[황지미, 여, 1학년 3반, 100점]
[강지미, 여, 1학년 3반, 150점]
[이자바, 남, 1학년 3반, 200점]
[황지미, 여, 2학년 3반, 100점]
[강지미, 여, 2학년 3반, 150점]
[이자바, 남, 2학년 3반, 200점]
2. 단순그룹화(성적별로 그룹화)
[HIGH]
[나자바, 남, 1학년 1반, 300점]
[김지미, 여, 1학년 1반, 250점]
[김자바, 남, 1학년 1반, 200점]
[이자바, 남, 1학년 3반, 200점]
[나자바, 남, 2학년 1반, 300점]
[김지미, 여, 2학년 1반, 250점]
[김자바, 남, 2학년 1반, 200점]
[이자바, 남, 2학년 3반, 200점]
[MID]
[이지미, 여, 1학년 2반, 150점]
[남자바, 남, 1학년 2반, 100점]
[황지미, 여, 1학년 3반, 100점]
[강지미, 여, 1학년 3반, 150점]
[이지미, 여, 2학년 2반, 150점]
[남자바, 남, 2학년 2반, 100점]
[황지미, 여, 2학년 3반, 100점]
[강지미, 여, 2학년 3반, 150점]
[LOW]
[안지미, 여, 1학년 2반, 50점]
[안지미, 여, 2학년 2반, 50점]
3. 단순그룹화 + 통계(성적별 학생수)
[LOW] - 2명, [HIGH] - 8명, [MID] - 8명,
4. 다중그룹화(학년별, 반별)
[나자바, 남, 1학년 1반, 300점]
[김지미, 여, 1학년 1반, 250점]
[김자바, 남, 1학년 1반, 200점]
[이지미, 여, 1학년 2반, 150점]
[남자바, 남, 1학년 2반, 100점]
[안지미, 여, 1학년 2반, 50점]
[황지미, 여, 1학년 3반, 100점]
[강지미, 여, 1학년 3반, 150점]
[이자바, 남, 1학년 3반, 200점]
[나자바, 남, 2학년 1반, 300점]
[김지미, 여, 2학년 1반, 250점]
[김자바, 남, 2학년 1반, 200점]
[이지미, 여, 2학년 2반, 150점]
[남자바, 남, 2학년 2반, 100점]
[안지미, 여, 2학년 2반, 50점]
[황지미, 여, 2학년 3반, 100점]
[강지미, 여, 2학년 3반, 150점]
[이자바, 남, 2학년 3반, 200점]
5. 다중그룹화 + 통계(학년별, 반별1등)
[나자바, 남, 1학년 1반, 300점]
[이지미, 여, 1학년 2반, 150점]
[이자바, 남, 1학년 3반, 200점]
[나자바, 남, 2학년 1반, 300점]
[이지미, 여, 2학년 2반, 150점]
[이자바, 남, 2학년 3반, 200점]
6. 다중그룹화 + 통계(학년별, 반별 성적그룹)
[1-1][HIGH] // 상위권만 있음
[2-1][HIGH]
[1-2][LOW, MID] // 중위궈, 하위궈 다 있음
[2-2][LOW, MID]
[1-3][HIGH, MID]
[2-3][HIGH, MID]