20. 스트림(Stream) API
1. 키워드
- 스트림 API
- 스트림의 생성
- 스트림의 중개 연산(Intermediate Operation)
- 스트림의 최종 연산(Terminal Operation)
Optiona
l 클래스
2. 스트림 API
- Java SE 8부터 추가된 스트림 API는 앞서 입력과 출력(IO)에서 살펴본 스트림과는 전혀 다른 개념이다.
- 자바에서는 많은 양의 데이터를 저장하기 위해서 배열이나 컬렉션을 사용한다.
- 이렇게 저장된 데이터에 접근하기 위해서는 반복문이나 반복자(
Iterator
)를 사용하여 매번 새로운 코드를 작성해야 한다. - 하지만 이렇게 작성된 코드는 길이가 너무 길고 가독성도 떨어지며, 코드의 재사용이 거의 불가능하다.
- 즉, 데이터베이스의 쿼리와 같이 정형화된 처리 패턴을 가지지 못했기 때문에 데이터마다 다른 방법으로 접근해야만 했다.
- 이러한 문제점을 극복하기 위해서 Java SE 8부터 스트림 API를 도입했다.
- 스트림 API는 데이터를 추상화하여 다루므로, 다양한 방식으로 저장된 데이터를 읽고 쓰기 위한 공통된 방법을 제공한다.
- 따라서 스트림 API를 이용하면 배열이나 컬렉션뿐만 아니라 파일에 저장된 데이터도 모두 같은 방법으로 다룰 수 있게 된다.
1) 스트림 API의 특징
- 스트림 API는 다음과 같은 특징을 가진다.
1] 스트림은 외부 반복을 통해 작업하는 컬렉션과는 달리 내부 반복(Internal Iteration)을 통해 작업을 수행한다.
2] 스트림은 재사용이 가능한 컬렉션과는 달리 단 한 번만 사용할 수 있다.
3] 스트림은 원본 데이터를 변경하지 않는다.
4] 스트림의 연산은 필터-맵(Filter-Map) 기반의 API를 사용하여 지연(Lazy) 연산을 통해 성능을 최적화한다.
5] 스트림은 parallelStream()
메서드를 통한 손쉬운 병렬 처리를 지원한다.
2) 스트림 API의 동작 흐름
- 스트림 API는 다음과 같이 세 가지 단계에 걸쳐서 동작한다.
1] 스트림의 생성
2] 스트림의 중개 연산 (스트림의 변환)
3] 스트림의 최종 연산 (스트림의 사용)
- 다음 그림은 자바 스트림 API가 동작하는 흐름을 나타낸다.
3. 스트림의 생성
- 스트림 API는 다음과 같은 다양한 데이터 소스에서 생성할 수 있다.
1] 컬렉션
2] 배열
3] 가변 매개변수
4] 지정된 범위의 연속된 정수
5] 특정 타입의 난수들
6] 람다 표현식
7] 파일
8] 빈 스트림
1) 컬렉션
- 자바에서 제공하는 모든 컬렉션의 최고 상위 조상인
Collection
인터페이스에는stream()
메서드가 정의되어 있다. - 따라서
Collection
인터페이스를 구현한 모든List
와Set
컬렉션 클래스에서도stream()
메서드로 스트림을 생성할 수 있다. - 또한,
parallelStream()
메서드를 사용하면 병렬 처리가 가능한 스트림을 생성할 수 있다.
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(4);
list.add(2);
list.add(3);
list.add(1);
Stream<Integer> stream = list.stream(); // 컬렉션에서 스트림 생성
stream.forEach(System.out::println); // forEach() 메서드를 이용한 스트림 요소의 순차 접근
// 4
// 2
// 3
// 1
Stream
클래스의forEach()
메서드는 해당 스트림의 요소를 하나씩 소모해가며 순차적으로 요소에 접근하는 메서드이다.- 따라서 같은 스트림으로는
forEach()
메서드를 한 번밖에 호출할 수 없다. - 단, 원본 데이터의 요소를 소모하는 것은 아니므로, 같은 데이터에서 또 다른 스트림을 생성하여
forEach()
메서드를 호출하는 것은 가능하다.
2) 배열
- 배열에 관한 스트림을 생성하기 위해
Arrays
클래스에는 다양한 형태의stream()
메서드가 클래스 메서드로 정의되어 있다. - 또한, 기본 타입인
int
,long
,double
형을 저장할 수 있는 배열에 관한 스트림이 별도로 정의되어 있다. - 이러한 스트림은
java.util.stream
패키지의IntStream
,LongStream
,DoubleStream
인터페이스로 각각 제공된다.
String[] arr = new String[] { "넷", "둘", "셋", "하나" };
Stream<String> stream1 = Arrays.stream(arr); // 배열에서 스트림 생성
stream1.forEach(e -> System.out.print(e + " "));
System.out.println();
Stream<String> stream2 = Arrays.stream(arr, 1, 3); // 배열의 특정 부분만을 이용한 스트림 생성
stream2.forEach(e -> System.out.print(e + " "));
// 넷 둘 셋 하나
// 둘 셋
Arrays
클래스의stream()
메서드는 전체 배열뿐만 아니라 배열의 특정 부분만을 이용하여 스트림을 생성할 수도 있다.
3) 가변 매개변수(Variable Parameter)
Stream
클래스의of()
메서드를 사용하면 가변 매개변수를 전달받아 스트림을 생성할 수 있다.
Stream<Double> stream = Stream.of(4.2, 2.5, 3.1, 1.9); // 가변 매개변수에서 스트림 생성
stream.forEach(System.out::println);
// 4.2
// 2.5
// 3.1
// 1.9
4) 지정된 범위의 연속된 정수
- 지정된 범위의 연속된 정수를 스트림으로 생성하기 위해
IntStream
이나LongStream
인터페이스에는range()
와rangeClosed()
메서드가 정의되어 있다. range()
메서드는 명시된 시작 정수를 포함하지만, 명시된 마지막 정수는 포함하지 않는 스트림을 생성한다.rangeClosed()
메서드는 명시된 시작 정수뿐만 아니라 명시된 마지막 정수까지도 포함하는 스트림을 생성한다.
IntStream stream1 = IntStream.range(1, 4); // 지정된 범위의 연속된 정수에서 스트림 생성
stream1.forEach(e -> System.out.print(e + " "));
System.out.println();
IntStream stream2 = IntStream.rangeClosed(1, 4);
stream2.forEach(e -> System.out.print(e + " "));
// 1 2 3
// 1 2 3 4
5) 특정 타입의 난수들
- 특정 타입의 난수로 이루어진 스트림을 생성하기 위해
Random
클래스에는ints()
,longs()
,doubles()
와 같은 메서드가 정의되어 있다. - 이 메서드들은 매개변수로 스트림의 크기를
long
타입으로 전달받을 수 있다. - 이 메서드들은 만약 매개변수를 전달받지 않으면 크기가 정해지지 않은 무한 스트림(Infinite Stream)을 반환한다.
- 이때에는
limit()
메서드를 사용하여 따로 스트림의 크기를 제한해야 한다.
IntStream stream = new Random().ints(4); // 특정 타입의 난수로 이루어진 스트림 생성
stream.forEach(System.out::println);
// -1001463507
// -552757525
// -1266712892
// -77125700
6) 람다 표현식
- 람다 표현식을 매개변수로 전달받아 해당 람다 표현식에 의해 반환되는 값을 요소로 하는 무한 스트림을 생성하기 위해
Stream
클래스에는iterate()
와generate()
메서드가 정의되어 있다. iterate()
메서드는 시드(Seed)로 명시된 값을 람다 표현식에 사용하여 반환된 값을 다시 시드로 사용하는 방식으로 무한 스트림을 생성한다.- 반면에
generate()
메서드는 매개변수가 없는 람다 표현식을 사용하여 반환된 값을 무한 스트림을 생성한다.
- 다음 예제는
iterate()
메서드를 이용하여 홀수만으로 이루어진 무한 스트림을 생성하는 예제이다.
7) 파일
- 파일의 한 행(Line)을 요소로 하는 스트림을 생성하기 위해
java.nio.file.Files
클래스에는lines()
메서드가 정의되어 있다. - 또한,
java.io.BufferedReader
클래스의lines()
메서드를 사용하면 파일뿐만 아니라 다른 입력으로부터도 데이터를 행 단위로 읽어 올 수 있다.
8) 빈 스트림
- 아무 요소도 가지지 않는 빈 스트림은
Stream
클래스의empty()
메서드를 사용하여 생성할 수 있다.
Stream<Object> stream = Stream.empty(); // 빈 스트림 생성
System.out.println(stream.count()); // 0 -> 스트림의 요소의 총 개수를 출력
4. 스트림의 중개 연산(Intermediate Operation)
- 스트림 API에 의해 생성된 초기 스트림은 중개 연산을 통해 또 다른 스트림으로 변환된다.
- 이러한 중개 연산은 스트림을 전달받아 스트림을 반환하므로, 중개 연산은 연속으로 연결해서 사용할 수 있다.
- 또한, 스트림의 중개 연산은 필터-맵(Filter-Map) 기반의 API를 사용함으로 지연(Lazy) 연산을 통해 성능을 최적화할 수 있다.
- 스트림 API에서 사용할 수 있는 대표적인 중개 연산과 그에 따른 메서드는 다음과 같다.
1] 스트림 필터링: filter()
, distinct()
2] 스트림 변환: map()
, flatMap()
3] 스트림 제한: limit()
, skip()
4] 스트림 정렬: sorted()
5] 스트림 연산 결과 확인: peek()
1) 스트림 필터링
filter()
메서드는 해당 스트림에서 주어진 조건(Predicate)에 맞는 요소만으로 구성된 새로운 스트림을 반환한다.- 또한,
distinct()
메서드는 해당 스트림에서 중복된 요소가 제거된 새로운 스트림을 반환한다. distinct()
메서드는 내부적으로Object
클래스의equals()
메서드를 사용하여 요소의 중복을 비교한다.
IntStream stream1 = IntStream.of(7, 5, 5, 2, 1, 2, 3, 5, 4, 6);
IntStream stream2 = IntStream.of(7, 5, 5, 2, 1, 2, 3, 5, 4, 6);
stream1.distinct().forEach(e -> System.out.print(e + " ")); // 스트림에서 중복된 요소 제거
System.out.println();
stream2.filter(n -> n % 2 != 0).forEach(e -> System.out.print(e + " ")); // 스트림에서 홀수만을 골라냄
// 7 5 2 1 3 4 6
// 7 5 5 1 3 5
2) 스트림 변환
map()
메서드는 해당 스트림의 요소들을 주어진 함수에 인수로 전달하여, 그 반환값들로 이루어진 새로운 스트림을 반환한다.- 만약 해당 스트림의 요소가 배열이라면,
flatMap()
메서드를 사용하여 각 배열의 각 요소의 반환값을 하나로 합친 새로운 스트림을 얻을 수 있다.
- 다음 예제는 문자열로 이루어진 스트림을
map()
메서드를 이용하여 각 문자열의 길이로 이루어진 스트림으로 변환하는 예제이다.
Stream<String> stream = Stream.of("HTML", "CSS", "JAVA", "JAVASCRIPT");
stream.map(s -> s.length()).forEach(System.out::println);
// 4
// 3
// 4
// 10
- 다음 예제는 여러 문자열이 저장된 배열을 각 문자열에 포함된 단어로 이루어진 스트림으로 변환하는 예제이다.
String[] arr = { "I study hard", "You study JAVA", "I am hungry" };
Stream<String> stream = Arrays.stream(arr);
stream.flatMap(s -> Stream.of(s.split(" +"))).forEach(System.out::println);
// I
// study
// hard
// You
// study
// JAVA
// I
// am
// hungry
3) 스트림 제한
limit()
메서드는 해당 스트림의 첫 번째 요소부터 전달된 개수만큼의 요소만으로 이루어진 새로운 스트림을 반환한다.skip()
메서드는 해당 스트림의 첫 번째 요소부터 전달된 개수만큼의 요소를 제외한 나머지 요소만으로 이루어진 새로운 스트림을 반환한다.
IntStream stream1 = IntStream.range(0, 10);
IntStream stream2 = IntStream.range(0, 10);
IntStream stream3 = IntStream.range(0, 10);
stream1.skip(4).forEach(n -> System.out.print(n + " "));
System.out.println();
stream2.limit(5).forEach(n -> System.out.print(n + " "));
System.out.println();
stream3.skip(3).limit(5).forEach(n -> System.out.print(n + " "));
// 4 5 6 7 8 9
// 0 1 2 3 4
// 3 4 5 6 7
4) 스트림 정렬
sorted()
메서드는 해당 스트림을 주어진 비교자(Comparator)를 이용하여 정렬한다.- 이때 비교자를 전달하지 않으면 기본적으로 사전 편찬 순(Natural Order)으로 정렬하게 된다.
Stream<String> stream1 = Stream.of("JAVA", "HTML", "JAVASCRIPT", "CSS");
Stream<String> stream2 = Stream.of("JAVA", "HTML", "JAVASCRIPT", "CSS");
stream1.sorted().forEach(s -> System.out.print(s + " "));
System.out.println();
stream2
.sorted(Comparator.reverseOrder())
.forEach(s -> System.out.print(s + " "));
// CSS HTML JAVA JAVASCRIPT
// JAVASCRIPT JAVA HTML CSS
5) 스트림 연산 결과 확인
peek()
메서드는 결과 스트림으로부터 요소를 소모하여 추가로 명시된 동작을 수행한다.- 이 메서드는 원본 스트림에서 요소를 소모하지 않으므로, 주로 연산과 연산 사이에 결과를 확인하고 싶을 때 사용한다.
- 따라서 개발자가 디버깅 용도로 많이 사용한다.
IntStream stream = IntStream.of(7, 5, 5, 2, 1, 2, 3, 5, 4, 6);
stream
.peek(s -> System.out.println("원본 스트림 : " + s))
.skip(2)
.peek(s -> System.out.println("skip(2) 실행 후 : " + s))
.limit(5)
.peek(s -> System.out.println("limit(5) 실행 후 : " + s))
.sorted()
.peek(s -> System.out.println("sorted() 실행 후 : " + s))
.forEach(n -> System.out.println(n));
// 원본 스트림 : 7
// 원본 스트림 : 5
// 원본 스트림 : 5
// skip(2) 실행 후 : 5
// limit(5) 실행 후 : 5
// 원본 스트림 : 2
// skip(2) 실행 후 : 2
// limit(5) 실행 후 : 2
// 원본 스트림 : 1
// skip(2) 실행 후 : 1
// limit(5) 실행 후 : 1
// 원본 스트림 : 2
// skip(2) 실행 후 : 2
// limit(5) 실행 후 : 2
// 원본 스트림 : 3
// skip(2) 실행 후 : 3
// limit(5) 실행 후 : 3
// sorted() 실행 후 : 1
// 1
// sorted() 실행 후 : 2
// 2
// sorted() 실행 후 : 2
// 2
// sorted() 실행 후 : 3
// 3
// sorted() 실행 후 : 5
// 5
- 위의 예제에서 첫 번째 요소인
7
과 두 번째 요소인5
는skip()
메서드에 의해 삭제되므로, 원본 스트림에서만 나타난다. - 하지만 세 번째 요소인
5
는skip()
메서드와limit()
메서드가 실행된 후에도 존재하므로, 모두 나타난다. - 이렇게
peek()
메서드는 스트림의 각 요소가 해당 중개 연산 후에 어떻게 변화하는지를 보여준다.
5. 스트림의 최종 연산(Terminal Operation)
- 스트림 API에서 중개 연산을 통해 변환된 스트림은 마지막으로 최종 연산을 통해 각 요소를 소모하여 결과를 표시한다.
- 즉, 지연(Lazy)되었던 모든 중개 연산들이 최종 연산 시에 모두 수행되는 것이다.
- 이렇게 최종 연산 시에 모든 요소를 소모한 해당 스트림은 더는 사용할 수 없게 된다.
- 스트림 API에서 사용할 수 있는 대표적인 최종 연산과 그에 따른 메서드는 다음과 같다.
1] 요소의 출력: forEach()
2] 요소의 소모: reduce()
3] 요소의 검색: findFirst()
, findAny()
4] 요소의 검사: anyMatch()
, allMatch()
, noneMatch()
5] 요소의 통계: count()
, min()
, max()
6] 요소의 연산: sum()
, average()
7] 요소의 수집: collect()
1) 요소의 출력
forEach()
메서드는 스트림의 각 요소를 소모하여 명시된 동작을 수행한다.- 반환 타입이
void
이므로 보통 스트림의 모든 요소를 출력하는 용도로 많이 사용한다.
Stream<String> stream = Stream.of("넷", "둘", "셋", "하나");
stream.forEach(System.out::println);
// 넷
// 둘
// 셋
// 하나
2) 요소의 소모
- 스트림의 최종 연산은 모두 스트림의 각 요소를 소모하여 연산을 수행하게 된다.
- 하지만
reduce()
메서드는 첫 번째와 두 번째 요소를 가지고 연산을 수행한 뒤, 그 결과와 세 번째 요소를 가지고 또다시 연산을 수행한다. - 이런 식으로 해당 스트림의 모든 요소를 소모하여 연산을 수행하고, 그 결과를 반환하게 된다.
- 또한, 인수로 초깃값을 전달하면 초깃값과 해당 스트림의 첫 번째 요소와 연산을 시작하며, 그 결과와 두 번째 요소를 가지고 계속해서 연산을 수행하게 된다.
- 다음 예제는 스트림의 각 문자열 요소를
++
기호로 연결하여 출력하는 예제이다.
Stream<String> stream1 = Stream.of("넷", "둘", "셋", "하나");
Stream<String> stream2 = Stream.of("넷", "둘", "셋", "하나");
Optional<String> result1 = stream1.reduce((s1, s2) -> s1 + "++" + s2);
result1.ifPresent(System.out::println);
String result2 = stream2.reduce("시작", (s1, s2) -> s1 + "++" + s2);
System.out.println(result2);
// 넷++둘++셋++하나
// 시작++넷++둘++셋++하나
- 위의 예제처럼 인수로 초깃값을 전달하는
reduce()
메서드의 반환 타입은Optional<T>
가 아닌T
타입이다. - 그 이유는 비어있는 스트림과
reduce
연산을 할 경우 전달받은 초깃값을 그대로 반환해야 하기 때문이다.
3) 요소의 검색
findFirst()
와findAny()
메서드는 해당 스트림에서 첫 번째 요소를 참조하는Optional
객체를 반환한다.- 두 메서드 모두 비어있는 스트림에서는 비어있는
Optional
객체를 반환한다.
- 다음 예제는 스트림의 모든 요소를 정렬한 후, 첫 번째에 위치한 요소를 출력하는 예제이다.
IntStream stream1 = IntStream.of(4, 2, 7, 3, 5, 1, 6);
IntStream stream2 = IntStream.of(4, 2, 7, 3, 5, 1, 6);
OptionalInt result1 = stream1.sorted().findFirst();
System.out.println(result1.getAsInt());
OptionalInt result2 = stream2.sorted().findAny();
System.out.println(result2.getAsInt());
// 1
// 1
- 위의 예제에서 볼 수 있듯이 두 메서드의 결과는 같게 출력된다.
- 하지만 병렬 스트림인 경우에는
findAny()
메서드를 사용해야만 정확한 연산 결과를 반환할 수 있다.
4) 요소의 검사
- 해당 스트림의 요소 중에서 특정 조건을 만족하는 요소가 있는지, 아니면, 모두 만족하거나 모두 만족하지 않는지를 다음 메서드를 사용하여 확인할 수 있다.
1] anyMatch()
- 해당 스트림의 일부 요소가 특정 조건을 만족할 경우에
true
를 반환함
2] allMatch()
- 해당 스트림의 모든 요소가 특정 조건을 만족할 경우에
true
를 반환함
3] noneMatch()
- 해당 스트림의 모든 요소가 특정 조건을 만족하지 않을 경우에
true
를 반환함
- 세 메서드 모두 인수로
Predicate
객체를 전달받으며, 요소의 검사 결과는boolean
값으로 반환한다.
- 다음 예제는 스트림의 모든 요소를 검사하여
80
보다 큰 값을 가지는 요소가 하나라도 존재하는지를 검사하는 예제이다.
IntStream stream1 = IntStream.of(30, 90, 70, 10);
IntStream stream2 = IntStream.of(30, 90, 70, 10);
System.out.println(stream1.anyMatch(n -> n > 80));
System.out.println(stream2.allMatch(n -> n > 80));
// true
// false
5) 요소의 통계
count()
메서드는 해당 스트림의 요소의 총 개수를long
타입의 값으로 반환한다.- 또한,
max()
와min()
메서드를 사용하면 해당 스트림의 요소 중에서 가장 큰 값과 가장 작은 값을 가지는 요소를 참조하는Optional
객체를 얻을 수 있다.
IntStream stream1 = IntStream.of(30, 90, 70, 10);
IntStream stream2 = IntStream.of(30, 90, 70, 10);
System.out.println(stream1.count());
System.out.println(stream2.max().getAsInt());
// 4
// 90
6) 요소의 연산
IntStream
이나DoubleStream
과 같은 기본 타입 스트림에는 해당 스트림의 모든 요소에 대해 합과 평균을 구할 수 있는sum()
과average()
메서드가 각각 정의되어 있다.- 이때
average()
메서드는 각 기본 타입으로 래핑된Optional
객체를 반환한다.
IntStream stream1 = IntStream.of(30, 90, 70, 10);
DoubleStream stream2 = DoubleStream.of(30.3, 90.9, 70.7, 10.1);
System.out.println(stream1.sum());
System.out.println(stream2.average().getAsDouble());
// 200
// 50.5
7) 요소의 수집
collect()
메서드는 인수로 전달되는Collectors
객체에 구현된 방법대로 스트림의 요소를 수집한다.- 또한,
Collectors
클래스에는 미리 정의된 다양한 방법이 클래스 메서드로 정의되어 있다. - 이 외에도 사용자가 직접
Collector
인터페이스를 구현하여 자신만의 수집 방법을 정의할 수도 있다. - 스트림의 요소의 수집 용도별 사용할 수 있는
Collectors
메서드는 다음과 같다.
1] 스트림을 배열이나 컬렉션으로 변환: toArray()
, toCollection()
, toList()
, toSet()
, toMap()
2] 요소의 통계와 연산 메서드와 같은 동작을 수행: counting()
, maxBy()
, minBy()
, summingInt()
, averagingInt()
등
3] 요소의 소모와 같은 동작을 수행: reducing()
, joining()
4] 요소의 그룹화와 분할: groupingBy()
, partitioningBy()
- 다음 예제는
collect()
메서드를 이용하여 해당 스트림을 리스트로 변환하는 예제이다.
Stream<String> stream = Stream.of("넷", "둘", "하나", "셋");
List<String> list = stream.collect(Collectors.toList());
Iterator<String> iter = list.iterator();
while (iter.hasNext()) {
System.out.print(iter.next() + " ");
}
// 넷 둘 하나 셋
- 다음 예제는
Collectors
클래스의partitioningBy()
메서드를 이용하여 해당 스트림의 각 요소별 글자 수에 따라 홀수와 짝수로 나누어 저장하는 예제이다.
Stream<String> stream = Stream.of("HTML", "CSS", "JAVA", "PHP");
Map<Boolean, List<String>> patition = stream.collect(
Collectors.partitioningBy(s -> (s.length() % 2) == 0)
);
List<String> oddLengthList = patition.get(false);
System.out.println(oddLengthList);
List<String> evenLengthList = patition.get(true);
System.out.println(evenLengthList);
// [CSS, PHP]
// [HTML, JAVA]
6. Optional
클래스
1) java.util.Optional<T>
클래스
Optional<T>
클래스는Integer
나Double
클래스처럼T
타입의 객체를 포장해 주는Wrapper
클래스이다.- 따라서
Optional
인스턴스는 모든 타입의 참조 변수를 저장할 수 있다. - 이러한
Optional
객체를 사용하면 예상치 못한NullPointerException
예외를 제공되는 메서드로 간단히 회피할 수 있다. - 즉, 복잡한 조건문 없이도 널(
null
) 값으로 인해 발생하는 예외를 처리할 수 있게 된다.
2) Optional
객체의 생성
of()
메서드나ofNullable()
메서드를 사용하여Optional
객체를 생성할 수 있다.of()
메서드는null
이 아닌 명시된 값을 가지는Optional
객체를 반환한다.- 만약
of()
메서드를 통해 생성된Optional
객체에null
이 저장되면NullPointerException
예외가 발생한다. - 따라서 만약 참조 변수의 값이 만에 하나
null
이 될 가능성이 있다면,ofNullable()
메서드를 사용하여Optional
객체를 생성하는 것이 좋다. ofNullable()
메서드는 명시된 값이null
이 아니면 명시된 값을 가지는Optional
객체를 반환하며, 명시된 값이null
이면 비어있는Optional
객체를 반환한다.
Optional<String> opt = Optional.ofNullable("자바 Optional 객체");
System.out.println(opt.get()); // 자바 Optional 객체
3) Optional
객체에 접근
get()
메서드를 사용하면Optional
객체에 저장된 값에 접근할 수 있다.- 만약
Optional
객체에 저장된 값이null
이면,NoSuchElementException
예외가 발생한다. - 따라서
get()
메서드를 호출하기 전에isPresent()
메서드를 사용하여Optional
객체에 저장된 값이null
인지 아닌지를 먼저 확인한 후 호출하는 것이 좋다.
- 다음 예제는
isPresent()
메서드를 이용하여 좀 더 안전하게Optional
객체에 저장된 값에 접근하는 예제이다.
Optional<String> opt = Optional.ofNullable("자바 Optional 객체");
if (opt.isPresent()) {
System.out.println(opt.get());
}
// 자바 Optional 객체
- 또한, 다음과 같은 메서드를 사용하여
null
대신에 대체할 값을 지정할 수도 있다.
1] orElse()
메서드
- 저장된 값이 존재하면 그 값을 반환하고, 값이 존재하지 않으면 인수로 전달된 값을 반환함
2] orElseGet()
메서드
- 저장된 값이 존재하면 그 값을 반환하고, 값이 존재하지 않으면 인수로 전달된 람다 표현식의 결괏값을 반환함
3] orElseThrow()
메서드
- 저장된 값이 존재하면 그 값을 반환하고, 값이 존재하지 않으면 인수로 전달된 예외를 발생시킴
Optional<String> opt = Optional.empty(); // Optional를 null로 초기화함.
System.out.println(opt.orElse("빈 Optional 객체"));
System.out.println(opt.orElseGet(String::new));
// 빈 Optional 객체
- 위의 예제에서 사용된
empty()
메서드는Optional
객체를null
로 초기화해 준다.
4) 기본 타입의 Optional
클래스
- 자바에서는
IntStream
클래스와 같이 기본 타입 스트림을 위한 별도의Optional
클래스를 제공하고 있다.
1] OptionalInt
클래스
2] OptionalLong
클래스
3] OptionalDouble
클래스
- 이러한 클래스는 반환 타입이
Optional<T>
타입이 아니라 해당 기본 타입이라는 사실만 제외하면 거의 모든 면에서 비슷하다.
IntStream stream = IntStream.of(4, 2, 1, 3);
OptionalInt result = stream.findFirst();
System.out.println(result.getAsInt()); // 4