관리 메뉴

나구리의 개발공부기록

자바의 정석 기초편 ch14 - 35 ~ 44 [Optional, 최종연산(reduce까지)] 본문

유튜브 공부/JAVA의 정석 기초편(유튜브)

자바의 정석 기초편 ch14 - 35 ~ 44 [Optional, 최종연산(reduce까지)]

소소한나구리 2023. 12. 22. 09:13

1) Optional<T>

  • T타입 객체의 래퍼클래스이며 간접적으로 null을 다루고 코드를 간결하게 함
  • null을 직접 다루는 것은 위험(NullPointerException 발생) 하기 때문에 null체크을 항상 체크해야하는데 if문으로 항상 null을 체크하게되면 코드가 지저분해지게 되어 Optional을 반환타입으로 자주 사용함
public final class Optional<T> {
     private final T value	// 모든 종류의 객체를 저장 가능(참조형), null포함
     ...
}

 

(1) Optional<T>객체를 생성하는 다양한 방법

  • of는 null을 허용하지 않고 ofNullable은 null을 허용
String str = "abc";
Optional<String> optVal = Optional.of(str);
Optional<String> optVal = Optional.of("abc");
Optional<String> optVal = Optional.of(null);         // NullPointerException발생
Optional<String> optVal = Optional.ofNullable(null); // OK

 

(2) null대신 빈 Optional<T>객체를 사용

  • ofNullable로 null을 초기화하는 것은 권장하지 않으므로 값이 없다면 empty()로 빈 Optional로 초기화하는 것을 권장함
Optional<String> optVal = null;	// 널 초기화 -> 바람직하지 않음
Optional<String> optVal = Optional.<String>empty();	// 빈 객체로 초기화, 제네릭스 생략가능

 

(3) Optional<T>객체의 값 가져오기

  • 전부 값을 반환하지만 null일때의 동작이 다름
  • get() : null일때는 예외발생하여 잘 사용하지 않음
  • orElse() : null일때 인수에 입력한 값을 반환함
  • orElseGet() : null일때의 동작을 람다식이나 메서드참조를 입력하여 작성
  • orElseThrow() : null일 때 인수의 예외를 발생시킬 수 있음, 람다식 or 메서드참조로 작성
Optional<String> optVal = Optional.of("abc");
String str1 = optVal.get();                  // optVal에 저장된 값을 반환. null이면 예외발생(잘안씀)
String str2 = optVal.orElse("");             // optVal에 저장된 값이 null일 때는, ""를 반환
String str3 = optVal.orElseGet(String::new); // 람다식 사용가능 () -> new String() 
String str4 = optVal.orElseThrow(NullPointerException::new); // 값이 없으면 예외발생

// 메서드 선언부
T orElseGet(Supplier<? extends T> other)
T orElseThrow(Supplier<? extends X> exceptionSupplier)

 

(4) isPresent() - null 여부 확인

  • Optional객체의 값이 null이면 false, 아니면 true를 반환
if(Optional.ofNullable(str).isPresent()) {  // if(str!=null) {
     System.out.println(str);
}

// ifPresnt(Consumer) - 널이 아닐때만 작업 수행, 널이면 아무 일도 안 함
Optional.ofNullable(str).ifPresent(System.out::println);

 

(5) 예제

  • 다양한 Optional 객체 생성 방법
import java.util.Optional;

public class Ex14_OptionalBasic {
	public static void main(String[] args) {
    
//		int[] arr = null; // 에러발생(NullPointerException)
		int[] arr= new int[0];
		System.out.println("arr.length=" +arr.length);
		
//		Optional<String> opt = null;	// 에러는 발생하지 않지만 바람직하지 않음
		Optional<String> opt = Optional.empty();   // 빈 Optional 객체 생성 
		Optional<String> opt2 = Optional.of("abc"); // 값이 "abc"인 opt객체 생성
		System.out.println("opt2=" +opt2);
//		System.out.println("opt=" +opt.get());	// Optional이 null이면 에러 발생

		String str = "";
		
		str = opt.orElse("");	// Optional에 저장된 값이 null이면 "" 반환
//		str = opt.orElseGet(() -> new String());   // 람다식 사용
		str = opt.orElseGet(String::new);          // 메서드참조로 변경(위와 같음)
		System.out.println("str="+str);
	
	}
}

 

2) OptionalInt, OptionalLong, OptionalDouble

  • 기본형 값을 감싸는 래퍼클래스, 기본형스트림과 마찬가지로 성능향상을 위해 사용
  • 값의 저장 여부와 값을 담을 변수가 멤버로 있음
