Stream 연산 예시
IntStream
.range(1, 100)//1~99까지 생성
.filter(i -> i % 2 == 0)//짝수인 값만 통과시키는 필터.Predicate 함수를 받음.
// 💡 아래의 중간과정을 하나하나 주석해제해 볼 것
.skip(10)//앞에서 부터 숫자 10개 제거
.limit(10)//10개만 스트림으로 반환
.map(i -> i * 10)//인자별로 각각 10을 곱한 값을 반환
.forEach(System.out::println);//각 요소 별로 주어진 Consumer(여기서는 출력) 실행
- 그림처럼 필터는 특정조건이 true인 경우에만 값을 내보낸다.
- map은 모든 인자에 대해서 함수를 적용시키고 내보낸다.
System.out.println("\n- - - - -\n");
String str1 = "Hello World! Welcome to the world of Java~";
str1.chars().forEach(System.out::println);
- chars는 문자열을 대응하는 숫자로 바꾸는 메서드. 그후 각 요소들 출력하는 스트림.
System.out.println("\n- - - - -\n");
char str1MaxChar = (char) str1.chars()
.max() // OptionalInt 반환 - 이후 배울 것
//.min() // 변경해 볼 것
.getAsInt();
- max(), min()은 뒤에 배울 OptionalInt를 반환하고, 최종값 반환이라 값수정을 위한 메서드 체이닝은 불가능하다.
// 사용되는 모든 알파벳 문자들을 정렬하여 프린트
str1.chars()
.sorted()// !HJWWaacddeeeefhlllllmoooooorrttvw~ 빈칸이 제일 적은 숫자와 대응되므로 맨앞에 정렬된다.
.distinct()// !HJWacdefhlmortvw~ 중복제거
//.filter(i -> (i >= 'A' && i <= 'Z') || (i >= 'a' && i <= 'z'))
.filter(Character::isLetter)// isLetter는 정수에 대응되는 문자열 존재여부를 boolean으로 리턴한다.
.forEach(i -> System.out.print((char) i));
- 문자열만 추출해서 출력하는 코드
- 정렬 -> 중복 제거 -> filter로 문자열과 대응되는 숫자만 통과 -> foreach로 각각 요소 char형으로 쭉 이어서 출력(print)
// 대소문자 구분 없이 중복 제거한 뒤 정렬하고 쉼표로 구분
String fromStr1 = str1.chars()
.boxed()
// 💡 boxed를 사용하여 Stream<Integer>으로 변환
// 요소를 다른 타입으로 바꾸려면 Stream<T>을 사용해야 함
.map(i -> String.valueOf((char) i.intValue()))
//.map(String::toUpperCase)
//.filter(s -> Character.isLetter(s.charAt(0)))
//.sorted()
//.distinct()
.collect(Collectors.joining(", "));
- chars()는 IntStream 자료형을 가진다. map은 스트림의 각 요소를 변환하는데 사용된다. 그래서 요소를 문자열 타입 스트림으로 바꾸려고 하는데, IntStream은 다른 자료형으로 변경이 안된다. 그래서 변경이 가능한 Stream<Integer>로 변환하기 위해 boxed()를 사용한다.
peek
// 💡 peek으로 중간 과정 체크하기
// - 스트림이나 요소를 변경하지 않고 특정 작업 수행 - 디버깅에 유용
String oddSquaresR = IntStream.range(0, 10).boxed()
.peek(i -> System.out.println("초기값: " + i))
.filter(i -> i % 2 == 1)
.peek(i -> System.out.println("홀수만: " + i))
.map(i -> i * i)
.peek(i -> System.out.println("제곱: " + i))
.sorted((i1, i2) -> i1 < i2 ? 1 : -1)
.peek(i -> System.out.println("역순: " + i))
.map(String::valueOf)
.collect(Collectors.joining(", "));
- 물레방아처럼 코드에는 영향을 끼치지 않으면서 특정작업이 가능하다(Consumer사용). 중간중간 값 확인하기 좋다
- 디버깅에 탁월해보인다.
allMatch/AnyMatch
Integer[] ints = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// 💡 allMatch/anyMatch : (모든 요소가/어느 한 요소라도)
// - 주어진 Predicate에 부합하는가 여부 반환
boolean intsMatch = Arrays.stream(ints)
.allMatch(i -> i > 0); //모두 0보다 크니 true
//.allMatch(i -> i % 2 == 0); // 전부짝수는 아니니 false
//.anyMatch(i -> i < 0); // 0보다 작은게 1개도 없으니 false
//.anyMatch(i -> i % 2 == 0); // 짝수가 1개이상 있으니 true
- 모든 요소, 어느 한 요소라도 조건으로 최종결과를 boolean값으로 반환하는 메서드.
takeWhile/dropWhile
System.out.println("\n- - - - -\n");
// 💡 takeWhile/dropWhile 주어진 Predicate을 충족시킬 때까지 취함/건너뜀
// 💡 count : 중간과정을 거친 요소들의 개수를 반환
long afterWhileOp = Arrays.stream(ints)
.takeWhile(i -> i < 4) // 1,2,3
//.dropWhile(i -> i < 4) // 4..9
.peek(System.out::println)
.count();
- takeWhile : 조건을 충족하는 동안만 아래로 통과시킨다.
- dropWhile : 조건을 충족하는 동안만 건너뛴다.
sum
// 💡 sum : IntStream, LongStream, DoubleStream 요소의 총합 반환
int intsSum = IntStream.range(0, 100 + 1)
//.filter(i -> i % 2 == 1)
//.filter(i -> i % 2 == 0)
//.filter(i -> i % 3 == 0)
.sum();
- +연산을 직관적으로 사용가능하다.
reduce
System.out.println("\n- - - - -\n");
// 💡 reduce : 주어진 BiFunction으로 값을 접어나감
// seed 값이 없을 때 : Optional 반환 (빈 스트림일 수 있으므로)
int intReduce = IntStream.range(1, 10)
//.filter(i -> i % 2 == 1)
.reduce((prev, curr) -> {
System.out.printf("prev: %d, cur: %d%n", prev, curr);
return prev * curr;
})
.getAsInt(); // 필요함
- reduce는 파라미터 2개를 받아서 람다식을 실행한 후, 결과값을 첫번째 인자로 둔다. 계속해서 누적하여 처리한다.
1 -> 2
↓
2 -> 3
↓
6 -> 4
↓
24 -> 5
↓
120 -> 6
↓
720 -> 7
↓
5040 -> 8
↓
40320 -> 9
↓
362880
- 초기값이 없으면 스트림값 첫번째, 2번째 값으로 연산을 진행한다.
- 초기값이 있으면 초기값과 스트림내 첫번째 값으로 연산을 진행한다.
- 초기값이 없으면, 빈값으로 나올 수 있기 때문에 오류를 방지하기 위해 OptionalInt로 반환한다. 여기서 int값을 뽑아내기 위해 getAsInt()를 사용한다.
- 초기값이 있으면 빈값으로 나오지 않기 때문에, OptionalInt를 사용하지 않는다. 그래서 getAsInt()도 사용하지 않는다.
좀 더 복잡한 예시
// 내부 정적 클래스
public static class StringStat {
int length = 0;
int upperCases = 0;
int lowerCases = 0;
int nonLetters = 0;
}
StringStat stringStat = "Hello World! Welcome to the world of Java~"
.chars()
.boxed()
.reduce(
new StringStat(),
(ss, i) -> {
ss.length++;
if (i >= 'A' && i <= 'Z') { ss.upperCases++; }
else if (i >= 'a' && i <= 'z') { ss.lowerCases++; }
else { ss.nonLetters++; }
return ss;
},
// 아래의 combiner 인자는 병령 연산에서만 작용 (여기서는 무의미)
// 요소와 다른 타입을 반환하는 오버로드의 필수인자이므로 임의로 넣음
(ss1, ss2) -> ss1
);
- 위 예제는 초기값을 StringStat이라는 클래스의 인스턴스로 사용한다.
- 람다식연산은 초기값 인스턴스와 stringStat 문자열의 각각 문자 1개당 조건문을 진행한다. 그후 연산이후 업데이트된 초기값 인스턴스와 동일한 연산을 반복한다.
map()으로 다른 자료형으로 바꾸는 예시
String[] names = {
"강백호", "서태웅", "채치수", "송태섭", "정대만",
"윤대협", "변덕규", "황태산", "안영수", "허태환",
"이정환", "전호장", "신준섭", "고민구 ", "홍익현",
"정우성", "신현철", "이명헌", "최동오", "정성구"
};
Stream<String> nameStream = Arrays.stream(names);
Random random = new Random();
random.setSeed(4); // 균일한 결과를 위해 지정된 시드값
List<Person> people = nameStream
.map(name -> new Person(
name,
random.nextInt(18, 35),
random.nextDouble(160, 190),
random.nextBoolean()
))//이름 가지고 새 객체들을 만든다.
.sorted()
//.sorted((p1, p2) -> p1.getHeight() > p2.getHeight() ? 1 : -1)
//.sorted((p1, p2) -> Boolean.compare(p1.isMarried(), p2.isMarried()))
.toList();
- 배열이었던 인스턴스를 map(..)을 통해서 Person객체들의 Stream으로 바꿔보는 예제. Stream<Person> 자료형이 될 것이고, 그걸 toList()를 써서 리스트로 바꾼다.
내가 원하는 자료형으로 바꾸는 방법 ( collect 활용)
- collect안에서 Collectors.toList().. 등을 쓰면 원하는 기본 컬렉션(리스트,맵,셋)으로 최종 자료형을 변경할 수 있다.
- Collectors.toCollection(..)을 쓰면 내가 원하는 컬렉션(ex : LinkedList)같은 걸 사용할 수 있다.
var peopleLastNameSet = people.stream()
.map(p -> p.getName().charAt(0))
// 💡 아래 중 원하는 컬렉션으로 택일
.collect(Collectors.toList());
//.collect(Collectors.toSet());
//.collect(Collectors.toCollection(ArrayList::new));
//.collect(Collectors.toCollection(LinkedList::new));
//.collect(Collectors.toCollection(TreeSet::new));
특정 기준으로 key value 맵 만드는 방법 (Collectors.groupingBy)
- Collectors.GroupingBy를 사용하면 특정 조건을 키값, 스트림에서 그 조건을 만족 하는 요소를 value로 한 맵을 리턴한다.
- 어떤 조건에 대해 분류할때 유용해보인다.
Map<Boolean, List<Person>> peopleHMapByMarried = people.stream()
.collect(Collectors.groupingBy(Person::isMarried));
List<Person> marrieds = peopleHMapByMarried.get(true);
System.out.println(marrieds);
key : true
value : Person객체.....
key : false
value : Person 객체...
이런식으로 Map이 만들어질 수 있는 것이다.
수의 통계를 인스턴스로 가지는 클래스를 만드는 방법(Collectors.summarizingInt,Double)
- Collectors.sumarizing을 사용하면 최대값,최소값등 통계값들을 바로 얻어낼수 있다.
- 값들 중에서 통계를 낼때, 최대,최소,평균등의 값이 필요할 때 아주 유용하겠다.
// 💡 수의 통계를 인스턴스 형태로 갖는 클래스
IntSummaryStatistics ageStats = people.stream()
.map(Person::getAge)
.collect(Collectors.summarizingInt(Integer::intValue));
DoubleSummaryStatistics heightStats = people.stream()
.map(Person::getHeight)
.collect(Collectors.summarizingDouble(Double::doubleValue));
[Java] Stream API 연습문제 풀이 (5/5)
이번에는 Stream API를 연습해볼만한 문제를 제공해보고자 합니다. 자동화된 테스트를 통해 정답을 확인하도록 제공하고 있으니 직접 문제를 풀어서 정답을 확인해보실 분들은 아래 내용을 참고
mangkyu.tistory.com
출처
- 사이트, 검색명 (날짜)
'얄코의 제대로 파는 자바 > 섹션9 함수형 프로그래밍' 카테고리의 다른 글
스트림 문제 풀이 (0) | 2024.08.17 |
---|---|
스트림 (0) | 2024.08.12 |
메서드 참조 (0) | 2024.08.11 |
java.util.function 패키지 (0) | 2024.08.04 |
람다식과 함수형 인터페이스 (0) | 2024.07.28 |