java 8 정리6

Java 정리 2021. 5. 21. 14:34

인프런 강의 8일차.

 - 더 자바, Java 8 (백기선 강사님)

 

1. 자바에서 지원하는 Concurrent 프로그래밍

 - 멀티프로세싱 (ProcessBuilder)

 - 멀티쓰레드

 

2. 자바 멀티쓰레드 프로그래밍

 - Thread / Runnable

 - Thread 상속

public static void main(String[] args){
  HelloThread helloThread = new HelloThread();
  helloThraed.start();
  System.out.println("hello = "+ Thread.currentThread().getName());
}

static class HelloThread extends Thread {
  @Override
  public void run() {
  System.out.println("World = "+ THread.currentThread().getName());
}

 - Runnable 구현 또는 람다

Thread thread = new Thread(() -> System.out.println("World = "+Thread.currentThread().getName()));
thread.start();
System.out.println("Hello = "+ Thread.currentThread().getName());

 - 쓰레드 주요 기능

   > 현재 쓰레드 멈춰두기(sleep) : 다른 쓰레드가 처리할 수 있도록 기회를 주지만, 그렇다고 락을 놔주진 않는다. (잘못하면 데드락이 걸릴 수 있다)

   > 다른 쓰레드 깨우기(interupt) : 다른 쓰레드를 깨워서 interruptedException을 발생 시킨다. 그 에러가 발생했을 때 할 일은 코딩하기 나름. 종료시킬 수도 있고, 계속 하던 일 할 수도 있고, 사용자 구현 가능

   > 다른 쓰레드 기다리기(join) : 다른 쓰레드가 끝날 때 까지 기다린다.

 

package me.whiteship.java8to11;

import ch.qos.logback.core.util.ExecutorServiceUtil;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class App {

    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        myThread.start();
        System.out.println("Hello");

        Thread thread = new Thread(() -> {
            while(true) {
                try {
                    Thread.sleep(3000L);
                } catch (InterruptedException e) {
                    //InterruptedException은 누군가 이 쓰레드를 깨울 때 동작한다.
                    System.out.println("Interrupted!!");
                    return;
                }
            }
        });
        thread.start();

        System.out.println("Thread : " + Thread.currentThread().getName());
        //Thread.sleep(3000L);
        //thread.interrupt(); 쓰레드 인터럽트 발생
        thread.join();  //위 쓰레드가 끝날 때 까지 기다린다.
        System.out.println(thread + " is finished");    //join()으로 인해 위 쓰레드 종료될 때 가지 기다린 후 출력됨

        //join으로 다른 쓰레드 기다리는 와중에 또 다른 쓰레드에서 인터럽트 발생 시 복잡도가 기하급수적으로 늘어난다
        //여러개의 쓰레드를 코딩으로 관리하는 것은 비효율적 & 어렵기 때문에 executor가 나왔다.
    }

    static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("Thread : " + Thread.currentThread().getName());
        }
    }
}

 

