오늘은 간단하게 기초적인 예제와 함께 Stream API를 정리해보도록 하겠습니다. 사실 개념 설명이라기보단 단순 정리에 가까우니 참고용으로 봐주시길 바랍니다.
스트림 생성
//배열 스트림 생성
String[] arr = {"a", "b", "c"};
Stream<String> arrStream = Stream.of(arr);
//리스트 스트림 생성
List<String> list = Arrays.asList(arr);
Stream<String> listStream = list.stream();
스트림을 만드는 방법은 다양하고 배열이나 컬렉션이냐에 따라 생성하는 방법도 다릅니다.
여기선 가장 많이 사용되는 방법으로 스트림을 생성해보았습니다.
스트림의 중간 연산
⦁ 자르기 (skip, limip)
String[] ex1 = {"a", "b", "c", "d", "e"};
Stream.of(ex1)
.skip(1).limit(3) //1개는 건너뛰고 3개 출력
.forEach(System.out::print); //b, c, d
⦁ 필터링 (distinct, filter)
String[] ex2 = {"a", "a", "moong", "뭉", "a"};
Stream.of(ex2)
.distinct() //중복제거
.filter(s -> s.matches("\\W")) //영어나 숫자는 제외
.forEach(System.out::print); //뭉
⦁ 정렬과 출력 (sorted, peek)
String[] ex3 = {"b", "a", "c", "e", "d"};
Stream.of(ex3)
.sorted() //Comparable로 정렬
.peek(System.out::print) //abcde
.sorted(Comparator.reverseOrder()) //역순정렬
.forEach(System.out::print); //edcba
⦁ 변환 (map, flatMap)
String[][] ex5 = {{"jax", "shen", "darius"}, {"lulu", "nami"}};
Stream.of(ex5)
.flatMap(Arrays::stream) //2뎁스를 1뎁스로
.map(String::toUpperCase) //대문자로 변경
.forEach(System.out::print); //JAX, SHEN, DARIUS, LULU, NAMI
스트림의 최종 연산
⦁ 갯수 (count)
String[] ex7 = {"a", "b", "c", "", " "};
Long count = Stream.of(ex7)
.filter(s -> !s.isBlank()) //빈 문자열 거르기
.count(); //갯수 구하기
System.out.println(count); //3
⦁ 조건 판별 (allMatch, anyMatch, noneMatch)
tring[] ex8 = {"가", "a", "A", "1"};
boolean isAllTrue = Stream.of(ex8)
.allMatch(s -> (s.length() == 1)); //모두 길이가 1
boolean isAnyTrue = Stream.of(ex8)
.anyMatch(s -> s.matches("\\d")); //적어도 하나는 숫자
boolean isNoneTrue = Stream.of(ex8)
.noneMatch(s -> s.matches("\\s")); //모두 길이가 1
System.out.println(isAllTrue); //true
System.out.println(isAnyTrue); //true
System.out.println(isNoneTrue); //true
⦁ 누산 (reduce)
Integer[] ex9 = {1, 2, 3};
int multiple = Stream.of(ex9)
.reduce(1, (r, a) -> r * a); //초기값 1에 * 1 * 2 * 3
System.out.println(multiple); //6
⦁ 자료형 담기 (collect, toArray)
String[] data = {"one", "two", "three"};
List<String> list = Stream.of(data)
.collect(Collectors.toList()); //리스트에 담기
String[] arr = list.stream()
.toArray(String[]::new); //배열에 담기
Map<String, Integer> map = Stream.of(data)
.collect(Collectors.toMap(s -> s, String::length)); //맵에 담기
String text = Stream.of(data)
.collect(Collectors.joining(", ")); //문자열에 담기
System.out.println(list); //[one, two, three]
System.out.println(Arrays.toString(arr)); //[one, two, three]
System.out.println(map); //{one=3, two=3, three=5}
System.out.println(text); //one=3, two=3, three=5
⦁ 그룹핑 - 2분할 [collect(Collectors.partitioningBy)]
String[] names = {"junho", "jinho", "hoya", "lee", "lulu", "darius", "jax"};
Map<Boolean, List<String>> nameByStart = Stream.of(names)
.collect(Collectors.partitioningBy(s -> s.startsWith("j"))); //2분할(j로 시작인지)
System.out.println(nameByStart.get(true)); //[junho, jinho, jax]
System.out.println(nameByStart.get(false)); //[hoya, lee, lulu, darius]
위 소스는 사람들의 이름이 저장된 배열을 j로 시작하는 이름과 j로 시작하지 않는 이름 둘로 나눈 것입니다.
즉 partitioningBy는 매개변수로 Predicate 타입의 함수를 매개변수로 받습니다.
Predicate 조건에 따라 스트림이 2분할로 나누어 지고 해당 데이터들은 리스트안에 저장됩니다. 그리고 해당 리스트는 Map안에 저장되어 Boolean 타입의 값들로 꺼낼 수 있습니다.
⦁ 그룹핑 - 재가공하여 자료형 담기 [collect(Collectors.mapping)]
String[] names = {"junho", "jinho", "hoya", "lee", "lulu", "darius", "jax"};
Map<Boolean, Set<String>> nameByStartMap = Stream.of(names)
.collect(Collectors.partitioningBy(
s -> s.startsWith("j"), //2분할(j로 시작하는지)
Collectors.mapping(String::toUpperCase, Collectors.toSet()) //재가공
));
System.out.println(nameByStartMap.get(true)); //[JUNHO, JAX, JINHO]
System.out.println(nameByStartMap.get(false)); //[LULU, DARIUS, LEE, HOYA]
이전에 봤던 2분할과 같은 예시입니다만 partitioningBy 안에 매개변수가 하나 더 추가되었습니다.
위와 같이 mapping을 사용하면 자료를 재가공하여 원하는 자료형에 담을 수 있습니다. 분할된 이름들을 대문자로 재가공한 후 Set에 담아 마찬가지로 Map 안에 저장됩니다.
⦁ 그룹핑 - n분할 [collect(Collectors.groupingBy)]
String[] names = {"junho", "jinho", "hoya", "lee", "lulu", "darius", "jax"};
Map<Integer, List<String>> nameByLength = Stream.of(names)
.collect(Collectors.groupingBy(String::length)); //n분할(길이에 따른 분할)
System.out.println("3글자 이름 리스트 : " + nameByLength.get(3)); //[lee, jax]
System.out.println("4글자 이름 리스트 : " + nameByLength.get(4)); //[hoya, lulu]
System.out.println("5글자 이름 리스트 : " + nameByLength.get(5)); //[junho, jinho]
groupingBy는 n분할하여 데이터를 저장합니다. partitioningBy와 차이점은 매개변수가 Predicate가 아닌 Function 타입의 함수를 받으며 위 소스는 길이에 따라 n분할하여 마찬가지로 맵과 리스트에 저장됩니다.
이때 키는 Boolean 타입이 아닌 매개변수 Function의 리턴타입이 키로 지정됩니다.
⦁ 그룹핑 - 통계값 얻기 (Collectors.counting, Collectors.maxBy, Collectors.averagingInt)
분할한 뒤에 요소들의 갯수를 얻거나 최대값/최소값 그리고 평균같은 통계값을 얻을 수도 있습니다. 밑의 예제들은 자연수와 자연수가 아닌 수로 분류하고 해당 값들을 각각 구하는 예제입니다.
//요소 갯수 구하기
Integer[] numbers = {3, -25, 54, 1, -6, 20, -90, -1, 1000, 50};
Map<Boolean, Long> byNaturalCount = Stream.of(numbers)
.collect(Collectors.partitioningBy(
n -> n > 0, //2분할(자연수인지 아닌지)
Collectors.counting() //분할된 원소 갯수
));
System.out.println("자연수 갯수 : " + byNaturalCount.get(true)); //6
System.out.println("자연수가 아닌 수의 갯수 : " + byNaturalCount.get(false)); //4
반환값이 Long이므로 Map을 선언하실 때 값의 타입을 Long으로 설정해주셔야 합니다.
//최대값 구하기
Integer[] numbers = {3, -25, 54, 1, -6, 20, -90, -1, 1000, 50};
Map<Boolean, Optional<Integer>> byNaturalMax = Stream.of(numbers)
.collect(Collectors.partitioningBy(
n -> n > 0, //2분할(자연수인지 아닌지)
Collectors.maxBy(Comparator.naturalOrder()) //최대값...naturalOrder 생략 불가능
));
System.out.println("50 미만인 숫자 중 최대값 : " + byNaturalMax.get(true).orElse(0)); //6
System.out.println("50 이상인 숫자 중 최소값 : " + byNaturalMax.get(false).orElse(0)); //4
최대값/최소값을 구할 때 MaxBy는 매개변수로 Comparator 함수를 받으며 이때 naturalOrder는 생략 불가능합니다.
또한 리턴타입이 Optional<T>인 것을 주의하셔야 합니다.
//평균 구하기
Integer[] numbers = {3, -25, 54, 1, -6, 20, -90, -1, 1000, 50};
Map<Boolean, Double> byNaturalAvg = Stream.of(numbers)
.collect(Collectors.partitioningBy(
n -> n > 0, //2분할(자연수인지 아닌지)
Collectors.averagingInt(n -> n) //평균값
));
System.out.println("50 미만인 숫자 평균 : " + byNaturalAvg.get(true)); //188.0
System.out.println("50 이상인 숫자 평균 : " + byNaturalAvg.get(false)); //-30.5
averagingInt는 매개변수로 ToIntFunction 함수를 받으며 리턴 타입이 Double인 것에 주의하셔야 합니다.
'Java > Grammer' 카테고리의 다른 글
Optional의 옳바른 사용법 (0) | 2022.06.12 |
---|---|
입출력 스트림 (2) - File I/O (0) | 2022.05.24 |
입출력 스트림 (1) - System.out.println와 I/O (0) | 2022.05.22 |
ShallowCopy와 DeepCopy 완전 정복 (0) | 2022.05.07 |
객체의 중복/정렬 그리고 Collection (0) | 2022.04.24 |