본문 바로가기

백엔드/자바

자바 Stream과 Optional

Stream

  • 데이터를 담고 있는 것은 컬렉션. 스트림은 이러한 컬렉션에 들어있는 데이터들을 가지고 연산을 실행하는 것.
  • 컬렉션에 담겨있는 데이터값이 변경되는 것은 아니다.
  • 중개 operation과 종료 operation로 나뉜다.
  • 중개 operation은 LAZY 하다.

중개 operation은 LAZY 하기 때문에 종료operation이 들어와야 스트림의 연산이 끝난다. 그래서 여러개의 중개 연산과 하나의 종료 연산으로 구성되어있다.

List<String> names = new ArrayList<>();
names.add("hello");
names.add("man");
names.add("iii");
names.add("aaa");
names.add("bbb");
// 중개 연산. 연산이 끝나지 않았으므로 리턴값은 스트링
Stream<String> stringStream = names.stream().map(String::toUpperCase);
// 종료 연산
List<String> res = stringStream.collect(Collectors.toList());

스트림 연산을 통해 names 컬렉션의 데이터들 값 자체가 변경되는 것은 아니기때문에 collect 함수를 통해서 따로 res라는 변수에 결과값을 담아준다.

병렬 처리

스트림을 사용하면 병렬처리가 편해진다. parallelStream을 사용하면된다. 간단하게나마 위의 예제에 그대로 적용해봤다.

List<String> collect1 = names.stream().map(s -> {
    System.out.println(s);
    return s.toUpperCase(Locale.ROOT);
}).collect(Collectors.toList());

List<String> collect1 = names.parallelStream().map(s -> {
    System.out.println(s);
    return s.toUpperCase(Locale.ROOT);
}).collect(Collectors.toList());

실행결과

병렬적으로 처리하기 때문에 parallelStream을 사용한 연산은 컬렉션에 담긴 데이터의 순서가 보장되지 않는 것을 확인할 수 있었다. 쓰레드 이름과 함께 로그를 남겨봤는데 다음과 같았다.

 

활용해보기

List<MyClass> classes = new ArrayList<MyClass>();
classes.add(new MyClass(1, "class1", true));
classes.add(new MyClass(2, "class3", true));
classes.add(new MyClass(4, "class14", false));
classes.add(new MyClass(8, "class15", true));
classes.add(new MyClass(6, "class16", true));
classes.add(new MyClass(9, "class17", false));
Optional<Integer> reduce = classes.stream().filter(s -> s.isClosed).map(s -> s.id).reduce((acc, a) -> acc + a);
System.out.println(reduce.get());

map filter reduce를 사용한 stream 실습 코드 

flatMap

List<List<MyClass>> classList = new ArrayList<>();
classList.add(classes);
classList.add(classes2);
classList.stream().flatMap(Collection::stream).forEach(System.out::println);

리스트를 가지고 있는 리스트의 경우 flat을 사용해서 하나의 리스트로 펼치는 것도 가능하다. (2차원 -> 1차원)

 

 

Optional

등장배경

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

        Object obj = App.getObj();
        if(obj != null) {

        }
    }

    private static Object getObj() {
        return null;
    }
}

Optional 등장 이전에 자바에서는 null 체크는 흔히 볼 수 있는 코드였다. 하지만 사람이기에 저런 null 체크는 깜빡하고 빼먹어서 에러가 날 가능성이 다분했다. 그래서 Optional은 매번 null 체크를 하지않기 위해 혹은 특정 함수에서 리턴값이 null이 리턴되는 상황 등을 해결하기 위해 Optional 이 등장했다.

 

    private static Optional<App> getObj() {
        App mayBeNull = null;
        return Optional.ofNullable(mayBeNull);
    }

만약 null 값이 될 수 있는 변수일 경우에는 Optional로 한번 감싸서 전달하는 것이다. Optional을 사용할 때에는 예시처럼 반환값으로만 사용해야한다. 나머지의 경우에는 Optional을 사용하는 것이 무의미해질 수도 있다.

주의점 간략 정리

  • Optional은 반환값으로만 사용하자
  • Optional로 감싸는 대상은 null인지 아닌지 체크할 수 없는 객체를 대상으로 할 것

 