3. Executors

 - 고수준(High-Level) Cuncurency 프로그래밍

   > 쓰레드를 만들과 관리하는 작업을 애플리케이션에서 분리

   > 그런 기능을 Executors에게 위임

 

 - Executors가 하는 일

   > 쓰레드 만들기 : 애플리케이션이 사용할 쓰레드 풀을 만들어서 관리한다.

   > 쓰레드 관리 : 쓰레드 생명 주기를 관리한다

   > 작업 처리 및 실행 : 쓰레드로 실행할 작업을 제공할 수 있는 API를 제공한다.

 

 - 주요 인터페이스

   > Executor: execute(Runnable)

   > ExecutorService : Executor 상속 받은 인터페이스로, Callable도 실행할 수 있으며, Executor를 종료시키거나, 여러 Callable을 동시에 실행하는 등의 기능을 제공한다.

   > ScheduledExecutorService : ExecutorService를 상속 받은 인터페이스로 특정 시간 이후에 또는 주기적으로 작업을 실행할 수 있다.

 

 - ExecutorService로 작업 실행하기

ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.submit(() -> {
	System.out.println("Hello = " + Thread.currentThread().getName());
}

 

 - ExecutorService로 멈추기

executorService.shutdown();		//처리중인 작업 기다렸다가 종료
executorService.shutdownNow();	//당장 종료

 

 - Fork/Join 프레임워크

   > ExecutorService의 구현체로 손쉽게 멀티 프로세서를 활용할 수 있게끔 도와준다.

 

package me.whiteship.java8to11;

import java.sql.SQLOutput;
import java.util.concurrent.*;

public class App {

    public static void main(String[] args) throws InterruptedException {
        /*ExecutorService executorService = Executors.newSingleThreadExecutor();
        executorService.submit(() -> {  //executorService는 만들어서 실행하게 되면 다음 작업이 들어올 때 까지 계속 대기하므로 명시적으로 shutdown을 해주어야 한다.
            System.out.println("Thread : "+Thread.currentThread().getName());
        });*/

        //쓰레드는 2개지만 5개의 작업 실행
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        //2개의 쓰레드가 나눠서 5개의 작업을 수행한다.
        executorService.submit(getRunnable("Hello"));
        executorService.submit(getRunnable("World"));
        executorService.submit(getRunnable("dhpark"));
        executorService.submit(getRunnable("Java"));
        executorService.submit(getRunnable("Thread"));

        executorService.shutdown();     //graceful shutdown이라고 한다. (현재 진행중인 작업을 전부 마치고 종료)
        //executorService.shutdownNow();  //현재 돌고 있는 쓰레드 종료여부와 상관없이 바로 종료*/

        ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
        scheduledExecutorService.schedule(getRunnable("Hello"), 3, TimeUnit.SECONDS);   //3초 정도 딜레이 후 getRunnable 실행
        scheduledExecutorService.scheduleAtFixedRate(getRunnable("fixedHello"), 1, 2. TimeUnit.SECONDS);    //1초만 기다렸다가 2초에 한번씩 출력

    }

    private static Runnable getRunnable(String message){
        return () -> System.out.println(message + Thread.currentThread().getName());
    }
}

 

 

'Java 정리' 카테고리의 다른 글

java 8 정리8  (0) 2021.06.02
java 8 정리7  (0) 2021.05.26
java 8 정리5  (0) 2021.05.20
java 8 정리4  (0) 2021.05.19
java 8 정리3  (0) 2021.05.17

java 8 정리5

Java 정리 2021. 5. 20. 15:40

인프런 강의 7일차.

 - 더 자바, Java 8 (백기선 강사님)

 

1. CompletableFuture 1

 - 자바에서 비동기(Asynchronous) 프로그래밍을 가능하게하는 인터페이스.

   > Future를 사용해서도 어느정도 가능했지만 하기 힘든 일들이 많았다.

 

 - Future로는 하기 어렵던 작업들

   > Future를 외부에서 완료 시킬 수 없다. 취소하거나, get()에 타임아웃을 설정할 수는 있다.

   > 블로킹 코드(get())을 사용하지 않고서는 작업이 끝났을 때 콜백을 실행할 수 없다.

   > 여러 Future를 조합할 수 없다. ex) Event 정보 가져온 다음 Event에 참석하는 회원의 목록 가져오기

   > 예외 처리용 API를 제공하지 않는다.

 

 - CompletableFuture

   > Implements Future

   > Impletments CompletableFuture

 

 - 비동기로 작업 실행하기

   > 리턴값이 없는 경우 : runAsync()

   > 리턴값이 있는 경우 : supplySync()

   > 원하는 Executor(쓰레드풀)를 사용해서 실행할 수도 있다.(기본은 ForkJoinPool.commonPool())

 

 - 콜백 제공하기

   > thenApply(Function) : 리턴값을 받아서 다른 값으로 바꾸는 콜백

   > thenAccept(Consumer) : 리턴값을 또 다른 작업으로 처리하는 콜백 (리턴없이)

   > thenRun(Runnable) : 리턴값을 받아 다른 작업을 처리하는 콜백

   > 콜백 자체를 또 다른 쓰레드에서 실행할 수 있다.

 

 - 조합하기

   > thenCompose() : 두 작업이 서로 이어서 실행하도록 조합

   > thenCombine() : 두 작업을 독립적으로 실행하고, 둘 다 종료했을 때 콜백 실행

   > allOf() : 여러 작업을 모두 실행하고 모든 작업 결과에 콜백 실행

   > anyOf() : 여러 작업 중 가장 빨리 끝난 하나의 결과에 콜백 실행

 

 - 예외처리

   > exceptionally(Function) : exception 발생 시 처리하는 코드

   > handle(BiFunction) : handle은 예외가 발생했을 때, 발생하지 않았을 때 둘 다 사용가능하다

 