// OptionalInt 내부 구조 일부
public final class OptionalInt {
     ...
     private final boolean isPresent; // 값이 저장되어 있으면 true
     private final int value;         // int타입의 변수(기본형)
     ...
}

 

(1) 값 반환

  • OptionalInt : getAsInt()
  • OptionalLong : getAsLong()
  • OptionalDouble : getAsDouble()

(2) 빈 Optional객체와의 비교

  • isPresent()로 옵셔널에 값이 저장되어있는지 확인할 수 있음, 값이 null이면 false
  • 옵셔널을 equals로 비교하면 값이 같아도 isPresent의 결과까지 true여야 equals의 결과가 true가 됨
// opt1과 opt2를 어떻게 구분? isPresent 활용(값이 있으면 true)
OptionalInt opt1 = OptionalInt.of(0);   // OptionalInt에 0을 저장
OptionalInt opt2 = OptionalInt.empty(); // 빈 OptionalInt객체. OptionalInt에 0이 저장됨

System.out.println(opt1.isPresent()); // true
System.out.println(opt2.isPresent()); // false
System.out.println(opt1.equals(opt2)); // false, value는 같지만 isPresent값까지 같아야 true

 

(3) 예제 

package ch14;

import java.util.*;

class Ex14_8 {
    public static void main(String[] args) {

        Optional<String> optStr = Optional.of("abcdef");
//      Optional<Integer> optInt = Optional.of(6);
//      Optional<Integer> optInt = optStr.map((i) -> i.length());
        Optional<Integer> optInt = optStr.map(String::length);  // optStr에 저장된 요소의 길이로 옵셔널 생성

        System.out.println("optStr = " + optStr.get());
        System.out.println("optInt = " + optInt.get());

        int result1 = Optional.of("123") // 길이가 3인 문자열
                .filter((s) -> s.length() > 0) // 0보다 큰 문자열의 길이를 반환
                .map(Integer::parseInt).get(); // Integer로 변환 후 값을 저장

        int result2 = Optional.of("") // 길이가 0인 문자열
                .filter((s) -> s.length() > 0)
                .map(Integer::parseInt).orElse(-1);
        // 값이 없음(null) -> null이면 -1 출력

        System.out.println("result1 = " + result1);
        System.out.println("result2 = " + result2);
        
        // 참조변수에 저장하지 않고 바로 출력
        Optional.of("너굴너굴").map(String::valueOf)
                .ifPresent( x -> System.out.printf("result3 = %s%n",x));

        Optional.of("456").map(Integer::parseInt)
                .ifPresent( x -> System.out.printf("result4 = %s%n",x));

        OptionalInt optInt1 = OptionalInt.of(0);  // 값이 0인 OptionalInt
        OptionalInt optInt2 = OptionalInt.empty();      // 빈객체를 생성

        //isPresent() : 객체의 값이 null이면 false 아니면 true
        System.out.println(optInt1.isPresent()); // 값이 0 -> true
        System.out.println(optInt2.isPresent()); // 값이 null -> false

        System.out.println(optInt1.getAsInt()); // 값 가져오기
        // 값 가져오기 -> 값이 없으면 에러발생(NoSuchElementException)
//		System.out.println(optInt2.getAsInt());

        System.out.println("optInt1 = " + optInt1);
        System.out.println("optInt2 = " + optInt2);
        System.out.println(optInt1.equals(optInt2)); // isPresent값이 일치하지 않음 -> false
    }
}

/*
출력결과
optStr = abcdef
optInt = 6
result1 = 123
result2 = -1
result3 = 너굴너굴
result4 = 456
true
false
0
optInt1 = OptionalInt[0]
optInt1 = OptionalInt.empty
false
*/

3) 스트림의 최종연산

(1) 반복 실행

  • 스트림의 모든 요소에 지정된 작업을 수행
  • forEach() - 병렬 스트림의 실행 순서를 보장하지 않음(성능은 더 빠름)
  • forEachOrdered() - 병렬 스트림인 경우에도 순서가 보장됨
void  forEach(Consumer<? super T> action)        // 병렬스트림인 경우순서가 보장되지 않음
void  forEachOrdered(Consumer<? super T> action) // 병렬스트림인 경우에도 순서가 보장됨
 
//.sequential -> 직렬스트림 (기본적으로 스트림은 직렬이라서 생략 가능)
IntStream.range(1, 10).sequential().forEach(System.out::print);        // 123456789
IntStream.range(1, 10).sequential().forEachOrdered(System.out::print); // 123456789