사용해보기

optional 만드는 함수 3개

  • of() : null이 아닌 객체를 담고있는 Optional 생성
  • ofNullable() : null이든 아니든 다 받아서 Optional 생성
  • empty() : null 을 담고있는 Optional 생성

 

  • get() ; optional로 감싼 대상 꺼내오기.
  • ifPresent(() -> {}) : optional로 감싼 대상이 null이 아니면 전달받은 콜백함수 실행
  • isPresent() : optional로 감싼 대상이 null인지 체크 (사용 권장 x)
  • orElse(), orElseGet() : 값이 있으면 가져오고 없으면 ~ 를 리턴
  • orElseThrow() : null일 때 대안이 없으면 에러 던짐

 

orElse, orElseGet 예시

public static void main(String[] args) {
        Optional<App> obj = App.getObj();
        App app = obj.orElse(createNew());
}

private static App createNew() {
        return new App();
}

private static Optional<App> getObj() {
        App mayBeNull = null;
        return Optional.ofNullable(mayBeNull);
}

orElse는 createNew() 함수가 항상 실행된다. 그래서 만약 obj에 값이 null이 들어있으면 createNew의 결과값을 반환하고 null이 아니면 해당 값을 그대로 반환한다. null이든 아니든 createNew 가 실행되어서 조금 비효율적인데 이럴때는 orElseGet을 쓰면 된다. 만약 위의 예제에서 orElseGet을 썼다면 Optional의 값이 null 이 아닐때는 createNew가 실행되지 않을 것이다.

App app = obj.orElseGet(() -> createNew());
App app2 = obj.orElseGet(App::createNew);

orElse : 이미 만들어져있는 값일때 사용
orElseGet : 동적으로 함수 호출을 통해 값을 만들어야할 때 사용

 

Optional의 map과 filter

Optional<App> obj = App.getObj();
Optional<String> s1 = obj.map(s -> s.toString());

Optional에서 map과 filter를 바로 사용할 수 있도록 제공해준다. 두 메소드의 결과값은 당연히 Optional이다. 만약 map 함수의 결과가 Optional이라면 Optional 안에 Optional이 중첩되는 상황이 발생할 수 있다. 그럴때 사용하는 것이 flatMap이다.

    public static void main(String[] args) {
        Optional<App> obj = App.getObj();
        
        //map
        Optional<Optional<App>> app = obj.map(App::makeApp);
        Optional<App> app2 = app.orElse(Optional.empty());
        
        //flatMap
        Optional<App> appWithFlatMap = obj.flatMap(App::makeApp);
    }
    public Optional<App> makeApp() {
        return Optional.of(new App());
    }

    private static Optional<App> getObj() {
        App mayBeNull = null;
        return Optional.ofNullable(mayBeNull);
    }

 

 

마무리

한번쯤 정리가 필요했다 싶었던 내용들이었는데 강의와 함께 여러 레퍼런스를 찾아보며 정리하니 도움이 많이 된 시간이었다.

 

 

관련글

https://www.inflearn.com/course/the-java-java8/dashboard

 

더 자바, Java 8 - 인프런 | 강의

자바 8에 추가된 기능들은 자바가 제공하는 API는 물론이고 스프링 같은 제 3의 라이브러리 및 프레임워크에서도 널리 사용되고 있습니다. 이 시대의 자바 개발자라면 반드시 알아야 합니다. 이

www.inflearn.com

위 강의를 공부하며 정리한 글입니다.

 

참고하면 좋은 레퍼런스

https://www.daleseo.com/java8-optional-after/

https://www.daleseo.com/java8-optional-effective/

https://jeong-pro.tistory.com/165

https://futurecreator.github.io/2018/08/26/java-8-streams/

 

'백엔드 > 자바' 카테고리의 다른 글

자바 Executor  (0) 2021.10.01
자바8의 인터페이스  (0) 2021.09.26
Ecplise failed while installing Java 1.8 해결  (0) 2021.06.21
스프링 mysql 연동  (0) 2021.04.04
자바 thread 간단 정리  (0) 2021.01.29