package me.whiteship.java8to11;

import ch.qos.logback.core.util.ExecutorServiceUtil;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;
import java.util.stream.Collectors;

public class App {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        Future<String> future = executorService.submit(() -> "hello");

        future.get();   //get이 블로킹 코드이니 get 이후에 콜백 로직이 들어와야한다.

        //Case1. 명시적으로 dhpark 값 세팅해서 선언
        CompletableFuture<String> completableFuture = new CompletableFuture<>();
        completableFuture.complete("dhpark");   //completablefuture의 기본 값을 dhpark으로 정의
        System.out.println(completableFuture.get());    //dhpark 출력

        //Case2. static factory method 사용
        CompletableFuture<String> completableFuture1 = CompletableFuture.completedFuture("dhpark");
        System.out.println(completableFuture1.get());    //dhpark 출력

        //리턴이 없는 작업은 runAsync 사용
        CompletableFuture<Void> completableFuture2 = CompletableFuture.runAsync(() -> {
            System.out.println("Hello " + Thread.currentThread().getName());        //future만 정의했기 때문에 수행되지 않고, join()이나 get()을 해야 동작한다.
        });
        System.out.println(completableFuture2.get());

        //리턴이 있는 경우 supplyAsync 사용
        CompletableFuture<String> completableFuture3 = CompletableFuture.supplyAsync(() -> {
            System.out.println("Hello " + Thread.currentThread().getName());
            return "Hello";
        });
        System.out.println(completableFuture3.get());

        //위의 예제들은 Future로도 구현 가능한 기능이라고 봐도 무방하다.
        //CompletableFuture는 추가로 callback 구현이 가능.
        //리턴이 있는 supplyAsync에 callback구현(thenApply)
        CompletableFuture<String> completableFuture4 = CompletableFuture.supplyAsync(() -> {
            System.out.println("Hello " + Thread.currentThread().getName());
            return "Hello";
        }).thenApply((s) -> {       //java5때의 Future로는 get 호출하기 이전에 콜백 정의가 불가능했다.
            System.out.println(Thread.currentThread().getName());
            return s.toUpperCase();
        });
        System.out.println(completableFuture4.get());       //get을 호출하지 않으면 아무 일이 일어나지 않는건 동일함

        //리턴이 없는 콜백의 경우 thenAccept
        CompletableFuture<Void> completableFuture5 = CompletableFuture.supplyAsync(() -> {
            System.out.println("Hello " + Thread.currentThread().getName());
            return "Hello";
        }).thenAccept((s) -> {       //java5때의 Future로는 get 호출하기 이전에 콜백 정의가 불가능했다.
            System.out.println(Thread.currentThread().getName());
            System.out.println(s.toUpperCase());
        });
        completableFuture5.get();       //get을 호출하지 않으면 아무 일이 일어나지 않는건 동일함

