람다와 스트림 3
Java의 정석 의 책을 읽고 정리한 내용입니다.
📖 D. Optional<T>
와 OptionalInt
Optional<T>
은 지네릭 클래스로 T타입의 객체를 감싸는 래퍼 클래스다.
그래서Optional타입의 객체
에는 모든 타입의 참조변수를 담을 수 있다.
public final class Ooptional<T> {
private final T value; // T타입의 참조변수
...
}
최종 연산의 결과를 그냥 반환하는 게 아니라 Optional객체
에 담아서 반환하는 것이다.
이처럼 객체에 담아서 반환을 하면, 반환된 결과가 null
인지 매번 if문
으로 체크하는 대신 Optional에 정의된 메서드
를 통해서 간단히 처리할 수 있다.
✔️ Optional객체 생성하기
Optional객체
를 생성할 때는 of()
또는 ofNullable()
을 사용한다.
String str = "abc";
Optional<String> optVal = Optional.of(str);
Optional<String> optVal = Optional.of("abc");
Optional<String> optVal = Optional.of(new String("abc"));
만일 참조변수의 값이 null
일 가능성이 있으면, of()
대신 ofNullable()
을 사용해야한다.
of()
는 매개변수의 값이 null
이면 NullPointerException
일 발생하기 때문이다.
Optional<String> optVal = Optional.of(null); // NullPointerExpception 발생
Optional<String> optVal = Optional.ofNullable(null); // OK
Optional<T>
타입의 참조변수를 기본값으로 초기화할 때는 empty()
를 사용한다.
null
로 초기화하는 것이 가능하지만, empty()
로 초기화 하는 것이 바람직하다.
Optional<String> optVal = null; // null로 초기화
Optional<String> optVal = Optional.<String>empty(); // 빈 객체로 초기화
✔️ Optional 객체의 값 가져오기
Optional객체
에 저장된 값을 가져올 때는get()
을 사용한다. 값이null
일 때는NoSuchElementException
이 발생하며, 이를 대비해서orElse()
로 대체할 값을 지정할 수 있다.
Optional<String> optVal = Optional.of("abc");
String str1 = optVal.get(); // optVal에 저장된 값을 반환. null이면 예외발생
String str2 = optVal.orElse(""); // optVal에 저장된 값이 null일 때는, ""을 반환
orElse()
의 변형으로는 null
을 대체할 값을 반환하는 람다식을 지정할 수 있는 orElseGet()
과 null
일 때 지정된 예외를 발생시키는 orElseThrow()
가 있다.
T orElseGet(Supplier<? extends T> other)
T orElseThrow(Supplier<? extends X> exceptionSupplier)
사용하는 방법은 아래와 같다.
String str3 = optVal2.orElseGet(String::new); // () -> new String()과 동일
String str4 = optVal2.orElseThrow(NullPointException::new); // null이면 예외 발생
Stream
처럼 Optional객체
에도 filter()
, map()
, flatMap()
을 사용할 수 있다. map()
의 연산결과가 Optional<Optional<T>>
일 때, flatMap()
을 사용하면 Optional<T>
를 결과로 얻는다. 만일, Optional객체
의 값이 null
이면, 이 메서드들은 아무 일도 하지 않는다.
int result = Optional.of("123")
.filter(x->x.length() > 0)
.map(Integer::parseInt).orElse(-1); // result=123
result = Optional.of("")
.filter(x->x.length() > 0)
.map(Integer::parseInt).orElse(-1); // result = -1
우리가 이미 알고 있는 것처럼 parseInt()
는 예외가 발생하기 쉬운 메서드이다. 만일 예외처리된 메서드를 만든다면 다음과 같을 것이다.
static int optStrToInt(Optional<String> optStr, int defaultValue){
try{
return optStr.map(Integer::parseInt).get();
}catch(Exception e){
return defaultValue;
}
}
isPresent()
는 Optional객체의 값
이 Null
이면 false
를, 아니면 true
를 반환한다.
ifPresent(Consumer<T> block)
은 값이 있으면 주어진 람다식을 실행하고, 없으면 아무일도 하지 않는다.
if(str!=null){
System.out.println(str);
}
이 코드를 ifPresent()
를 이용해서 바꾸면 더 간단히 할 수 있다. 아래의 문장은 참조변수 str
이 null
이 아닐 때만 값을 출력하고, null
이면 아무 일도 일어나지 않는다.
Optional.ofNullable(str).ifPresent(System.out::println);
ifPresent()
는 Optional<T>
를 반환하는 findAny()
나 findFirst()
와 같은 최종 연산과 잘 어울린다.
Stream클래스
에 정의된 메서드 중에서 Optional<T>
를 반환하는 것들은 다음과 같다.
Optioanl<T> findAny()
Optional<T> findFirst()
Optional<T> max(Comparator<? super T> comparator)
Optional<T> min(Comparator<? super T> comparator)
Optional<T> reduce(BinaryOperator<T> accumulator)
이처럼 Optional<T>
를 결과로 반환하는 최종 연산 메서드들은 몇 개 없다. 심지어 max()
와 min()
같은 메서드들은 reduce()
를 이용해서 작성된 것이다.
✔️ OptionalInt, OptionalLong, OptionalDouble
IntStream
과 같은 기본 스트림에는 Optional
도 기본형을 값으로 하는 OptionalInt
, OptionalLong
, OptionalDouble
을 반환한다. 아래의 목록은 IntStream
에 정의된 메서드들이다.
OptionalInt findAny()
OptioanlInt findFirst()
OptionalInt reduce(IntBinaryOperator op)
OptionalInt max()
OptionalInt min()
OptionalInt average()
반환 타입이 Optional<T>
가 아니라는 것을 제외하고는 Stream
에 정의된 것과 비슷하다.
그리고 기본형 Optional
에 저장된 값을 꺼낼 때 사용하는 메서드의 이름이 조금씩 다르다는 것에 주의하자!
Optional 클래스 | 값을 반환하는 클래스 |
---|---|
Optional<T> | T get() |
OptionalInt | int getAsInt() |
OptionalLong | long getAsLong() |
OptionalDouble | double getAsDouble() |
OptionalInt
는 다음과 같이 정의되어 있다.
public final class OptionalInt{
private final boolean isPresent; // 값이 저장되어 있으면 true
private final int value; // int 타입의 변수
기본형 int
의 기본값은 0이므로 아무런 값을 갖지 않는 OptionalInt
에 저장되는 값은 0일 것이다.
그러면 아래의 두 OptionalInt객체
는 같은 것일까?
OptionalInt opt = OptionalInt.of(0); // OptionalInt에 0을 저장
OptionalInt opt2 = OptionalInt.empty(); // OptionalInt에 0을 저장
다행히 저장된 값이 없는 것과 0이 저장된 것은 isPresent
라는 인스턴스 변수로 구분이 가능하다. isPresent()
는 이 인스턴스변수의 값을 반환한다.
System.out.println(opt.isPresent()); //true
System.out.println(opt2.isPresent()); //false
System.out.println(opt.getAsInt()); // 0
System.out.println(opt2.getAsInt()); // NoSuchElementException예외발생
System.out.println(opt.equals(opt2)); // false
그러나 Optional객체
의 경우 null
을 저장하면 비어있는 것과 동일하게 취급한다.
Optional<String> opt = Optional.ofNullable(null);
Optional<String> opt2 = Optional.empty();
System.out.println(opt.equals(opt2)); // true
import java.util.*;
public class OptionalEx1 {
public static void main(String[] args) {
Optional<String> optStr = Optional.of("abcde");
Optional<Integer> optInt = optStr.map(String::length);
System.out.println("optStr="+optStr.get());
System.out.println("optInt="+optInt.get());
String str = null;
int result1 = Optional.of("123")
.filter(x->x.length() > 0)
.map(Integer::parseInt).get(); // Optional객체에 저장된 값을 가져올때
int result2 = Optional.of("")
.filter(x->x.length() > 0)
.map(Integer::parseInt).orElse(-1); // Integer함수에 있는 parseInt객체를 쓴다.
//여기서 orElse는 객체에 저장된 값을 가져올때 null일때 NoSuchElementException이 발생하며, 이를 대비해서 orElse()로 대체할 값을 지정한다.
//""은 값이 null이라고 인식 -> 중간연산과정 filter는 x->x.length() > 0 을 제외하고 map은 그거를 숫자로 바꿀때 값이 null이어서 -1 출력
//filer는 주어진 조건에 맞지 않는 요소는 다 제거 즉, length>0이아닌 길이가 없는 것들 다 제거
System.out.println("result1="+result1);//123
System.out.println("result2="+result2);//-1
Optional.of("456").map(Integer::parseInt)
.ifPresent(x->System.out.printf("result3=%d%n",x));
OptionalInt optInt1 = OptionalInt.of(0); // 0을 저장
OptionalInt optInt2 = OptionalInt.empty(); // 빈 객체를 생성
System.out.println(optInt1.isPresent()); // true
System.out.println(optInt2.isPresent()); // false
System.out.println(optInt1.getAsInt()); // 0
//System.out.println(optInt2.getAsInt()); // NoSuchElementException
System.out.println("optInt1="+optInt1); // OptionalInt[0]
System.out.println("optInt2="+optInt2); // OptionalInt.empty
System.out.println("optInt1.equals(optInt2)?"+optInt1.equals(optInt2)); // false -> 0이랑 비어있는것은 다른것이다.
Optional<String> opt = Optional.ofNullable(null); // null을 저장한다.
Optional<String> opt2 = Optional.empty(); // 빈 객체를 생성
System.out.println("opt="+opt);
System.out.println("opt2="+opt2);
System.out.println("opt.equals(opt2)?"+opt.equals(opt2)); // true -> null이랑 비어있는것은 같은것이다.
int result3 = optStrToInt(Optional.of("123"),0);
int result4 = optStrToInt(Optional.of(""),0);
System.out.println("result3="+result3); //123
System.out.println("result4="+result4); //0
}
static int optStrToInt(Optional<String> optStr, int defaultValue){
try {
return optStr.map(Integer::parseInt).get();
}catch (Exception e){
return defaultValue;
}
}
}
optStr=abcde
optInt=5
result1=123
result2=-1
result3=456
true
false
0
optInt1=OptionalInt[0]
optInt2=OptionalInt.empty
optInt1.equals(optInt2)?false
opt=Optional.empty
opt2=Optional.empty
opt.equals(opt2)?true
result3=123
result4=0
📖 E. 스트림의 최종 연산
- 최종 연산은 스트림의 요소를 소모해서 결과를 만들어낸다.
- 그래서 최종 연산 후에는 스트림이 닫히게 되고 더 이상 사용할 수 없다.
- 최종 연산의 결과는 스트림 요소의 합과 같은 단일 값이거나, 스트림의 요소가 담긴 배열 또는 컬렉션일 수 있다.
✔️ forEach()
forEach()
는 peek()
와 달리 스트림의 요소를 소모하는 최종연산이다. 반환타입이 void
이므로 스트림의 요소를 출력하는 용도로 많이 사용된다.
void forEach(Consume<? super T> action)
✔️ 조건 검사 - allMatch(), anyMatch(), noneMatch(), findFirst(), findAny()
스트림의 요소에 대한 지정된 조건에 모든 요소가 일치하는 지, 일부가 일치하는지 아니면 어떤 요소도 일치하지 않는지 확인하는데 사용할 수 있는 메서드들이다. 이 메서드들은 모두 매개변수로 Predicate
를 요구하며, 연산결과로 boolean
을 반환한다.
boolean allMatch (Predicate<? super T> predicate)
boolean anyMatch (Predicate<? super T> predicate)
boolean noneMatch (Predicate<? super T> predicate)
예를 들어 학생들의 성적 정보 스트림 stuStream
에서 총점이 낙제점(총점 100점 이하)인 학생이 있는지 확인하는 방법은 다음과 같다.
boolean noFailed = stuStream.anyMatch(s -> s.getTotalScore()<=100)
이외에도 스트림의 요소 중에서 조건에 일치하는 첫 번재 것을 반환하는 findFirst()
가 있는데, 주로 filter()
와 함께 사용되어 조건에 맞는 스트림의 요소가 있는지 확인하는데 사용된다.
병렬 스트림인 경우에는 findFirst()
대신 findAny()
를 사용해야 한다.
Optional<Student> stu = stuStream.filter(s -> s.getTotalScore() <= 100).findFirst();
Optional<Student> stu = parallelStream.filter(s -> s.getTotalScore() <= 100).findAny();
findAny()
와 findFirst()
의 반환 타입은 Optional<T>
이며, 스트림의 요소가 없을 때는 비어있는 Optional객체
를 반환한다.
💡 참고
비어있는Optional객체
는 내부적으로null
을 저장하고 있다.
✔️ 통계 - count(), sum(), average(), max(), min()
InStream
과 같은 기본형 스트림에는 스트림의 요소들에 대한 통계 정보를 얻을 수 있는 메서드들이 있다. 그러나 기본형 스트림이 아닌 경우에는 통계와 관련된 메서드들이 아래의 3개뿐이다.
💡 참고
기본형 스트림의min()
,max()
와 달리 매개변수로Comparator
를 필요로 한다는 차이가 있다.
long count()
Optional<T> max(Comparator<? super T> comparator)
Optional<T> min(Comparator<? super T> comparator)
대부분의 경우 위 메서드를 사용하기보다 기본형 스트림으로 변환하거나, 아니면 앞으로 배우게 될 reduce()
와 collect()
를 사용해서 통계 정보를 얻는다.
✔️ 리듀싱 - reduce()
스트림의 요소를 줄여나가면서 연산을 수행하고 최종결과를 반환한다.
그래서 매개변수의 타입이 BinaryOperator<T>
인 것이다.
처음 두 요소를 가지고 연산한 결과를 가지고 그 다음 요소와 연산한다.
이 과정에서 스트림의 요소를 하나씩 소모하게 되며, 스트림의 모든 요소를 소모하게 되면 그 결과를 반환한다.
Optional<T> reduce(BinaryOperator<T> accumulator)
이 외에도 연산결과의 초기값(identify
)을 갖는 reduce()
도 있는데, 이 메서드들은 초기값과 스트림의 첫 번째 요소로 연산을 시작한다. 스트림의 요소가 하나도 없는 경우, 초기값이 반환되므로, 반환 타입이 Optional<T>
가 아니라 T
이다.
💡 참고
BinaryOperator<T>
는BiFunction의 자손
이며,BiFunction<T,T,T>
와 동등하다.
T reduce(T identify, BinaryOperator<T> accumulator)
U reduec(U identify, BiFunction<U,T,U> accumulator, BinaryOperator<U> combiner)
위의 두 번째 메서드의 마지막 매개변수인 combiner
는 병렬 스트림에 의해 처리된 결과를 합칠 때 사용하기 위해 사용하는 것이다.
최종 연산 count()
와 sum()
등은 내부적으로 모두 reduce()
를 이용해서 아래와 같이 작성된 것이다.
int count = intStream.reduce(0, (a,b) -> a+1); // count()
int sum = intStream.reduce(0, (a,b) -> a+b); // sum()
int max = intStream.reduce(0, (a,b) -> a>b ? a:b); // max()
int min = intStream.reduce(Integer.MIN_VALUE, (a,b) -> a
reduce의 동작과정
reduce()
가 내부적으로 어떻게 동작하는지 이해를 돕기 위해, reduce()
로 스트림의 모든 요소를 다 더하는 과정을 for문
으로 표현해 보았다.
int a = identity; // 초기값을 저장한다.
for(int b : stream)
a = a + b; // 모든 요소의 값을 a에 누적한다.
위의 for문
을 보고 나면, reduce()
가 아마도 다음과 같이 작성되어 있을 것이라고 추측하는 것은 그리 어려운 일이 아닐 것이다.
T reduce(T identity, BinaryOperator<T> accmulator){
T a = identity;
for(T b : stream)
a = accumulator.apply(a, b);
return a;
}
reduce()
를 사용하는 방법은 간단하다. 그저 초기값(identity
)과 어떤 연산(BinaryOperator
)으로 스트림의 요소를 줄여나갈 것인지만 결정하면 된다.
import java.util.*;
import java.util.stream.*;
public class StreamEx5 {
public static void main(String[] args) {
String[] strArr = {
"Inheritance", "Java", "Lambda", "stream",
"OptionalDouble", "IntStream", "count", "sum"
};
Stream.of(strArr).forEach(System.out::println);
boolean noEmptyStr = Stream.of(strArr).noneMatch(s->s.length()==0);
Optional<String> sWord = Stream.of(strArr)
.filter(s->s.charAt(0)=='s').findFirst();
System.out.println("noEmptyStr = " + noEmptyStr);
System.out.println("sWord = " + sWord.get());
// Stream<String[]>을 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);
int sum = intStream2.reduce(0, (a,b)->a+b);
OptionalInt max = intStream3.reduce(Integer::max);
OptionalInt min = intStream4.reduce(Integer::min);
System.out.println("count = " + count);
System.out.println("sum = " + sum);
System.out.println("max = " + max.getAsInt());
System.out.println("min = " + min.getAsInt());
}
}
Inheritance
Java
Lambda
stream
OptionalDouble
IntStream
count
sum
noEmptyStr = true
sWord = stream
count = 8
sum = 58
max = 14
min = 3
Author And Source
이 문제에 관하여(람다와 스트림 3), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@chang626/람다와-스트림-3저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)