혼자서 하는 개발보다 많은 사람들과 협력하여 개발하는 일이 많다. 또한, 실제로 기능을 개발하는 일보다 유지보수하는 일이 많다고 한다. 그렇기때문에, 코드는 다른 사람이 보고 이해하기 쉽도록 할 필요가 있다.

 

 

개발, 강의를 보며 더 깔끔하게 코드를 작성할 수 있는 요령을 발견한다면 이에 대하여 정리할 예정이다. 

 

 

 

Tell Don`t Ask

다른 객체에 데이터를 요청해서 변경하고 저장하라고 하지말고 기능을 실행하라! 즉,데이터를 잘 알고 있는 객체에게 기능을 수행하라고 하라. 이렇게되면, Encapsulation이 유지되어 변경에 영향을 안 받게 된다.

예를들어 보자. 넷플릭스에서 회원의 구독이 만료되어있는지, 만료되지 않았는지 확인하는 코드로 예시를 들어보겠다.

if(member.getExpiredDate().getTime() < System.currentTimeMillis)

클라이언트에서 위처럼 코드를 작성하면 member객체에 저장되어 있는 만료날짜를 가져와 현재 시간보다 적으면 만료되지 않았다는 것을 알 수 있다.

 

하지만, 이보다는 아래와 같은 방식이 역할과 책임을 잘 분리하고 유지보수하기 수월하다.

if(member.isExpired())

위처럼 설계해야 하는 이유는 두가지가 있다. 

1. 만료가 되었는지 되지 않았는지는 Member 객체에 대한 책임이고, 수행해야할 역할이다. 

2. 유지보수하기 수월하다.

 

여기서 유지보수하기가 수월하다는 이유가 와닿지 않을 수도 있다. 그렇다면, 가정해보자. 처음 설계는 밀리세컨드로 만료날짜를 확인했다. 하지만, 후에 밀리세컨드가 아닌 세컨드로 만료날짜를 확인하기로 변경되었다면 클라이언트 코드 중 어디서 만료날짜를 확인했는지부터 찾아야할 것이다. 하지만, 만료날짜를 확인하는 클라이언트 코드가 많은 경우라면? 코드를 다 찾고 수정하기 어렵다는 것을 알 수 있다.

 

하지만, 2번째와 같이 코드가 되어있다면 Member 클래스에서 isExpired() 메소드 안에 코드만 한번 수정해주면 끝난다. 이처럼 초반 설계는 중요하다..!

 

 

 

 

 

 

 

 

'JAVA' 카테고리의 다른 글

절차지향 vs 객체지향  (0) 2022.11.04
[Java 8] Metaspace  (0) 2022.10.18
[JAVA 8] ParallelSort  (0) 2022.10.15
[Java 8] CompletableFuture  (0) 2022.10.13
[Java 8] Date와 Time  (0) 2022.10.12

절차지향 vs 객체지향

절차지향                 /                 객체지향

절차지향

하나의 데이터로 모든 프로시저가 공유하고 있기때문에 데이터를 변경할 경우 많은 프로시저에 영향을 끼친다. 이때문에 데이터 변경이 어렵다.ㅜ웛
  • 장점
    • 유지보수를 신경쓰지 않아도되기 때문에, 설계부터 구현까지 객체지향에 비해 시간이 오래걸리지 않는다.
  • 단점
    • 수정이 어렵기때문에 유지보수가 필요한 대형 프로젝트에서 사용하기에 부적합하다.

 

객체지향

데이터와 코드가 객체로 캡슐화 되어있다. 데이터와 코드가 캡슐화되어있는 객체끼리 서로 상호작용한다.
  • 장점
    • 절차지향과 다르게 데이터와 코드의 변경은 외부에 영향을 미치지않는다.
    • 외부에 노출된 인터페이스만 변경되지 않는다면 프로시저를 실행하는데 필요한 만큼의 데이터만 가진다.
    • 비교적 유지보수가 수월하기때문에 대형 프로젝트에서 사용하기에 적합하다.
    • 객체끼리 서로 상호작용하기 때문에, 코드의 재사용성을 높일 수 있다.
  • 단점
    • 절차지향에 비해 속도가 느리다.
    • 설계에 시간을 사용한만큼 유지보수가 수월하기때문에, 설계단계에서 시간이 많이 소모된다.

 

 

 

'JAVA' 카테고리의 다른 글

Clean Code  (0) 2022.11.04
[Java 8] Metaspace  (0) 2022.10.18
[JAVA 8] ParallelSort  (0) 2022.10.15
[Java 8] CompletableFuture  (0) 2022.10.13
[Java 8] Date와 Time  (0) 2022.10.12

JVM의 여러 메모리 영역 중에 PermGen 메모리 영역이 없어지고 Metaspace 영역이 생겼다.

 

PermGen

  • permanent generation(eden generation, old generation와 비슷한 영역), 클래스 메타데이터를 담는 곳.
  • Heap 영역에 속함
  • 기본값으로 제한된 크기를 가지고 있음
  • -XX:PermSize=N, PermGen 초기 사이즈 설정
  • -XX:MaxPermSize=N, PermGen 최대 사이즈 설정

 

Metaspace

  • 클래스 메타데이터를 담는 곳
  • Heap 영역(Eden, Old)이 아니라, Native 메모리 영역이다.
  • 기본값으로 제한된 크기를 가지고 있지 않다. (필요한 만큼 계속 늘어난다.)
  • Java 8 부터는 PermGen 관련 java 옵션은 무시한다.
  • -XX:MetaspaceSize=N, Metaspace 초기 사이즈 설정.
  • -XX:MaxMetaspaceSize=N, Metaspace 최대 사이즈 설정.

 

참고

 

'JAVA' 카테고리의 다른 글

Clean Code  (0) 2022.11.04
절차지향 vs 객체지향  (0) 2022.11.04
[JAVA 8] ParallelSort  (0) 2022.10.15
[Java 8] CompletableFuture  (0) 2022.10.13
[Java 8] Date와 Time  (0) 2022.10.12

Arrays.parallelSort()

  • Fork/Join 프레임워크를 사용해서 배열을 병렬로 정렬하는 기능을 제공한다.

 

병렬 정렬 알고리즘

  • 배열을 둘로 계속 쪼갠다.
  • 합치면서 정렬한다.

 

이전에 parallelStream을 설명할 때 말한 적이 있다. 상황에 따라서 병렬로 하는게 빠를 수도 있고, 더 느릴 수도 있다고 말이다. 실제로 그렇게 되는지 확인해보자.

 

sort()와 parallelSort() 비교

int size = 10000000;
int[] numbers = new int[size];
Random random = new Random();
IntStream.range(0, size).forEach(i -> numbers[i] = random.nextInt());

long start = System.nanoTime();
Arrays.sort(numbers);
System.out.println("  serial sorting took " + (System.nanoTime() - start));

IntStream.range(0, size).forEach(i -> numbers[i] = random.nextInt());
start = System.nanoTime();
Arrays.parallelSort(numbers);
System.out.println("parallel sorting took " + (System.nanoTime() - start));
위의 코드에서 size만 변경해보며 시간이 얼마나 걸리는지 차이를 알아보자.

 

size가 1,000인 경우

  • serial sorting took 635,363
  • parallel sorting took 2,320,564

 

size가 10,000인 경우

  • serial sorting took 4,114,773
  • parallel sorting took 5,344,624

 

size가 100,000인 경우

  • serial sorting took 39,835,655
  • parallel sorting took 42,495,858

 

size가 1,000,000인 경우 

  • serial sorting took 967,621,466
  • parallel sorting took 333,204,237
  • 알고리즘 효율성은 같다. 시간복잡도 : O(nlogN), 공간복잡도 : O(n)

 

size가 10,000,000인 경우

  • serial sorting took 968,464,268
  • parallel sorting took 277,913,894

 

size가 100,000,000인 경우

  • serial sorting took 10,152,806,777
  • parallel sorting took 2,313,726,436
size를 10단위로 늘려가며 테스트를 해보니 값이 커질 수록 병렬처리가 빠른 것을 확인해볼 수 있습니다.

 

그렇다면, size가 작으면 병렬이 느리고 size가 크면 병렬이 빠른 이유는 무엇일까요??

 

일단, 직렬 프로그래밍과 병렬 프로그래밍이 어떻게 다른지 알아야합니다.

 

프로세스에서 여러가지 일을 수행하기 위해선 멀티쓰레드를 사용해야 합니다. 단일쓰레드는 직렬 프로그래밍, 멀티쓰레드는 병렬 프로그래밍에서 사용한다고 보면됩니다. 여튼, 병렬 프로그래밍에서 사용하는 쓰레드는 쓰레드풀이 있지만, 쓰레드를 만들어 사용한다고하면 시간이 오래걸립니다. 이 때문에 실제로 size가 작을 때 작은 size를 계산하는 동안 쓰레드를 가져오는데 시간을 소모하기때문에 더 시간이 오래 걸리는 것 입니다. 

 

실제로 size가 100일 때 시간 차이를 확인해 보았습니다. 

public class SortTest {
    public static void main(String[] args) {
        int size = 100;
        int[] numbers = new int[size];
        Random random = new Random();
        IntStream.range(0, size).forEach(i -> numbers[i] = random.nextInt());

        long start = System.nanoTime();
        Arrays.sort(numbers);
        System.out.println("  serial sorting took " + (System.nanoTime() - start));

        IntStream.range(0, size).forEach(i -> numbers[i] = random.nextInt());
        start = System.nanoTime();
        Arrays.parallelSort(numbers);
        System.out.println("parallel sorting took " + (System.nanoTime() - start));



        start = System.nanoTime();
        MyThread myThread = new MyThread();
        myThread.start();
        System.out.println("Thread took " + (System.nanoTime() - start));
    }

    public static class MyThread extends Thread{
        int num;

        public MyThread() {
            this.num = 0;
        }

        public MyThread(int num) {
            this.num = num;
        }

        @Override
        public void run() {
            System.out.println("Thread Test");
        }
    }
}

 

출력

size 100일 때 시간

serial sorting 작업은 399,160입니다. 1개의 쓰레드를 생선하는데 필요한 시간은 1,075,145로 100개를 직렬로 정렬하는 것보다 더 오랜 시간을 소모하는 것을 확인할 수 있습니다. 그렇기 때문에 size에 따라서 직렬로 사용할지 병렬로 사용할지 잘 판단하여야 합니다.

 

'JAVA' 카테고리의 다른 글

절차지향 vs 객체지향  (0) 2022.11.04
[Java 8] Metaspace  (0) 2022.10.18
[Java 8] CompletableFuture  (0) 2022.10.13
[Java 8] Date와 Time  (0) 2022.10.12
[Java 8] Optional  (0) 2022.10.07

자바 Concurrent 소개

Concurrent 소프트웨어

  • 동시에 여러 작업을 할 수 있는 소프트웨어
  • Ex) 웹 브라우저로 유튜브를 보면서 키보드로 문서에 타이핑을 할 수 있다.

 

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

  • 멀티프로세싱 (ProcessBuilder)
  • 멀티쓰레드

 

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

  • Thread / Runnable

 

Thread 상속

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

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

Main이 먼저 출력 될 수도 있고, Thread가 먼저 출력될 수도있다. 

 

Runnable 구현 또는 람다

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

Runnable 또한 Main이 먼저 출력 될 수도 있고, Thread가 먼저 출력될 수도있다. 

 

Thread 주요 기능

  • 현재 쓰레드 멈춰두기(sleep)
    • 다른 쓰레드가 처리할 수 있도록 기회를 주지만 그렇다고 lock을 놔주진 않는다. (==>> 잘못하면 Deadlock (교착상태)에 빠짐)
  • 다른 쓰레드 깨우기 (Interrupt)
    • 다른 쓰레드를 꺠워서 InterruptedException을 발생시킨다. 그 에러가 발생했을 때 할 일은 코딩하기 나름. 종료시킬 수도 있고, 계속 하던일을 할 수도 있다.
  • 다른 쓰레드 기다리기 (join)
    • 다른 쓰레드가 끝날 때까지 기다린다.
public static void main(String[] args) throws InterruptedException {
    // sleep, interrupt
    Thread thread = new Thread(() -> {
        while(true) {
            System.out.println("Thread : " + Thread.currentThread().getName());

            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                // 자는 동안 누군가가 깨우면 error
                System.out.println("exit!!");
                return;
                //e.printStackTrace();
            }
        }
    });
    thread.start();

    System.out.println("Main : " + Thread.currentThread().getName());
    Thread.sleep(3000L);
    thread.interrupt();
    
    
    // join
    Thread thread = new Thread(() -> {
        System.out.println("Thread : " + Thread.currentThread().getName());

        try {
            Thread.sleep(3000L);
        } catch (InterruptedException e) {
            // 자는 동안 누군가가 깨우면 error
            System.out.println("exit!!");
            return;
            //e.printStackTrace();
        }
    });
    thread.start();

    System.out.println("Main : " + Thread.currentThread().getName());
    thread.join();      // thread가 끝날 때까지 기다린 다음 아래 출력
    System.out.println(thread + " is finished");

}

Thread가 두개만 되도 복잡한데 Thread가 여러개면 어떻게 될까?? 상상만해도 너무 복잡할 것 같다..

 

 

 

Executors

고수준 Concurrency 프로그래밍

  • 쓰레드를 만들고 관리하는 작업을 애플리케이션에서 분리
  • 위의 기능을 Executors에게 위임

 

Executors가 하는 일

  • 쓰레드 만들기
    • 애플리케이션이 사용할 쓰레드 풀을 만들어 관리한다.
  • 쓰레드 생명 주기 관리
  • 작업 처리 및 실행
    • 쓰레드로 실행할 작업을 제공할 수 있는 API를 제공

ExecutorService를 사용한 코드
실행한 결과

실행한 결과를 보면, main쓰레드가 아닌 미리 만들어놓은 쓰레드 풀에서 가져온 것을 확인할 수 있다. 특이한 점은 shutdown을 해주기전까지 쓰레드는 종료되지 않는다(당연히 프로세스도 죽지 않음)는 점이다.

 

주요 인터페이스

  • Executor
    • execute(Runnable)
  • ExecutorService
    • Executor 상송 받은 인터페이스로, Callable도 실행할 수 있으며, Executor를 종료시키거나 여러 Callable을 동시에 실행하는 등의 기능을 제공
  • ScheduleExecutorService
    • ExecutorService를 상속받은 인터페이스로 특정 시간 이후에 또는 주기적으로 작업을 실행할 수 있다.

 

ExecutorService로 작업 실행하기

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

 

ExecutorService로 멈추기

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

 

ScheduledExecutorService로 5초 뒤에 작업 실행하기

ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
scheduledExecutorService.schedule(()-> System.out.println(Thread.currentThread().getName()),
        5, TimeUnit.SECONDS);
scheduledExecutorService.shutdown();

 

Fork/Join 프레임워크

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

 

지금까지 사용한 Runnable은 기본적으로 return이 void로 반환 값이 없다. 만약, 반환을 받고싶으면 Callable을 사용해야 한다.

 

 

Callable과 Future

Callable

  • Runnable과 유사하지만 작업의 결과를 받을 수 있다. 

Future

  • 비동기적인 작업의 현재 상태를 조회하거나 결과를 가져올 수 있다.

 

결과를 가져오기 get()

ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<String> helloFuture = executorService.submit(() -> {
    Thread.sleep(2000L);
    return "Callable";
});
System.out.println("Hello");
String result = helloFuture.get();	//결과 값을 가져올 때까지 기다림  ==>> 블록킹
System.out.println(result);
executorService.shutdown();
  • 블록킹 콜이다.
  • 타임아웃(최대한으로 기다릴 시간)을 설정할 수 있다.

 

작업 상태 확인하기

  • isDone()
    • 완료했으면? true:false를 return

 

작업 취소하기

  • cancel()
    • 취소했으면? true:false를 return
    • parameter로 true를 전달하면 현재 진행중인 쓰레드를 interrupt하고 그러지 않으면 현재 진행중인 작업이 끝난 뒤 취소한다.

 

여러 작업 동시에 실행하기

  • invokeAll()
    • 동시에 실행한 작업 중에 제일 오래 걸리는 작업만큼 시간이 걸린다.

 

여러 작업 중에 하나라도 먼저 응답이 오면 끝내기 

  • invokeAny()
    • 동시에 실행한 작업 중에 가장 짧게 걸리는 작업만큼 시간이 걸린다.
    • 블록킹 콜이다.

 

 

CompletableFuture

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

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

 

Future로 하기 어렵던 작업

  • Future를 외부에서 완료시킬 수 없다. 취소하거나 get()에 타임아웃을 설정할 수는 있따.
  • 블로킹 코드인 get()을 사용하지 않고서는 작업이 끝났을 때 콜백을 실행할 수 없다.
  • 여러 Future를 조합할 수 없다.
    • Ex) Event 정보 가져온 다음 Event에 참여하는 회원 목록 가져오기
  • 예외 처리용 API를 제공하지 않는다.

 

CompletableFuture

 

비동기로 작업 실행하기

  • 리턴 값이 없는 경우
    • runAsync()
  • 리턴 값이 있는 경우
    • supplyAsync()
  • 원하는 Executor(쓰레드풀)를 사용해서 실행할 수도 있다.
    • 기본은 ForkJoinPool.commonPool()
// 리턴 X
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    System.out.println("Not Return : " + Thread.currentThread().getName());
});
future.get();       // Not Return : ForkJoinPool.commonPool-worker-3

// 리턴 O
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
    return "Return : " + Thread.currentThread().getName();
});
System.out.println(future2.get());      // Return : ForkJoinPool.commonPool-worker-3

 

콜백 제공

  • thenApply(Function)
    • 리턴 값을 받아서 다른 값으로 바꾸는 콜백
  • thenAccept(Consumer)
    • 리턴 값을 또 다른 작업을 처리하는 콜백 (리턴 X)
  • thenRun(Runnable)
    • 리턴 값 받고 다른 작업을 처리하는 콜백
  • 콜백 자체를 또 다른 쓰레드에서 실행할 수 있다.
// 리턴 값을 받아서 다른 값으로 바꾸는 콜백
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    return "Return : " + Thread.currentThread().getName();
}).thenApply((s) -> {   // 이것이 future와 다른 점
    System.out.println(s);  // Return : ForkJoinPool.commonPool-worker-3
    return s.toUpperCase();
});
System.out.println(future.get());   // RETURN : FORKJOINPOOL.COMMONPOOL-WORKER-3


// 리턴 값을 또 다른 작업을 처리하는 콜백 (리턴 X)
CompletableFuture<Void> future2 = CompletableFuture.supplyAsync(() -> {
    return "Return : " + Thread.currentThread().getName();
}).thenAccept((s) -> {
    System.out.println(s.toUpperCase());  // Return : ForkJoinPool.commonPool-worker-3
});

// 리턴 값 받고 다른 작업을 처리하는 콜백
CompletableFuture<Void> future3 = CompletableFuture.supplyAsync(() -> {
    return "Return : " + Thread.currentThread().getName();
}).thenRun(() -> {
    System.out.println("thenRun");
});

 

조합하기

  • thenCompose()
    • 두 작업이 서로 이어서 실행하도록 조합
  • thenCombine()
    • 두 작업을 독립적으로 실행하고 둘 다 종료 했을 때 콜백 실행
  • allOf()
    • 여러 작업을 모두 실행하고 모든 작업 결과에 콜백 실행
  • anyOf()
    • 여러 작업 중에 가장 빨리 끝난 하나의 결과에 콜백 실행

 

예외처리

  • Exceptionally(Function)
  • handle(BiFunction)
// exceptionally
boolean throwError = true;
CompletableFuture<String> hello = CompletableFuture.supplyAsync(() -> {
    if (throwError) {
        throw new IllegalArgumentException();
    }

    System.out.println("Hello " + Thread.currentThread().getName());
    return "Hello";
}).exceptionally(ex -> {
    return "Error!";
});

System.out.println(hello.get());

// handle
boolean throwError2 = true;
CompletableFuture<String> hello2 = CompletableFuture.supplyAsync(() -> {
    if (throwError2) {
        throw new IllegalArgumentException();
    }

    System.out.println("Hello " + Thread.currentThread().getName());
    return "Hello";
}).handle((result,ex) -> {
    if(ex != null) {
        return "Error!";
    }
    return result;
});

System.out.println(hello2.get());

 

 

 

'JAVA' 카테고리의 다른 글

[Java 8] Metaspace  (0) 2022.10.18
[JAVA 8] ParallelSort  (0) 2022.10.15
[Java 8] Date와 Time  (0) 2022.10.12
[Java 8] Optional  (0) 2022.10.07
[JAVA 8] Stream  (0) 2022.10.06

Date와 Time 소개

Java 8에서 새로운 날짜와 시간 API가 만들어진 이유

  • 이전에 사용하던 java.util.Date 클래스는 mutable하기 때문에 thread safe 하지 않다. (Set이 가능하다.)
    • 새로 나온 LocalDate는 immutable하다! (java.time.*에 있음)
  • 클래스 이름이 명확하지 않다. Date지만 시간을 다룬다.
  • 버그가 발생할 여지가 많다. (타입 안정성이 없고, 월이 0부터 시작한다... 등등)
  • 날짜 시간 처리가 복잡한 애플리케이션에는 보통 Joda Time을 쓰곤 했다. 

Month의 경우 MagicConstant로 매직넘버도 가능하고, 기존 달인 1~12가아닌 0~11로 주의해서 사용해야 한다.

 

Java 8에서 제공하는 Date-Time API

  • JSR-310 스펙의 구현체를 제공한다.
  • 디자인 철학
    • Clear
    • Fluent
    • Immutable
    • Extensible

 

주요 API

  • 기계용 시간 (machine time)과 인류용 시간 (human time)으로 나눌 수 있다.
  • 기계용 시간은 EPOCK (1970년 1월 1일 0시 0분 0초)부터 현재까지의 타임스탬프를 표현한다.
  • 인류용 시간은 우리가 흔히 사용하는 연,월,일,시,분,초 등을 표현한다.
  • 타임스탬프는 Instant를 사용한다.
  • 특정 날짜(LocalDate), 시간(LocalTime), 일시(LocalDateTime)를 사용할 수 있다.
  • 기간을 표현할 때는 Duration(시간 기반)과 Period(날짜 기반)를 사용할 수 있다.
  • DateTimeFomatter를 사용해서 일시를 특정한 문자열로 formatting할 수 있다.

 

Date와 Time API
지금 시간을 기계 시간으로 표현하는 방법

  • Instant.now()
    • 현재 UTC (GMT)를 리턴
  • Universal Time Coordinated == Gereenwich Mean Time
Instant now = Instant.now();
System.out.println(now);        // 기준 UTC
System.out.println(now.atZone(ZoneId.of("UTC")));   // 위와 같음

System.out.println(ZoneId.systemDefault());     // 시스템 기준 시점
ZonedDateTime zonedDateTime = now.atZone(ZoneId.systemDefault()); // 시스템 기준 시점 시간
System.out.println(zonedDateTime);

 

인류용 일시를 표현하는 방법

  • LocalDateTime.now()
    • 현재 시스템 Zone에 해당하는 일시를 리턴 (로컬 시간)
  • LocalDateTime.of(int, Month, int, int, int, int)
    • 로컬의 특정 일시를 리턴
  • LocalDateTime.of(int, Month, int, int, int, int, ZoneId)
    • 특정 Zone의 특정 일시를 리턴
LocalDateTime now = LocalDateTime.now();    // Local은 시스템 정보 확인해서 시간을 가져옴
System.out.println(now);

LocalDateTime birthDay = LocalDateTime.of(1995, Month.MARCH, 10, 0, 0, 0);
ZonedDateTime nowInKorea = ZonedDateTime.now(ZoneId.of("Asia/Seoul"));
System.out.println("birthDay : " + birthDay);
System.out.println("korea : " + nowInKorea);

 

기간을 표현하는 방법

  • Period / Duration .between()

 

 

파싱 또는 포매팅

// 포맷
LocalDateTime now = LocalDateTime.now();
System.out.println(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
DateTimeFormatter MMddyyyy = DateTimeFormatter.ofPattern("MM/dd/yyyy");
System.out.println(now.format(MMddyyyy));       // 10/13/2022

// 파싱
DateTimeFormatter MMddyyyy = DateTimeFormatter.ofPattern("MM/dd/yyyy");
LocalDate birthDay = LocalDate.parse("03/10/1995", MMddyyyy);
System.out.println(birthDay);       // 1995-03-10

 

레거시 API 지원

  • GregorianCalendar와 Date 타입의 인스턴스를 Instant나 ZonedDateTime으로 변환 가능
  • java.util.TimeZone 에서 java.time.ZoneId로 상호 변환 가능
Date date = new Date();
Instant instant = date.toInstant();
Date newDate = Date.from(instant);

GregorianCalendar gregorianCalendar = new GregorianCalendar();
LocalDateTime now = gregorianCalendar.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();

 

 

 

 

 

 

'JAVA' 카테고리의 다른 글

[JAVA 8] ParallelSort  (0) 2022.10.15
[Java 8] CompletableFuture  (0) 2022.10.13
[Java 8] Optional  (0) 2022.10.07
[JAVA 8] Stream  (0) 2022.10.06
[JAVA 8] 인터페이스의 변화  (0) 2022.10.06

Optional 소개

자바 프로그래밍에서 NullPointerException과 같은 에러들을 종종 볼 수 있다. 이러한 에러의 경우 컴파일 에러가 아닌 런타임 에러로 프로젝트에서 발생하면 치명적일 수 있는 에러이다. 

 

보통 NullPointerException의 경우 아래와 같은 상황에서 발생한다.

  • Null 리턴 (설계상)
  • Null 체크 실수 (설계상)

나도 코딩테스트를 보며 NullPointerException을 가끔 본다. 이럴 때마다 설계의 중요성을 한번씩 깨닫게 되는 것 같다..

 

하지만, 메소드에서 작업 중 특별한 상황에서 값을 제대로 리턴을 할 수 없는 경우가 있을 수 있다. 이러한 경우에는 아래와 같은 방법이 있을 것이다.

  • 예외를 던진다. (비싸다.. 스택트레이스(에러가 발생하기 전까지의 콜스타일 저장)를 찍어두기때문이다.)
    • 로직을 처리할 때 Exception처리는 좋은 로직이 아니다!
  • null을 리턴한다. (비용 문제가 없지만, 코드를 사용하는 클라이언트 코드에서 주의해야 한다.)
  • Optional을 리턴한다. (클라이언트에 코드에게 명시적으로 빈 값일 수도 있다는 것을 알려주고, 빈 값인 경우에 대한 처리를 강제한다.)

null을 리턴하는 경우 설계상 실수가 있을 수 있지만, Optional을 사용한다면 실수를 없앨 수 있다고 생각이 든다. 그렇다면 Optional을 알아보자.

 

Optional

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

 

주의할 점

  • 리턴값으로만 쓰기를 권장 (메소드 매개변수 타입, 맵의 키 타입, 인스턴스 필드 타입으로 쓰지 말자.)
    • 메소드 매개변수 타입) 매개변수로 받았지만, 메소드 안에서 다시 체크를 해야하기 때문에 코드가 길어지는 문제가 발생
    • 맵의 키 타입) 맵 키의 특징 중 하나는 키가 Null이 아닌 것 이다. 이를 무너뜨린다..
  • Optional을 리턴하는 메소드에서 null을 리턴하지 말자.
  • 프리미티브 타입용 Optional은 따로 있다. OptionalInt, OptionalLong....
    • 프리미티브 타입의 경우 Optional.of(10)과 같이 사용하면 박싱, 언박싱이 안에서 이루어지기 때문에 성능이 안좋다. 이를 해결하고자 OptionalInt, OptionalLong, OptionalDouble과 같은 Optional을 사용하자.
  • Collection, Map, Stream Array, Optional은 Optional로 감싸지 말자.
    • 이미 container의 성격을 갖고 있다. 굳이 감쌀 이유가 없다.

 

Optional 만들기

  • Optional.of(instance)
    • .of 는 instance가 Null이 무조건 아닐 때 사용
  • Optional.ofNullable(instance) 
    • .ofNullable 은 instance가 null일 수도 있을 때 사용
  • Optional.empty()

 

Optional에 값이 있는지 없는지 확인하기

  • isPresent()
  • isEmpty()
    • Java 11부터 제공

 

Optional에 있는 값 가져오기

  • get()
  • 만약에 비어있는 Optional에서 무언가를 꺼낸다면??
    • 런타임 에러가 발생한다. (NoSuchElementException)

 

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

  • isPresent(instance)
  • ex) Spring으로 시작하는 수업이 있으면 id를 출력하라

 

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

  • orElse(T)
  • ex) JPA로 시작하는 수업이 없다면 비어있는 수업을 Return

 

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

  • orElseGet(Supplier)
  • ex) JPA로 시작하는 수업이 없다면 새로 만들어서 Return

 

Optional에 갑싱 있으면 가져오고 없는 경우 에러를 던져라

  • orElseThrow()

 

Optional에 들어있는 값 걸러내기

  • Optional filter(Predicate)

 

Optional에 들어있는 값 변환하기

  • Optional map(Function)
  • Optional flatMap(Function)
    • Optional 안에 들어있는 인스턴스가 Optional인 경우에 사용하면 편리 (안에 있는 Optional이 한번 까져서 나옴)

코드로 확인해보자.

 

 

package me.qazyj.java8;

import java.time.Duration;
import java.util.*;

public class OptionalTest {

    public static void main(String[] args) {

        List<OnlineClass> springClass = new ArrayList<>();
        springClass.add(new OnlineClass(1, "spring boot", true));
        springClass.add(new OnlineClass(6, "rest api", false));

        Optional<OnlineClass> findStartSpring = springClass.stream()
                .filter(oc -> oc.getTitle().startsWith("spring"))
                .findFirst();       // 찾은 첫번째 값을 return
        System.out.println(findStartSpring.isPresent());        // id 1이 spring으로 시작함 ==>> true
        //값이 있는지 없는지 확실하지 않을 때 ifPresent로 확인 후 있을 떄만 실행
        //findStartSpring.ifPresent(oc -> System.out.println(oc.getTitle()));

        //없으면 생성 x(있어도 생성하고 Return 함) orElse
        Optional<OnlineClass> findStartJpa = springClass.stream()
                .filter(oc -> oc.getTitle().startsWith("jpa"))
                .findFirst();       // 찾은 첫번째 값을 return
        OnlineClass onlineClass = findStartJpa.orElse(new OnlineClass(10, "New class", false));
        System.out.println(onlineClass.getTitle());

        // 에러
        //OnlineClass onlineClass1 = findStartJpa.orElseThrow(IllegalStateException::new);

        // is.empty - 비어있을 떄 true / 있을 떄 false
        Optional<OnlineClass> onlineClass2 = findStartSpring.filter(oc -> oc.getClosed());
        System.out.println("empty : " + onlineClass2.isEmpty());

    }
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

'JAVA' 카테고리의 다른 글

[Java 8] CompletableFuture  (0) 2022.10.13
[Java 8] Date와 Time  (0) 2022.10.12
[JAVA 8] Stream  (0) 2022.10.06
[JAVA 8] 인터페이스의 변화  (0) 2022.10.06
[JAVA 8] 함수형 인터페이스와 람다 표현식  (0) 2022.09.23

Stream 소개

Stream이란 용어가 익숙하지 않을 수 있다. Stream이란 컨베이너 벨트에 데이터를 놓고 컨베이너 벨트를 지나가며 원하는 형식에 따라 데이터 처리를 하는 것이라고 보면 된다.

 

Stream

  • sequence of elements supporting sequential and parallel aggregate operations
  • 데이터를 담고 있는 저장소(컬렉션)가 아니다.
  • Functional in nature, 스트림이 처리하는 데이터 소스를 변경하지 않는다. (기존 데이터는 그대로라고 보면 된다.)
  • 스트림으로 처리하는 데이터는 오직 한번만 처리한다.
  • 무제한일 수도 있다. (Short Circuit 메소드를 사용해서 제한할 수 있다.)
  • 중개 오퍼레이션은 근본적으로 lazy하다. (lazy 함은 종료 오퍼레이션이 오기전까지, 작업을 수행하지 않는다. 라고 보면 좋다.)
  • 손쉽게 병렬 처리 할 수 있다.

 

스트림 파이프라인

  • 0 또는 다수의 중개 오퍼레이션 (intermediate operation)과 한개의 종료 오퍼레이션 (terminal operation으로 구성한다.
  • 스트림의 데이터 소스는 오직 터미널 오퍼레이션을 실행할 때에만 처리한다.

 

중개 오퍼레이션

  • Stream을 리턴한다.
  • Stateless / Stateful 오퍼레이션으로 더 상세하게 구분할 수도 있다. (대부분은 Stateless지만 distinct나 sorted 처럼 이전 소스 데이터를 참조해야 하는 오퍼레이션은 Stateful 오퍼레이션이다.)
  • filter, map, limit, skip, sorted, ....

 

종료 오퍼레이션

  • Stream을 리턴하지 않는다. (Stream을 다른 자료구조로 변경하기 때문)
  • collect, allMatch, count, forEach, min, max, ....

 

이제 코드로 확인해보자.

package me.qazyj.java8;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamTest {

    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("kyj");
        names.add("yj");
        names.add("kim");
        names.add("young_jin");

        Stream<String> stringStream = names.stream().map(String::toUpperCase);
        names.forEach(System.out::println); // 기존 데이터는 소문자 그대로이다.

        System.out.println("==============");

        // 중개 오퍼레이션이 lazy함의 증거로 아무출력도 일어나지 않는다.
        names.stream().map((s) -> {
            System.out.println(s);
            return s.toUpperCase();
        });

        // 중개 오퍼레이션 1개와 종료 오퍼레이션 1개로 stream 처리한 예시
        // 종료 오퍼레이션이 왔기때문에 출력이 발생한다.
        // 종료 오퍼레이션은 아래와 같이 Stream이 아닌 List나 Set과 같은 자료형임을 볼 수 있다.
        List<String> collect = names.stream().map((s) -> {
            System.out.println(s);
            return s.toUpperCase();
        }).collect(Collectors.toList());

        // 병렬 처리 예시
        List<String> collect1 = names.parallelStream().map(String::toUpperCase)
                .collect(Collectors.toList());
        collect1.forEach(System.out::println);
    }
}

병렬 처리할 때 사용하는 parallelStream의 경우는 오히려 성능을 저하시킬 수 있다. 상황에 따라서 병렬로 하는게 빠를 수 있고, 더 느릴 수 있기 때문에 병렬을 쓸 때와 쓰지 않았을 때의 시간 차이를 보고 적절한 방법으로 사용하는걸 권장한다.

 

병렬처리 테스트에 관련한 자료는 아래

 

[JAVA 8] ParallelSort

Arrays.parallelSort() Fork/Join 프레임워크를 사용해서 배열을 병렬로 정렬하는 기능을 제공한다. 병렬 정렬 알고리즘 배열을 둘로 계속 쪼갠다. 합치면서 정렬한다. 이전에 parallelStream을 설명할 때 말

qazyj.tistory.com

 

 

 

Stream API

스트림 API 사용 예시

 

걸러내기

  • Filter (Predicate)
  • 예) 문자열 시작이 kim인 데이터만 새로운 스트림으로

 

변경하기 

  • Map(Function) 또는 FlatMap(Function)
  • 예) 각각의 User 인스턴스에서 String name만 새로운 스트림으로 
  • 예) List<Stream<String>>을 String의 스트림으로

 

생성하기

  • Generate(Supplier) 또는 Iterate(T seed, UnaryOperator)
  • 예) 2씩 증가하는 무제한 숫자 스트림
  • 예) 랜덤 Integer 무제한 스트림 (limit을 주지 않으면 무제한으로 간다.)

 

제한하기

  • limit(long) 또는 skip(long)
  • 예) 최대 5개의 요소가 담긴 스트림을 리턴한다.
  • 예) 앞에서 3개를 뺀 나머지 스트림을 리턴한다.

스트림에 있는 데이터가 특정 조건을 만족하는지 확인

  • anyMatch(), allMatch(), nonMatch()
  • 예) k로 시작하는 문자열이 있는지 확인한다. (true 또는 false return)
  • 예) 스트림에 있는 모든 문자열의 길이가 10보다 작은지 확인한다.

 

개수 세기

  • count()
  • 예) k로 시작하는 문자열의 개수를 센다.

 

스크림을 데이터 하나로 뭉치기

  • reduce(identity, BiFunction), collect(), sum(), max
  • 예) 모든 숫자 합 구하기
  • 예) 모든 데이터를 하나의 List 혹은 Set에 옮겨 담기

이제 코드로 알아보자.

 

package me.qazyj.java8;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class OnlineClassTest {

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

        System.out.println("spring 문자열로 시작하는 OnlineClass의 id");
        springClass.stream().filter(oc -> oc.getTitle().startsWith("spring"))
                        .forEach(oc -> System.out.println(oc.getId()));
        System.out.println();

        System.out.println("close 되지 않은 OnlineClass의 id");
        springClass.stream().filter(oc -> !oc.getClosed())   // Predicate.not(OnlineClass::getClosed()) 도 가능
                        .forEach(oc -> System.out.println(oc.getId()));
        System.out.println();

        System.out.println("수업 이름만 모아서 스트림 만들기");
        springClass.stream().map(OnlineClass::getTitle)
                        .forEach(System.out::println);
        System.out.println();


        List<OnlineClass> javaClass = new ArrayList<>();
        javaClass.add(new OnlineClass(7, "The Java, Test", true));
        javaClass.add(new OnlineClass(8, "The Java, Code", true));
        javaClass.add(new OnlineClass(9, "The Java, 8 to 11", false));

        List<List<OnlineClass>> kyjEvents = new ArrayList<>();
        kyjEvents.add(springClass);
        kyjEvents.add(javaClass);


        System.out.println("두 수업 목록에 들어있는 모든 OnlineClass의 id");
        // flatMap이란 List 등과 같은 컬렉션의 데이터들을 풀어 놓는 것?? 이라고 보면된다. 현재는 OnlineClass 하나 하나 데이터들이 나오게 된다고 보면 된다.
        kyjEvents.stream().flatMap(Collection::stream)      // list -> stream 으로 flat
                        .forEach(oc -> System.out.println(oc.getId()));
        System.out.println();

        System.out.println("2씩 증가하는 무제한 스트림 중 앞에 2개 뺴고 최대 10개까지만");
        Stream.iterate(0, i -> i + 2)
                .skip(2)
                .limit(10)
                .forEach(System.out::println);
        System.out.println();

        System.out.println("자바 수업 중에서 Test가 들어있는 수업이 있는지 확인");
        boolean test = javaClass.stream().anyMatch(oc -> oc.getTitle().contains("Test"));
        System.out.println(test);

		//.collect(Collectors.toList()) 대신 count()를 쓰면 개수가 나옴
        System.out.println("스프링 수업 중에서 제목에 spring이 들어간 것만 모아서 List로 만들기");
        List<String> spring = springClass.stream().filter(oc -> oc.getTitle().contains("spring"))
                .map(OnlineClass::getTitle)
                .collect(Collectors.toList());
        spring.forEach(System.out::println);
        System.out.println();


    }
}

 

 

 

 

 

 

'JAVA' 카테고리의 다른 글

[Java 8] Date와 Time  (0) 2022.10.12
[Java 8] Optional  (0) 2022.10.07
[JAVA 8] 인터페이스의 변화  (0) 2022.10.06
[JAVA 8] 함수형 인터페이스와 람다 표현식  (0) 2022.09.23
ListIterator  (0) 2022.09.23

+ Recent posts