        //전달되는 파라미터(리턴) 없이 동작을 하기만 하면 되는 것은 thenRun
        //별다른 Executor 정의가 없다면 ForkJoinPool.commonPool을 사용하게 된다.
        //ForkJoinPool : Java7에 도입된 기능, Executor를 구현한 구현체. 작업을 DeQueue를 사용해서 자기 쓰레드가 할일이 없으면 할일을 가져와서 처리하는 방식
        //자기가 파생시키는 subTask들을 다른 쓰레드에 분산시켜서 작업을 처리하고, 결과를 모아서 최종 결과값을 도출해냄
        //원할 경우 Executor를 새로 정의하는 것도 가능하다.
        //새로 정의한 경우 supplyAsync의 2번째 인자로 전달해야한다.
        ExecutorService executorService1 = Executors.newFixedThreadPool(4);
        CompletableFuture<Void> completableFuture6 = CompletableFuture.supplyAsync(() -> {
            System.out.println("Hello " + Thread.currentThread().getName());
            return "Hello";
        }, executorService1).thenRun(() -> {    //새로 정의한 executorService를 사용해서 쓰레드 호출
            //ExecutorService를 추가 하지 않은 경우 Hello ForkJoinPool.commonPool-worker 출력
            //ExecutorService를 추가한 경우 Hello pool-1-thread-1 출력
            //java5때의 Future로는 get 호출하기 이전에 콜백 정의가 불가능했다.
            System.out.println(Thread.currentThread().getName());
        });
        //}, executorService1);  //callback에서도 정의된 executor를 사용할 수 있다. 이 때 callback은 @async를 사용해야 한다. thenRunAsync, thenApplyAsync, ...
        //다만 supplyAsync때 사용된 쓰레드와 callback에서 사용된 쓰레드가 다를 수 있다!

        //쓰레드 조합해서 사용하기
        //thenCompose

        CompletableFuture<String> hello = CompletableFuture.supplyAsync(() -> {
            System.out.println("Hello " + Thread.currentThread().getName());
            return "Hello";
        });

        //Case1. 2개의 쓰레드 작업 조합
        CompletableFuture<String> completableFuture7 = hello.thenCompose(App::getWorld);
        System.out.println(completableFuture7.get());       //Hello World 출력

        CompletableFuture<String> world = CompletableFuture.supplyAsync(() -> {
            System.out.println("World" + Thread.currentThread().getName());
            return "World";
        });

        //Case2. 입력 값은 2개, 결과 값은 1개로 처리
        CompletableFuture<String> completableFuture8 = hello.thenCombine(world, (h, w) -> h + " " + w); //BiFunction에 해당하는 람다 실행
        System.out.println(completableFuture8.get());   //hello world 출력

        //Case3. 2개 이상의 태스크를 합쳐서 한번에 실행
        //인자로 넘어가는 태스크의 결과가 동일한 타입인지 보장할 수 없고, 태스크가 전부 성공한다는 보장도 없기에 결과가 무의미하다.
        CompletableFuture<Void> voidCompletableFuture = CompletableFuture.allOf(hello, world)
                .thenAccept(System.out::println);       //thenAccept가 실행되어서 null이 출력됨
        System.out.println(voidCompletableFuture.get());        //결과가 null이다.

        //Case3의 결과가 null이기에 해당 결과를 출력시킬 수 있는 방법은 Collection으로 모아서 한데 처리해야한다.
        List<CompletableFuture<String>> completableFutures = Arrays.asList(hello, world);
        CompletableFuture[] futuresArray = completableFutures.toArray(new CompletableFuture[completableFutures.size()]);
        CompletableFuture<List<String>> listCompletableFuture = CompletableFuture.allOf(futuresArray)
                .thenApply(v -> {   //결과인 v는 무의미하고, return이 중요하다.
                    return completableFutures.stream()
                            //thenApply가 호출되는 시점은 futuresArray의 모든 작업이 끝난 상태이다!
                            //CompletableFuture.get()을 써도 되나 get은 checkedException이 발생하므로 exception까지 정의해줘야 가능함.
                            .map(CompletableFuture::join)       //join은 uncheckedException이 발생
                            .collect(Collectors.toList());

                });
        listCompletableFuture.get().forEach(System.out::println);       //Hello \n World 출력됨

