람다와 스트림 3

74761 단어 JavaJava

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()를 이용해서 바꾸면 더 간단히 할 수 있다. 아래의 문장은 참조변수 strnull이 아닐 때만 값을 출력하고, 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()
OptionalIntint getAsInt()
OptionalLonglong getAsLong()
OptionalDoubledouble 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

 

좋은 웹페이지 즐겨찾기