//.parallel() -> 병렬스트림 (순서 보장 안됨)
IntStream.range(1, 10).parallel().forEach(System.out::print);          // 683295714
IntStream.range(1, 10).parallel().forEachOrdered(System.out::print);   // 123456789

 

(2) 조건검사

  • allMatch() - 모든요소가 조건을 만족 하면 true
  • anyMatch() - 한 요소라도 조건을 만족 하면 true
  • noneMatch() - 모든 요소가 조건을 만족시키지 않으면 true
// predicate(조건식)을 받아서 불리언으로 반환
boolean allMatch (Predicate<? super T> predicate) // 모든 요소가 조건을 만족시키면 true
boolean anyMatch (Predicate<? super T> predicate) // 한 요소라도 조건을 만족시키면 true
boolean noneMatch(Predicate<? super T> predicate) // 모든 요소가 조건을 만족시키지 않으면 true

// ex) 100점 이하일 때 = 낙제자 -> 낙제가가 있는지 확인
boolean hasFailedStu = stuStream.anyMatch(s-> s.getTotalScore()<=100); // 낙제자가 있는지?

 

(3) 조건에 일치하는 요소 찾기

  • .filter()와 함께 사용
  • findFirst() - 순차 스트림에 사용 -> 첫 번째 요소를 반환
  • findAny() - 병렬 스트림에 사용 -> 아무거나 요소를 반환
Optional<T> findFirst()     // 첫 번째 요소를 반환.  순차 스트림에 사용
Optional<T> findAny()       // 아무거나 하나를 반환. 병렬 스트림에 사용

//조건을 만족하는 첫번째 요소를 반환 -> 직렬
Optional<Student> result = stuStream.filter(s-> s.getTotalScore() <= 100).findFirst();
//조건을 만족하는 요소를 아무거나 반환 -> 병렬
Optional<Student> result = parallelStream.filter(s-> s.getTotalScore() <= 100).findAny();

 

(4) 누적연산

  • 스트림의 요소를 하나씩 줄여가며 누적연산을(accumulator) 수행
  • reduce()의 파라미터 설명
    • identity - 초기값(핵심)
    • accumulator - 이전 연산결과와 스트림의 요소에 수행할 연산(핵심)
    • combiner - 병렬처리된 결과를 합치는데 사용할 연산(병렬 스트림일 때 사용)
Optional<T> reduce(BinaryOperator<T> accumulator) // identity 생략
T           reduce(T identity, BinaryOperator<T> accumulator)
U           reduce(U identity, BiFunction<U,T,U> accumulator, BinaryOperator<U> combiner)

// int reduce(int identity, IntBinaryOperator op) 
int count = intStream.reduce(0, (a,b) -> a + 1);                       // count() 
int sum   = intStream.reduce(0, (a,b) -> a + b);                       // sum()
int max   = intStream.reduce(Integer.MIN_VALUE,(a,b)-> a > b ? a : b); // max()
int min   = intStream.reduce(Integer.MAX_VALUE,(a,b)-> a < b ? a : b); // min()

// 위의 sum 스트림의 람다식을 풀어서 작성하면 아래처럼 작성할 수 있음
int a = identity; 
for(int b : stream)
     a = a + b;  // sum()

 

(5) 실습

import java.util.*;
import java.util.stream.*;

class Ex14_9 {
	public static void main(String[] args) {
		String[] strArr = {
			"Inheritance", "Java", "Lambda", "stream",
			"OptionalDouble", "IntStream", "count", "sum"
		};
		
		Stream.of(strArr)
		.parallel()	// 병렬로 처리 -> 순서가 계속 달라짐(forEachOrdered로 출력하면 순서가 유지)
		.forEach(System.out::println);
		
		System.out.println();
		
		// 문자열 길이가 0인게 하나도 없는지 확인
		boolean noEmptyStr = Stream.of(strArr).noneMatch(s->s.length()==0);
		
		// s로 시작하는 첫번째 요소 찾기(병렬 처리 후 findAny()로 찾으면 아무거나 먼저 찾음)
		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>을 Stream<Integer>으로 변환 (s) -> s.length()	
		Stream<Integer> intStream = Stream.of(strArr).map(String::length); 

		// Stream<String[]>을 IntStream으로 변환.
		// IntStream으로 변환할 때 mapToInt()를 사용(성능이 향상 됨)
		// 최종연산은 1번밖에 못써서 Stream을 많이 생성
		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());
	}
}

 

** 출처 : 남궁성의 정석코딩_자바의정석_기초편 유튜브