        //Case4. 아무거나 빨리 끝나는거 결과 하나 받아서 실행
        CompletableFuture<Void> voidCompletableFuture1 = CompletableFuture.anyOf(hello, world).thenAccept(System.out::println);
        voidCompletableFuture1.get();    //hello랑 world중에 먼저 끝나는 작업 출력

        //Case5. Exception 처리
        boolean throwError = true;
        CompletableFuture<String> exceptionHello = CompletableFuture.supplyAsync(() -> {
            if (throwError) {
                throw new IllegalStateException();
            }

            System.out.println("Hello " + Thread.currentThread().getName());
            return "Hello";
        }).exceptionally(ex -> {    //에러 발생 시 수행하는 코드
            System.out.println(ex);
            return "Error!";
        });

        //Case6. handle은 예외가 발생했을 때, 발생하지 않았을 때 둘 다 사용가능하다 (BiFunction)
        CompletableFuture<String> exceptionHello1 = CompletableFuture.supplyAsync(() -> {
            if (throwError) {
                throw new IllegalStateException();
            }

            System.out.println("Hello " + Thread.currentThread().getName());
            return "Hello";
        }).handle((result, ex) -> {
            if(ex != null){
                System.out.println(ex);
                return "Error!";
            }
            return result;      //에러가 없으면 결과 리턴
        });

    }

    private static CompletableFuture<String> getWorld(String message) {
        return CompletableFuture.supplyAsync(() -> {
            System.out.println("World " + Thread.currentThread().getName());
            return message + "World";
        });
    }
}

'Java 정리' 카테고리의 다른 글

java 8 정리7  (0) 2021.05.26
java 8 정리6  (0) 2021.05.21
java 8 정리4  (0) 2021.05.19
java 8 정리3  (0) 2021.05.17
java 8 정리2  (0) 2021.05.13

java 8 정리4

Java 정리 2021. 5. 19. 18:55

인프런 강의 6일차.

 - 더 자바, Java 8 (백기선 강사님)

 

1. Optional

 - 람다가 하는 일이 기존 메소드 또는 생성자를 호출하는 거라면, 메소드 레퍼런스를 사용해서 매우 간결하게 표현가능

 - 오직 값 한 개가 들어있을 수도, 없을 수도 있는 컨테이너.

 

 - 자바 프로그래밍에서 NullPointException을 종종 보는 이유

   > null을 리턴하니까!

   > && 체크를 깜빡했으니까!

 

 - 메소드에서 작업 중 특별한 상황에서 값을 제대로 리턴할 수 없는 경우 선택할 수 있는 방법

   > 예외를 던진다. (스택 트레이스를 찍게되므로 비싸다..)

   > null을 리턴한다. (비용 문제가 없지만 그 코드를 사용하는 클라이언트가 주의해야한다)

   > Optional을 리턴한다. (클라이언트 코드에게 명시적으로 빈 값일 수 있다는 걸 알려주고, 빈 값인 경우에 대한 처리를 강제한다.)

 

2. Optional 주의사항

 - 리턴 값으로만 쓰기를 권장한다. (메소드 매개변수 타입, 맵의 키 타입, 인스턴스 필드 타입으로 쓰지 말자.)

   > 맵의 key는 비어있을 수가 없다라는 것의 맵의 기본 요건!(key가 빈 값이라는 것은 설계의 문제..)

 - Optional을 리턴하는 메소드에서 null을 리턴하지 말자

 - 프리미티브 타입용 Optional은 따로 있다. OptionalInt, OptionalLong, ...

   > Optional.of(10) 처럼 사용이 가능하긴하나, boxing, unboxing이 일어나는 과정에서 cost가 소모되기 때문에 OptionalInt 를 사용하는 것이 바람직하다.

 - Collection, Map, Stream, Array, Optional은  Optional로 감싸지 말것!

package me.whiteship.java8to11;

import java.util.Optional;

