JAN's History
자바의 정석 CH14 람다식 본문
1. 람다식
- 함수(메서드)를 간단한 '식'으로 표현하는 방법
- 익명 함수이다.(이름이 없는 함수, anonymous function)
=> 반환타입과 이름을 지우기 때문!
- 함수와 메서드의 차이
> 근본적으로 동일하지만 함수는 일반적인 용어이고 메서드는 객체지향개념의 용어이다.
> 함수는 클래스에 독립적, 메서드는 클래스에 종속적이다.
> 자바에선 클래스 밖에 함수가 있을 수 없기 때문에 메서드밖에 없다.
람다식 작성법
1. 메서드의 이름과 반환타입을 제거하고 '->'를 블록 {}앞에 추가한다.
2. 반환값이 있는 경우, 식이나 값만 적고 return문 생략 가능 (끝에 ; 안붙임)
3. 매개변수의 타입이 추론 가능하면 생략가능(대부분의 경우 생략 가능)
작성 시, 주의사항
1. 매개변수가 하나인 경우 괄호()생략 가능 (타입이 없을 때만)
2. 블록 안 문장이 하나뿐 일때, 괄호{} 생략가능(끝에 ; 안붙임)
=> 빈괄호는 생략 불가
- 람다식은 익명함수가 아니라 익명 객체이다.
- 람다식(익명 객체)를 다루기 위한 참조변수가 필요하다. 참조변수의 타입은?
int max로 원래 객체를 생성해야하는데 람다식으로 작성하면 객체 이름이 없으니 호출할 수 없다.
즉, obj리모콘에 max 버튼이 없는 것.
1-2 함수형 인터페이스
- 단 하나의 추상메서드만 선언된 인터페이스
=>@functionalInterface를 붙이면 컴파일러가 함수형 인터페이스를 잘 만들었는지 확인해주기 때문에 붙여주는게 좋다
함수형 인터페이스 = 함수형 인터페이스 () {new 조상클래스 이름 or 인터페이스 {멤버}} 로
함수형 인터페이스를 생성할 수 있다.
=>obj.max에는 max가 없었기 때문에 에러가 났지만 f.max에는 화살표 위에 max가 있기 때문에 호출 가능
- 함수형 인터페이스 타입의 참조변수로 람다식을 참조할 수 있다.
(단, 함수형 인터페이스의 메서드와 람다식의 매개변수 개수와 반환타입이 일치해야한다.)
위 사진의 내용을 람다식으로 사용한 예시.
1. 함수형 인터페이스를 선언
2. 참조변수로 람다식을 생성
인터페이스에는 public abstract가 생략되어 있는 것.
그래서 함수형 인터페이스로 참조변수로 max를 생성할 때 앞에 public를 써줘야 한다.
오버라이딩의 조건에서 접근제어자는 좁게 못바꾸기 때문! (기본은 default니까)
+람다식의 이름이 없기 때문에 함수형인터페이스에서 추상메서드로 람다식 이름을 붙여주는 거라고 생각!
-익명 객체를 람다식으로 대체
sort (List list, Comparator c)를 가지고 있는데, Comparator에는 compare이라는 메서드만 있기 때문에 람다식 가능
comparator c = (s1, s2) -> s2.compareTo(s1)가 되는 것.
comparator이 람다식을 다루기 위한 함수형 인터페이스.
- 함수형 인터페이스 타입의 매개변수
람다식에 이름을 붙여주고 그것을 호출하는 것.
myMethod라는 메서드를 호출하는 람다식을 f라는 참조변수에 넣고 호출한다.
위에서 참조변수 f에 넣은 것을 직접 실행한 것.
=> 함수형 인터페이스를 통해서 메서드의 매개변수를 람다식으로도 받을 수 있다.
- 함수형 인터페이스 타입의 반환타입
MyFunction이 람다식을 반환하겠다는 의미.
=> 함수형 인터페이스를 통해서 메서드의 반환타입으로 함수형 인터페이스를 적어서 람다식을 반환할 수 있다.
run이 void 타입이고 매개변수가 없기 때문에 () -> 로 사용하고 return 값도 없는 것.
f1은 람다식으로 구현
f2는 익명클래스로 구현. 반드시 public 사용해야함
f3은 반환타입이 MyFuction인 메서드로 구현.
1-3 java.util.function패키지
- 자주 사용되는 다양한 함수형 인터페이스 제공
<String>이기 때문에 사용할 때 isEmptyStr = s 에서 String s를 생략할 수 있는 것.
Predicate는 boolean이니까 s의 길이가 0인지 반환하는 것!
Q. 아래 빈 칸에 알맞은 함수형 인터페이스(java.util.function패키지)를 적으시오.
1. 매개변수가 없고 반환값이 있기 때문에 공급자.
=> Supplier<Integer>
2. 매개변수가 있고 반환값이 없기 때문에 소비자
=> Consumer<Integer> i타입이니까 Integer
3. 조건식이니까
=> Perdicate<Integer>
4. 입력값과 출력값이 둘다 있다.
=> Function<Integer, Integer>
- 매개변수가 2개인 함수형 인터페이스
두개 짜리는 앞에 Bi가 붙는다.
Function은 T와 U를 받아서 R를 반환하는 것
+BiSupplier은 반환값이 2개여야하는데 그건 안되니까 없는 것.
=> 매개변수가 3개인 경우는 직접 만들어서 사용해야 한다.
-매개변수의 타입과 반환타입이 일치하는 함수형 인터페이스
int와 int를 더하면 int 값이 나오는 것처럼 두개 다 변환타입이 일치하기 때문에 하나만 써준다.
Function에서 f의 역할은 1의 자리의 숫자를 버리는 것.
1-4 Predicate의 결합
- and(), or(), negate()로 두 predicate를 하나로 결합(default메서드)
=> negate는 not 즉, !이다.
and와 or가 같이 있을 때 괄호 쳐주는 것이 좋다.
- 등가 비교를 위한 Predicate의 작성에는 isEqual()를 사용(static메서드)
=> str1.equals(str2)와 같은 문장.
컬렉션 프레임웍과 함수형 인터페이스
- 함수형 인터페이스를 사용하는 컬렉션 프레임웍의 메서드(와일드 카드 생략)
=> 원래는 Iterator로 hasNest를 통해 list를 읽어왔어야했는데 이제는 람다식으로 편리하게 출력 가능!
1-5 메서드 참조 = 클래스 이름::메서드 이름
- 하나의 메서드만 호출하는 람다식은 '메서드 참조'로 간단히 할 수 있다.
=> 이 세가지 방법 중 맨 아래는 거의 사용 X
- static 메서드 참조
=> 함수형 인터페이스에 정보가 다 있기 때문에 String을 생략가능 한 것
이해가 안갈땐 이렇게 그림을 그려보자.
- 생성자와 메서드 참조
Function<T(입력), R(출력)>에서 입력없이 출력만 있는 것. 그리고 그 출력이 MyClass라는 뜻.
=> 2개면 BiFunction<T, U, R>중 T, U가 입력이 되는 것
- 배열과 메서드 참조
x는 배열 길이이다. 입력된 배열 길이만큼 배열을 만든다는 뜻
배열타입[]::new;로 사용하면 됨 => 자주 쓰인다
=> Funcion은 apply, Supplier은 get으로 객체를 반환
=> 배열을 생성할 땐 배열의 길이를 넣어줘야하기 때문에 꼭 Function을 넣어줘야 한다.
2. 스트림
- 다양한 데이터 소스를 표준화된 방법으로 다루기 위한 것
Ex) 컬렉션 속 List, Set, Map과 배열 등..
스트림 메서드를 호출하면 데이터소스가 스트림으로 바뀌어 중간연산부터 최종연산까지 하나로 다룰 수 있다.
스트림의 생성 단계
1. 스트림 만들기
2. 중간연산 N번거치기
3. 최종연산을 통해 우리가 원하는 결과를 얻기
- 스트림이 제공하는 기능 - 중간연산과 최종연산
distinct() - 중복제거
limit(5) -5개 자르기
sorted() - 정렬하기
forEach(System.out::prinitln) - Stream의 요소를 하나씩 꺼내서 출력
2-2 스트림의 특징
- 스트림은 데이터 소스로부터 데이터를 읽기만할 뿐 변경하지 않는다. (원본 변경X)
- 스트림은 Iterator처럼 일회용이다. (필요하면 다시 스트림을 생성해야함)
+Iterator : 컬렉션에 있는 객체를 읽는 메서드
forEach는 최종연산이기 때문에 스트림의 최종 요소를 소모한다. 그렇기 때문에 사용할 최종 연산이 없어 닫힌다.
그럴땐 원본은 그대로 있으니 스트림을 다시 만들어 작업하면 된다.
- 최종 연산 전까지 중간연산이 수행되지 않는다. - 지연된 연산
=> 로또 번호 출력하는 코드
- 스트림은 작업을 내부 반복으로 처리한다.
=> for문을 메서드 안에 넣어 코드가 간결해지도록 할 수 있다.
- 스트림의 작업을 병렬로 처리 - 병렬스트림
스트림을 병렬스트림으로 전환하여 속도가 더 빨라진다. -> parallel을 호출하면 됨
- 기본형 스트림 - IntStream, LongStream, DoubleStream
> 오토박싱&언박싱의 비효율이 제거됨(Stream<Integer> 대신 IntStream 사용)
오토박싱&언박싱 : 기본형에서 객체로 바꾸고 또 객체를 다시 기본형으로 바꾸는 것
> 숫자와 관련된 유용한 메서드를 Stream<T>보다 더 많이 제공
2-3 스트림 만들기
- 컬렉션
1. 스트림 생성
2. 중간연산(0번~N번)
3. 최종연산(0~1번)
- Collection(List, Set) 인터페이스의 stream()으로 컬렉션을 스트림으로 변환
=> 최종연산 후에는 스트림을 다시 생성해야함.
stream을 다시 생성한 예제
- 배열
- 객체 배열로부터 스트림 생성하기
=> stream.of 메서드로도 가변인자로 생성 가능하고 Arrays로도 가능
- 기본형 배열로부터 스트림 생성하기
1. String 배열 생성 후 Arrays.stream()에 넣기
2. int 배열 생성 후 IntStream 배열에 넣기
=> 기본형 배열일 땐 IntStream 쓰는 것이 좋다. 기본형 int에는 숫자라서 count, sum, average 등 다양한 메서드가 있다.
3. Integer배열 생성 후 Arrays.stream에 넣기
=> 객체 스트림은 숫자인지 아닌지 모르기 때문에 count()밖에 없다.
-임의의 수 (난수)
- 난수를 요소로 갖는 스트림 생성하기
무한 스트림은 limit으로 중간과정에서 잘라주거나 애초에 크기를 자른 것을 반환해주어야한다.
*지정된 범위의 난수를 요소로 갖는 스트림을 생성하는 메서드(Random클래스)
- 특정 범위의 정수
- 특정 범위의 정수를 요소로 갖는 스트림 생성하기
- 람다식
- 람다식을 소스로 하는 스트림 생성하기
- iterate()는 이전 요소 seed로 해서 다음 요소를 계산한다.
=> 무한 스트림이 된다.
- generate()는 seed(초기값)를 사용하지 않는다.
=>limit을 하지 않으면 무한루프에 빠진다.
- 파일과 빈 스트림
- 파일을 소스로 하는 스트림 생성하기
line을 쓰면 파일의 내용을 라인단위, 한줄 씩 읽어서 String 요소로 만든다.
-비어있는 스트림 생성하기
2-4 스트림 연산
- 스트림이 제공하는 기능 - 중간연산과 최종연산
sorted()는 스트림 요소의 기본 정렬을 가지는 것이고
sorted(COmparator<T> comparator)은 comparator이 정렬기준
forEachOrdered는 스트림의 순서를 유지하며 반환한다.
findAny와 findFirst는 filter와 함께 쓰여 어떤 조건을 만족하는 요소를 반환한다.
- findAny는 병렬과 함께 스고 findFirst는 직렬과 함께 쓰인다.
reduce는 sum이던 count이던 하나씩 더하면서 요소를 줄여가며 계산하는 것.
- 중간연산
- 스트림 자르기 skip(), limit()
- 스트림의 요소 걸러내기 - filter(), distinct()
distinct - 중복제거
filter - 조건을 만족시키는 것만 통과시킨다.
- 스트림 정렬하기 - sorted()
정렬할 땐 1.정렬대상 2.정렬기준이 필요한데 comparator이 정렬 기준이 되는 것. (11장 복습)
- Comparator의 comparing()으로 정렬 기준을 제공
람다식의 원래는 (Students s) -> s.getBan()인 것 (람다식은 참조변수가 필요)
=> comparing의 반환타입이 comparator이기 때문에 sorted 안에 comparing을 쓰면 정렬기준이 되는 것
- 추가 정렬 기준을 제공할 때는 thenComparing()을 사용
=> 여러개일 땐 정렬기준을 thenComparing으로 붙이면 됨
스트림으로 Stream.of로 배열 객체 생성
=> 반으로 정렬하라고 했으니 반별로 정렬이 된 것. 또 기본정렬로 점수가 높은 사람이 정렬되기로 했으니
반별로 정렬 후 점수 높은 사람을 정렬한 것이다.
+역순으로 할 땐 reversed()만 찍어주면 된다.
+Student::getBan의 람다식은
(Student s) -> s.getBan이다.
Student가 Comparable를 구현한 것으로 보아 기본 정렬 기준이 CompareTo로 있다는 것을 확인할 수 있다.
s.totalScore - this.totalScore 즉, 총점이 높은 것부터 낮은 순 기본 정렬 기준인 것.
그리고 Name, Ban, TotalScore를 반환하는 getter들이 있다.
- 스트림의 요소 반환하기 - map()
Function은 어떤 타입 T를 넣으면 결과 R을 반환하는 것이다.
즉, file 이름을 getName으로 반환하면 그 결과를 String으로 이름을 출력하는 것이다.
이렇게 변환을 하는 것이 map의 역할
ex) 파일 스트림(Stream<file>)에서 파일 확장자(대문자)를 중복없이 뽑아내기
substring으로 파일명이 "Ex1.bak"면 bak으로 잘라 반환하도록 하는 것이다.
toUpperCase로 소문자로 대문자로 반환한다.
이후 중복제거 후 확장자를 출력했더니 모두 다 대문자가 나왔다.
fileArr이라는 배열을 만들고 스트림으로 생성한다.
map을 이용해 getName을 할 것이므로 String 타입으로 변경한다. 이후 출력하면 결과값은 왼쪽과 같음.
fileStream은 <File>이고 File::getName은 기존에 (String f) -> f.getName이었으므로 <File> -> <String>으로 바꾼다.
중간연산자를 거쳐 왼쪽과 같은 결과값이 나온다.
- 스트림의 요소를 소비하지 않고 엿보기 - peek()
forEach처럼 스트림의 요소를 반환하지만 중간연산이므로 스트림을 소비하지 않는다.
그래서 forEach는 반환타입이 void인 것
=> peek는 중간 작업 결과를 확인할 때 사용한다.
- 스트림의 스트림을 스트림으로 변환 - flatMap()
문자열 배열 스트림이 생성되어 있다.
이것을 map으로 인해 변환을 하면 스트림의 스트림으로 바뀐다.
map.(Arrays::stream); 을 람다식으로 반환하면 stream는 static 메서드이므로 (static은 참조변수 없이 접근 가능)
(arr) -> Arrays.stream이 된다. 즉, 어떤 배열을 주면 stream으로 바꾸는 것!
=> map을 쓰면 스트림의 스트림이 되어버린다. 그러나 내가 원하는 것은 각 문자열의 배열이 하나의 스트림으로 들어오는 것이기 때문에 그럴 땐 flatMap을 사
그러나 내가 원한 것은 따로따로 'aaa' 'bbb'가 있는 것이기 때문에 이럴 경우엔 flatMap를 사용해주면 된다.
왼쪽은 flatMap을 통해 stream을 합친 것이고, 오른쪽은 Map으로 Stream을 합친 것.
결과값을 보면 flatMap은 배열당 하나씩 스트림에 들어갔고 Map은 스트림의 스트림이 생성되어 확연한 차이가 느껴진다.
" +" 은 정규식으로 하나이상의 공백을 뜻한다. 그냥 암기!
Believe or not ...~ 을 하나씩 Stream에 담고 싶을 때도 flatMap을 사용한다.
즉, 문자열을 문자열 배열로 바꾸는 것이다.
+ 이걸 Map으로 바꾸면 스트림의 스트림이 되어버려 이상한 값이 출력된다.
소문자로 반환하고 중복제거하고 정렬하여 출력하면 하나씩 값이 출력!
=> 여러 문장이 있을 때 그 문장에 나온 단어를 중복제거하여 뽑아낼 때 flatMap 사용
2-4 Optinal<T>
- T 타입의 래퍼클래스 - Opional<T>
래퍼클래스 : Integer, Long타입의 값을 갖고 있는 클래스
value에 모든 종류의 객체를 저장할 수 있다. null을 포함하여!
사용하는 이유
1. null을 직접 다루는 것은 위험하다. NullpointException이 발생할 수 있기 때문.
2. null을 다루게 되면 null 체크를 해야하기 때문에 if문이 필수이다 즉, 코드가 지저분해진다.
=> 그래서 null을 Optional<T>에 넣어 간접적으로 null을 다루는 것이다.
만약 Object result = getResult();가 있으면
result.toString의 반환값으로 null 혹은 객체를 반환할텐데, null 예외가 발생할 수 있다.
또 null체크를 위해 if(result!=null) println(result.toString());을 꼭 넣어줘야 한다.
=> 이런 문제를 해결하기 위한 것이 Optional이다.
=> null을 직접 다루는 것이 아니라 Optional에 넣음으로써 nullpointException이 발생되지 않음. null 체크도 필요 없음.
String str = null; 보다 String str = "" 혹은 String str = new char[0]; (길이가 0인 char 배열)이 좋은 코드인 것처럼!
-Optional <T> 객체를 생성하는 다양한 방법
Optional의 static 메서드 of을 사용하면 된다.
원래는 직접 str을 다뤘지만 이제는 optVal을 통해 한단계 더 거치게 되는 것이다.
=> 한단계 더 거치면 optVal은 null이 될 수 없으니 안전!
Optional에 값으로 null을 넣는 것은 안된다. null을 넣기 위해선 ofNullable을 통해서 만들어줘야 한다.
=> 즉, Optional은 모든타입의 객체 T를 저장할 수 있다.
- null 대신 빈 Optional<T> 객체를 사용하자.
여기서 <String>은 앞에 Optional에서도 확인할 수 있으므로 생략가능하다.
Object [] objArr = null 보다 Object [] objArr = new Object[0];이 더 나은 것처럼
빈 Optional로 초기화하는 것이 더 좋다.
=> 이 모든 것은 NullpointException 오류를 피하기 위한 것
- Optional객체의 값 가져오기 - get(), orElse(), orElseGet(), orElseThrow()
str2, 3번을 가장 많이 사용한다.
orElseGet은 Supplier이기 때문에 람다식으로 바꾸면 () -> new String()이 된다.
- isPresent() - Optional 객체의 값이 null이면 false, 아니면 true를 반환한다.
Optional 객체를 갖고 있는 value가 Null이 아닐때만 작업을 수행한다!
ifPresent를 람다식으로 바꾸면 () -> (System.out.println(str))이 된다.
int [] arr = null; 로 하면 바람직 하지 못한 코드이다.
-> sysout.println(arr.length)에서 NullpointException이 발생하기 때문
=> int [] arr = new int[0];이 바람직한 코드
Optional<String> opt = null; 도 바람직하지 못한 코드이다.
-> sysout.println(opt.get())하면 오류가 발생하기 때문
=> Optional<String> opt = Optional.empty(); 이 바람직한 코드
opt의 반 값이 ""로 반환되기 위해선
1. String str = ""로 먼저 객체를 생성해주고
2. str = optional.get();을 드래그 후 Surround With > Try/catch Block를 클릭하면 된다.
3. 그러면 자동으로 try-catch가 나오고 예외 발생 부분에 ""을 적어주면 된다.
그러면 str를 출력했을 떄 빈 문자열이 찍힌다.
근데 이 과정이 너무 번거로우니 orElse를 사용하면 Optional에 저장된 값이 null일 때 ""를 반환하는 것이다.
orElseGet을 사용하면 람다식을 안에 넣을 수 있다.
String::new는 ()-> new String()을 줄인 것
- Optionallnt, OptionalLong, OptionalDouble
> 기본형을 감싸는 래퍼클래스 (성능 때문에 사용)
Optional 클래스에는 <T> , final T value;로 되어 있는데 여기선 기본형으로 되어있음! 성능이 향상된다.
> Optionallnt의 값 가져오기 - int getAslnt()
> 빈 Optional 객체와의 비교
0을 저장한 것과 empty을 저장하면 기본값이 0이기 때문에 0이 저장되는데 이 둘을 어떻게 구별할까?
똑같이 값이 0이더라도 isPresent를 사용하면 확인할 수 있다.
opt 는 value 가 0이고 isPresent가 true이고
opt2는 value가 0이지만 isPresent가 false다.
=> 0을 저장한건지 아무것도 저장하지않은 것인지 구분이 가능
abcde를 optStr에 Optional으로 저장. (null를 저장하기 위해 사용되는 것)
<Integer>로 s의 길이를 optInt로 저장.
그 둘을 get으로 반환하면 결과값이 우측과 괕다.
123이라는 Optional를 filter로 길이가 0보다 크도록 거르고, Int로 변환한 값을 result1에 저장하면
그대로 123이 출력된다.
result 2는 빈 문자열을 저장하고 필터로 확인해보면 길이가 0보다 크지 않기 때문에 필터 조건을 통과하지 못해서
orElse로 value 값이 없으면 -1를 반환한 것
456이라는 Optional 값을 저장하고 Int로 반환 후 값이 있으면 ifPresent 출력하는 거니까 정상적으로 출력된 것
+ifPresent는 null이 아닐땐 실행 X 즉 ""면 에러 발생
optInt1에 0을 저장하고 opt2Int2에 빈 객체를 저장한다.
isPresent를 하면 optInt는 값이 있으므로 True, optInt2는 값이 없으므로 false가 나온다.
=> equals로 확인해도 다르다고 나옴
2-5 스트림의 최종연산
Stream의 연산은 중간연산과 최종연산이 있다.
중간연산은 N번 연산 후 stream을 반환해 n번 가능! 최종연산은 1번만 가능하며 최종연산 후에는 스트림이 닫힌다.
중간연산 ex) limit, skip, filter, distinct, map, peek, flatmap..
최종연산 ex) allMatch, nonMatch, findFirst, findAny, reduce,collect..
- 스트림의 모든 요소에 지정된 작업을 수행한다. forEach(), forEachOrdered()
sequential() - 직렬 스트림이 기본이기 때문에 생략해도 된다.
=> 하나의 쓰레드가 직렬로 처리하니까 순서대로 결과값이 찍힌다.
parallel() - 병렬 스트림 : 여러 쓰레드를 나눠서 작업 (데이터가 많을 때 유리)
=> 여러개의 쓰레드가 작업하기 때문에 순서 보장이 안된다.
* 이때 forEachOrdered를 사용하면 병렬이더라도 순서가 보장된다.
+ range(1, 10)에서 10은 미포함이기 때문에 1~9까지의 범위가 저장된다.
- 조건 검사 - allMatch(), anyMatch(), noneMatch()
allMatch - 하나라도 만족시키면 참
anyMatch - 한 요소라도 만족시키면 참
noneMatch - 모든 요소가 조건을 만족시키지 않으면 참
- 조건에 일치하는 요소 찾기 - findFirst(), findAny()
Optional <T>인 이유 : 결과값이 null 일 수 있어서 옵셔널로 반환하는 것
findFirst - 순서대로 찾다가 발견한 첫번째 요소를 반환
findAny - 병렬 스트림에서 마구잡이로 찾다가 발견한 아무거나 하나를 반환
=>filter랑 같이 쓴다.
- 스트림의 최종 연산 reduce
- 스트림의 요소를 하나씩 줄여가며 누적연산 수행 - reduce
reduce는 오버로딩이 되어있는데 그 중에서 T만 보자!
identity(초기값)와 accumulator(어떤 작업할 건지)가 핵심이다.
0이 초기값이고 (a,b) -> a+1이 수행할 작업
count는 0부터 1 2 3 .. 까지 요소 개수를 반환한다
sum은 초기값을 0으로 놓고 for문으로 돌면서 sum = sum+i하면 된다.
=> reduce는 for문처럼 이렇게 돌아간다.
count, max, collect .. 들도 까보면 다 reduce처럼 작동한다.
배열로 String 객체를 생성하여 스트림으로 병렬처리해서 print를 찍으면 원래는 마구잡이로 찍힌다.
그러나 forEachOrdered를 이용했기 때문에 순서대로 찍힌 것
noneMatch 는 모든 요소가 조건식을 만족하지 않으면 true이기 때문에 길이가 모두 3 이상이므로 true.
pareallel로 filter를 이용해 첫 글자가 s로 시작하는 것을 findAny로 찍었더니 stream이 아니라 sum이 나온 것
mapToOnt는 Stream<String>을 intStream으로 변환하는 것이다.
map과 기본적으로 같지만 성능이 기본형으로 반환하는 mapToInt가 더 좋기 때문에 mapToInt를 사용한다.
결국 String 을 int로 바꿔서 {11, 4, 6, 6, 14, 9, 5, 3}이 된다.
이를 count 하면 8이 되고 sum을 하면 58이 나온다.
stream을 많이 만든 이유는 최종연산은 1번만 사용가능하기 때문!
max와 int는 null값이 있을 수 있기 때문에 초기값을 없애고 OptionalInt을 사용
만약 Interger::max의 연산 객체가 empty()면 max.getAsInt에서 에러가 나기 때문에
orElse(0) 이나 orElseGet(() -> 0) <- supplier 람다식 넣기를 써줘야 한다.
- collect()와 Collectors
> collect()는 Collector를 매개변수로 하는 스트림의 최종 연산
reduce와 collect의 차이 : reduce는 전체를 리듀싱하고 collect는 그룹별로 나눠서 리듀싱한다.
>Collector는 수집(collect)에 필요한 메서드를 정의해 놓은 인터페이스
T요소를 A에 계속 저장한 다음에 R로 변환하는 것
supplier(누적할 곳 )와 accumlator(누적할 방법)이 핵심이다.
reduce에서도 identify(초기값)과 accumaltor(누적 수행 작업)이 핵심인 것과 같다.
>Collectors 클래스는 다양한 기능의 컬렉터(Collector를 구현한 인터페이스)를 제공한다.
=> 원래는 매개변수 5개를 써야하는데 Collector 인터페이스로 지정해놓았고 그것을 구현한 클래스가 Collectors이다.
collect()는 최종연산!
- 스트림을 컬렉션으로 변환 - toList(), toSet(), toMap(), toCollection()
stream<Student>의 getName을 얻어서 List<String>에 저장하고 싶을 땐 최종연산 collect(Collectors.toList())를 사용
이를 배열로 저장하고 싶으면 toCollection(ArrayList::new)를 사용하면 된다.
Map에 담고 싶을 땐 toMap을 사용하여 Key, Value를 저장해줘야한다.
p라는 객체의 getRegld() = 주민번호를 Key로 받고 p->p는 항등함수로 그대로 value에 저장한다.
=> 스트림을 컬렉트로 바꿀 땐 collect 메서드를 사용하면 된다.
- 스트림을 배열로 변환 - toArray()
toArray()의 방법은 매개변수 있는 것과 없는 것 두가지가 있다.
매개변수 없는 것은 Object배열로 반환해야한다.
toArray가 반환하는 타입이 Object이기 때문에 Student로 반환하면 에러가 난다.
그래서 Student 타입으로 반환을 하기 위해선 매개변수에 내가 원하는 타입을 적어줘서 일치시켜줘야 한다.
첫 번째가 가장 많이 쓰인다.
+ Studecnt[]::new => (i) -> new Student(i)
- 스트림의 통계 - counting(), summinglnt()
> 스트림의 통계정보 제공- counting(), summinglnt(), maxBy(), minBy()
count()로 객체의 개수를 반환할 수 있는 것처럼 collect(counting())도 가능하다.
collect는 그룹별로 나눠서 카운팅이 가능하다.
totalScore의 총점을 구하는 메서드도 위 아래가 모두 같지만 sum은 전체를 더하고, collect는 그룹별로 묶을 수 있다.
최대값은 총점이 가장 높은 학생을 구하고, maxBy는 비교기준인 총점을 준다.
- 스트림을 리듀싱 - reducing()
> 스트림을 리듀싱 - reducing()
기본 reduce()는 전체 리듀싱이 가능하고 collect에 있는 reducing은 그룹별 리듀싱이 가능하다.
+ reduce() : sum, count ..
reducing도 (초기값, 누적작업)은 같다. 둘다 하는 일은 같다.
> 문자열 스트림의 요소를 모두 연결 - joining()
Student의 이름을 모두 뽑아서 하나의 문자열로 결합하고 싶을 땐 Stream<Student> -> Stream<String>으로 바꾼다.
그럴때 쓰는 것이 getName이다. 그리고 그것을 .collect(joining())을 쓰면 뒤에 하나씩 붙는다.
- 스트림의 그룹화와 분할(Collectors.)
> partitioningBy()는 스트림을 2분할한다.
key가 boolean이고 value가 List<Student>이다.
partitioningBy로 분할 기준을 주면 그 결과가 Map으로 나오는데 Boolean으로 나온다.
나누고 counting도 할 수 있다. 즉, 분할한 다음 통계정보도 얻는 것.
이후에 maxBy로 최대값을 성적별로 얻을 수 있다. 그 결과를 Optional로 받는다.
get하면 남학생 중 1등, 여학생 중 1등이 나오게 된다.
학생을 남녀로 나누고 또 남자 중에서도 합격 불합격, 여자 중에서도 합격, 불합격으로 나눌 수 있다.
그럴 땐 get(true).get(true)를 해야한다.
1. 단순 분할
isMale도 boolean값으로 isMale ? 남 : 여 로 print값이 찍히기로 되어있다.
그래서 true 값엔 maleStudent에 저장되고 false는 femaleStudent에 저장되어 있다.
2. 단순분할 + 통계(성별 학생 수)
collect 이용 후 cointing()으로 그룹별 카운팅을 한 후 get(true)값을 반환하면 count된 값이 찍힌다.
3. 단순분할 + 통계(성별 1등)
collect 이용 후 성별로 나눈 다음에 maxBy로 최대값 기준을 부여하면 Map에 들어가게 된다.
그럼 여학생 1등과 남학생 1등이 찍힌다.
+ 참고로 Optional::get을 주면 Student 값을 꺼내서 반환하기 때문에 프린트 값에 Optional이 찍히지 않는다.
4. 다중분할(성별 불합격자, 100점 이하)
그래서 100점 이하인 사람을 불합격자로 보고 뽑은 것이다.
> groupingBy()는 스트림을 n분할 한다.
학생을 반별로 그룹화 한다. 그리고 그 반에 따라서 List에 담아 반환한다.
다중 그룹화도 할 수 있다. 학년별로 그룹을 묶고, 반별로 그룹을 묶을 수 있음.
복잡하게 더 나누기 위해선 Map을 사용해야한다. 특별한 기준을 나눠 사용하고 싶을 땐 mapping으로 나눌 수 있다.
1. 단순 그룹화(반별로 그룹화)
Student3::getBan으로 반별로 그룹화를 진행한다.
2. 성적별로 단순 그룹화
key를 Level이 되어 HIGH, MID, LOW인 학생들을 구분하여 그룹화한다.
HIGH부터 나오게 하려고 TreeSet을 이용
3. 단순그룹화 + 통계 (성적별 학생수)
성적별로 나눈 학생들을 카운팅하여 출력.
4. 다중그룹화 (학년별, 반별)
학년별로 나눈 후 반별로 또 나눠 다중 그룹화 진행
5. 다중그룹화 + 통계(학년별, 반별 1등)
결과가 Optional이기 떄문에 Optional::get을 줘서 출력한 것이다.
그룹핑을 한 후에 학생성적 제일 높은 것을 get으로 꺼낸 것.
6. 다중그룹화 + 통계(학년별, 반별성적그룹)
여기선 그룹핑을 두번 한 것이 아니라 학생에서 학년과 반별을 나누어 통째로 그룹핑을 진행한 것이다.
그 다음 성적별로 나눈 것이다.
=> 학년과 반 기준으로 문자열을 나눈 다음에 그룹핑을 진행할 수 도 있다.
스트림의 변환
'자바' 카테고리의 다른 글
자바의 정석 CH 13 쓰레드 (0) | 2023.04.23 |
---|---|
자바의정석 CH 12 - 2 열거 (0) | 2023.04.23 |
자바의정석 CH 12 - 1 지네릭스, 열거형, 애너테이션 (0) | 2023.04.20 |
자바의정석 CH 11 컬렉션 프레임웍 - 2 (0) | 2023.04.19 |
자바의정석 CH 11 컬렉션 프레임웍 - 1 (0) | 2023.04.18 |