public class OnlineClass {

    private Integer id;

    private String title;

    private boolean closed;

    private Progress progress;

    public OnlineClass(Integer id, String title, boolean closed) {
        this.id = id;
        this.title = title;
        this.closed = closed;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public boolean isClosed() {
        return closed;
    }

    public void setClosed(boolean closed) {
        this.closed = closed;
    }

    public Optional<Progress> getProgress() {
        //Optional.of(param)의 경우 param이 null이 아니어야 한다. null일 경우 NullException이 발생한다.
        //Optional.ofNullable(param)의 경우 param이 null이면 empty로 취급해준다.
        //리턴타입이 Optional인데 return null같은 코드는 사용하지말자.. 정 리턴할게 없다면 Optional.empty를 사용1
        return Optional.ofNullable(progress);       //Optional 은 return type으로 쓰는 것이 권장사항.
    }

    /*
    Optional을 파라미터에 선언하는 것은 문법적으로 문제가 없으나 쓸 수 없다!!
    public void setProgress(Optional<Progress> progress) {
        //만약 Optional<Progress> progess 처럼 메소드 매개변수 타입으로 사용하고자 한다면 해당 함수 내에서 체크를 해줘야한다.
        progress.ifPresent(p -> this.progress = p); //해당 메소드를 호출할 때 파라미터가 null이 올 수 있기 때문에 위험하다.
        //this.progress = progress;
    }
    */
    public void setProgress(Progress progress) {
        this.progress = progress;
    }
}

 

3. Optional 만들기

 - Optional.of()

 - Optional.ofNullable()

 - Optional.empty()

 

4. Optional에 값이 있는지 없는지 확인

 - ifPresent()

 - isEmpty() (Java 11부터 제공)

 

5. Optional에 있는 값 가져오기

 - get()

 - 만약에 비어있는 Optional에서 무언가를 꺼낸다면?

 

6. Optional에 값이 있는 경우에 그 값을 가지고 ~~를 하라.

 - ifPresent(Consumer)

 

7. Optional에 값이 있으면 가져오고 없는 경우에 ~~를 리턴하라.

 - orElse(T)

    > 이미 만들어져 있는 인스턴스를 참고할 때는 orElse가 적합

 

8. Optional에 값이 있으면 가져오고 없는 경우에 ~~를 하라.

 - orElseGet(Supplier)

    > 동적으로 추가 작업을 해야하는 상황이라면 orElse 대신 orElseGet이 적합하다.

 

9. Optional에 값이 있으면 가져오고 없는 경우 에러를 던져라

 - orElseThrow()

    > orElseThrow(IllegralStateException::new) 처럼 발생시킬 익셉션을 정의할 수 있다.

 

10. Optional에 들어있는 값 걸러내기

 - Optional filter(Predicate)

    > 값이 있다는 가정 하게 동작하는 것.

 

11. Optional에 들어있는 값 변환하기

 - Optional map(Function)
 - Optional flatMap(Function): Optional 안에 들어있는 인스턴스가 Optional인 경우에 사용하면 편리하다.

 

package me.whiteship.java8to11;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class App {

    public static void main(String[] args) {
        List<OnlineClass> springClasses = new ArrayList<>();
        springClasses.add(new OnlineClass(1, "spring boot", true));
        springClasses.add(new OnlineClass(2, "spring data jpa", true));
        springClasses.add(new OnlineClass(3, "spring mvc", true));
        springClasses.add(new OnlineClass(4, "spring core", true));
        springClasses.add(new OnlineClass(5, "rest api development", false));

        OnlineClass spring_boot = new OnlineClass(1, "spring boot", true);

        /* java8 이전의 null처리 방식
        Progress progress = spring_boot.getProgress();      //getProgress의 return은 null인 상태!
        if(progress != null){
            System.out.println(studyDuration);
        }
         */

        //spring으로 시작하는 oc 중 가장 첫번째 값 리턴 시 optional로 변수가 선언됨
        Optional<OnlineClass> optionalOnlineClass = springClasses.stream()
                .filter(oc -> oc.getTitle().startsWith("spring"))
                .findFirst();

        //spring에 값이 있는지 없는지 검사
        boolean present = optionalOnlineClass.isPresent();
        System.out.println(present);

        //jpa로 시작하는 수업 중 첫번째 값 가져오기
        Optional<OnlineClass> optionalOnlineClass1 = springClasses.stream()
            .filter(oc -> oc.getTitle().startsWith("jpa"))
            .findFirst();

        boolean presentJpa = optionalOnlineClass1.isPresent();
        System.out.println(presentJpa);

        //값이 있으면 타이틀 출력
        optionalOnlineClass.ifPresent(oc -> System.out.println(oc.getTitle()));

        //jpa로 시작하는 수업이 있으면 가져오고, 없을 경우 createNewJpaClass를 새로 수행하여 클래스 생성
        //다만 optional에 값이 있더라도 createNewJpaClass메소드는 항상 실행된다(return을 하지 않을 뿐!)
        //이러한 현상을 방지하고자 orElseGet(Supplier)를 사용한다.orElse는 Supplier가 아닌 orElse(onlineClass)이다
        OnlineClass onlineClass = optionalOnlineClass.orElse(createNewJpaClass());
        System.out.println(onlineClass.getTitle());


        OnlineClass onlineClass1 = optionalOnlineClass1.orElseGet(() -> createNewJpaClass());//람다표현식 사용
//        optionalOnlineClass1.orElseGet(App::createNewJpaClass);     //메소드 레퍼런스 사용
        optionalOnlineClass1.orElseThrow(); //값이 없으면 NoSuchElementException인 RuntimeException을 던진다.
        optionalOnlineClass1.orElseThrow(IllegalStateException::new);   //발생시킬 exception을 정의할 수 있다.

        //spring으로 시작하는 클래스 중 closed가 안된 대상을 필터 : true값이 없으므로 empty가 리턴된다.
        Optional<OnlineClass> onlineClass2 = optionalOnlineClass.filter(oc -> !oc.isClosed());
        System.out.println(onlineClass2.isEmpty()); //비어있는 optional이 나오게 된다.

        //spring으로 시작하는 클래스의 id를 map으로 만듦
        Optional<Integer> integer = optionalOnlineClass.map(OnlineClass::getId);
        System.out.println(integer.isPresent());    //map 데이터가 있는지 확인

        //만약 map의 리턴 타입이 Optional이면 좀 복잡해진다.
        //map의 리턴타입은 Optional<Optional<Progress>> 임.
        Optional<Optional<Progress>> progress = optionalOnlineClass.map(OnlineClass::getProgress);  //progress의 리턴은 Optional
        Optional<Progress> progress1 = progress.orElseThrow();  //progress에서 한번 더 꺼내야만 실제 확인이 가능한 변수가 됨

        //위와 같은 형태를 줄이고자 할 때 사용하는 것이 flatMap
        //map과 다르게 리턴 타입이 Optional<Progress> 임.
        //orElseThrow를 하지는 않지만 구현이 비슷하게 된다고 알아두면 됨.(flatMap은 없을 때 optional.empty임)
        Optional<Progress> progress2 = optionalOnlineClass.flatMap(OnlineClass::getProgress);

        //Stream에서 쓰는 map은 Input:Output이 1:1 매핑, flatMap은 1:n 매핑이니 헷갈리지 말자!
        //get 보다는 가급적 다른 메소드를 사용하자.
    }

    private static OnlineClass createNewJpaClass() {
        System.out.println("creating new online class");
        return new OnlineClass(10, "New Class", false);
    }
}

 

'Java 정리' 카테고리의 다른 글

java 8 정리6  (0) 2021.05.21
java 8 정리5  (0) 2021.05.20
java 8 정리3  (0) 2021.05.17
java 8 정리2  (0) 2021.05.13
java 8 정리1  (0) 2021.05.12