목차

    JAVA8에서의 변경 사항

    2014년에 발표된 자바의 최신 버전인 Java SE 8 버전에서는 많은 사항이 변경되거나 새롭게 추가되었습니다.

    Java SE 8에서 변경되거나 새롭게 추가된 사항 중에서 주목할 만한 특징은 다음과 같습니다.

    람다 표현식(Lambda Expression)

    람다 표현식(lambda expression)이란 간단히 말해 메소드를 하나의 식으로 표현한 것입니다.

    즉, 식별자 없이 실행할 수 있는 함수 표현식을 의미하며, 따라서 익명 함수(anonymous function)라고도 부릅니다.

     

    메소드를 이렇게 람다 표현식으로 표현하면 클래스를 만들고 객체를 생성하지 않아도 메소드를 사용할 수 있습니다.

    또한, 람다 표현식은 메소드의 매개변수로 전달될 수도 있고, 메소드의 결괏값으로 반환될 수도 있습니다.

    이러한 람다 표현식은 기존의 불필요한 코드를 줄여주고, 작성된 코드의 가독성을 높이는 데 그 목적이 있습니다.

    Java SE 8 버전부터는 람다 표현식을 사용하여 자바에서도 함수형 프로그래밍을 할 수 있게 되었습니다.

    다음 예제는 전통적인 방식의 스레드 생성과 람다 표현식을 사용한 스레드 생성을 비교하는 예제입니다.

    new Thread(new Runnable() {
        public void run() {
            System.out.println("전통적인 방식의 일회용 스레드 생성");
        }
    }).start();
    //람다식 사용
    new Thread(()->{
        System.out.println("람다 표현식을 사용한 일회용 스레드 생성");
    }).start();

    스트림 API(Stream API)

    자바에서는 많은 양의 데이터를 저장하기 위해서 배열이나 컬렉션을 사용합니다.

    또한, 이렇게 저장된 데이터에 접근하기 위해서는 반복문(for문)이나 반복자(iterator)를 사용하여 매번 코드를 작성해야 했습니다.

     

    하지만 이렇게 작성된 코드는 길이가 너무 길고 가독성도 떨어지며, 코드의 재사용이 거의 불가능합니다.

    또한, 데이터베이스의 쿼리와 같이 정형화된 처리 패턴을 가지지 못했기에 데이터마다 다른 방법으로 접근해야만 했습니다.

     

    이러한 문제점을 극복하기 위해서 Java SE 8 버전부터 도입된 방법이 바로 스트림(stream) API입니다.

    스트림 API는 데이터를 추상화하여 다루므로, 다양한 방식으로 저장된 데이터를 읽고 쓰기 위한 공통된 방법을 제공합니다.

    따라서 스트림 API를 이용하면 배열이나 컬렉션뿐만 아니라 파일에 저장된 데이터도 모두 같은 방법으로 다룰 수 있습니다.

    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 + " "));

    java.time 패키지

    JDK 1.0에서는 Date 클래스를 사용하여 날짜에 관한 처리를 수행했습니다.

    하지만 Date 클래스는 현재 대부분의 메소드가 사용을 권장하지 않고(deprecated) 있습니다.

    JDK 1.1부터 새롭게 제공된 Calendar 클래스는 날짜와 시간에 대한 정보를 얻을 수는 있지만, 다음과 같은 문제점을 가지고 있습니다.

    'Java' 카테고리의 다른 글

    추상클래스 vs 인터페이스  (0) 2022.04.07
    GC (Garbage Collector)  (0) 2022.04.06
    Java & Spring 버전 특징 정리  (0) 2022.04.04
    객체의 래퍼클래스(WrapperClass)  (0) 2021.09.16
    IntelliJ IDEA 단축키  (0) 2021.01.28

    목차

      싱글턴 패턴(Singleton pattern)

      소프트웨어 디자인 패턴에서 싱글턴 패턴을 따르는 클래스는, 생성자가 여러 차례 호출되더라도 실제로 생성되는 객체는 하나이고 최초 생성 이후에 호출된 생성자는 최초의 생성자가 생성한 객체를 리턴한다.

      싱글턴 패턴을 사용하는 이유

      만약 우리가 만들었던 DI 컨테이너인 요청을 할 때마다 새로운 객체를 생성한다. 요청이 엄청나게 많은 트래픽 사이트에서는 계속 객체를 생성하게 되면 메모리 낭비가 심하기 때문이다.

      스프링 컨테이너

      스프링 컨테이너는 싱글턴 패턴을 적용하지 않아도 객체 인스턴스를 싱글톤으로 관리한다. 이러한 기능 덕분에 싱글톤 패턴의 모든 단점을 해결하고 객체를 싱글톤으로 유지할 수 있다

      Spring에서 싱글톤을 사용하는 이유

      애플리케이션 컨텍스트에 의해 등록된 빈은 기본적으로 싱글톤으로 관리된다. 즉, 스프링에 여러 번 빈을 요청하더라도 매번 동일한 객체를 돌려준다는 것이다. 애플리케이션 컨텍스트가 싱글톤으로 빈을 관리하는 이유는 대규모 트래픽을 처리할 수 있도록 하기 위함이다.

      스프링은 최초에 설계될 때 부터 대규모의 엔터프라이즈 환경에서 요청을 처리할 수 있도록 고안되었다. 그리고 그에 따라 계층적으로 처리 구조(Controller, Service, Repository 등) 가 나뉘어지게 되었다.

      그런데 매번 클라이언트에서 요청이 올 때마다 각 로직을 처리하는 빈을 새로 만들어서 사용한다고 생각해보자. 요청 1번에 5개의 객체가 만들어진다고 하고, 1초에 500번 요청이 온다고 하면 초당 2500개의 새로운 객체가 생성된다. 아무리 GC의 성능이 좋아졌다 하더라도 부하가 걸리면 감당이 힘들 것이다.

      그래서 이러한 문제를 해결하고자 빈을 싱글톤 스코프로 관리하여 1개의 요청이 왔을 때 여러 쓰레드가 빈을 공유해 처리하도록 하였다.

       Spring에서 관리하는 싱글톤의 장점

      Java로 기본적인 싱글톤 패턴을 구현하고자 하면 다음과 같은 단점들이 발생한다.

      • private 생성자를 갖고 있어 상속이 불가능하다.
      • 싱글톤으로 생성되면 객체테스트가 어렵기 때문에, 테스트를 위한 객체를 새로만들어야한다.
      • 서버 환경에서는 싱글톤이 1개만 생성됨을 보장하지 못한다.
      • 전역 상태를 만들 수 있기 때문에 바람직하지 못하다.

      기본적으로 싱글톤이 멀티쓰레드 환경에서 서비스 형태의 객체로 사용되기 위해서는 내부에 상태정보를 갖지 않는 무상태(Stateless) 방식으로 만들어져야 한다. 만약 여러 쓰레드들이 동시에 상태를 접근하여 수정한다면 상당히 위험하기 때문이다.

      직접 싱글톤을 구현한다면 상당히 많은 단점들이 있겠지만, Spring 프레임워크에서 직접 싱글톤으로 객체를 관리해주므로, 우리는 더욱 객체지향적인 개발을 할 수 있게 된 것이다.

      싱글턴 방식의 주의점 ✨

      싱글톤 패턴이든, 스프링에서 객체 인스턴스를 하나만 생성해서 공유하는 상황에서 객체 인스턴스를 공유하기 때문에 객체 상태를 유지(stateful)하게 설계하면 안된다.
      1. price는 공유되는 필드이기 때문에 특정 클라이언트가 값을 변경한다.
      2. 실무에서 이런 경우를 종종 보는데, 이로인해 정말 해결하기 어려운 큰 문제들이 터진다.(몇년에 한번씩 꼭 만난다.)

      stateless(무상태)방식으로 만들어라.

      • 읽기전용 값이라면 초기화시점에서 iv에 저장해두고 공유하는 것은 문제가 없지만,
        다중 사용자의 요청을 한꺼번에 처리하는 쓰레드들이 동시에 iv를 건드리는 것은 위험하다. 따라서 stateless방식으로 만들어져야한다.
      • 파라미터, 로컬변수, 리턴값 등을 이용해, 각각의 값을 iv처럼 공유되는 영역이 아닌, 독립적인 영역에 저장하자.

      싱글톤 레지스트리

      그래서 스프링은 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능을 제공하는데, 그것이 바로 싱글톤 레지스트리(Singleton Registry) 이다. 스프링 컨테이너는 싱글톤을 생성하고, 관리하고 공급하는 컨테이너이기도 하다. 싱글톤 레지스트리의 장점은 다음과 같다.

      • static 메소드나 private 생성자 등을 사용하지 않아 객체지향적 개발을 할 수 있다.
      • 테스트를 하기 편리하다.

      이러한 스프링에 장점을 활용하여 초기에 설정하면 사용중에는 변하지않는 읽기전용 인스턴스 변수인경우에는 서버의 성능향상을 위해 bean으로 등록하고 사용하자. 

      'Java > *****디자인패턴' 카테고리의 다른 글

      퍼사드(Facade) 패턴  (0) 2022.04.03
      빌더 패턴  (0) 2022.04.01
      싱글톤 (Singleton) 패턴  (0) 2022.04.01
      디자인 패턴  (0) 2022.03.31
      디자인 패턴 싱글톤 패턴 개념 정리  (0) 2022.03.18

      목차

      인터페이스를 사용하는 이유는 무엇인가?

      설계도라고 생각하면 된다.

      하나의 규약, 즉 구체적인 약속 같은 것으로 인해 협업에 필수적이라고 볼 수 있다.

      큰 프로젝트일수록 또는 개발 인원이 많을 경우 인터페이스를 통해 많은 이점을 얻게 된다.

       

      추상 클래스를 사용하는 이유는 무엇인가?

      상속을 강제하기 위함이다.

      부모 클래스에서 정의만 해놓고, 실제 동작은 자식 클래스에서 하게 된다.

      이러한 추상 클래스의 성격이 잘 반영되어진 것이 팩토리 메소드 패턴(Factory Method Pattern) 이다.

      다음 글에서 다뤄볼 예정이니 참고하면 더욱 이해에 도움이 될 것이다.

       

       

      'Java' 카테고리의 다른 글

      [Java] Java 8 에서의 변경사항  (0) 2022.05.05
      GC (Garbage Collector)  (0) 2022.04.06
      Java & Spring 버전 특징 정리  (0) 2022.04.04
      객체의 래퍼클래스(WrapperClass)  (0) 2021.09.16
      IntelliJ IDEA 단축키  (0) 2021.01.28

      목차

        GC란?

        Java Application은 JVM(Java Viirtual Machine) 위에서 구동됩니다. JVM에서는 Java Application이 사용하는 메모리를 관리하고 있는데 이 JVM의 기능중 더 이상 사용하지 않는 객체를 청소하여 공간을 확보하는 GC라는 작업이 있습니다.

         

        GC란 Garbage Collection(쓰레기 객체 정리, 이하 GC)의 약자입니다. Java Runtime시 Heap 영역에 저장되는 객체들은 따로 정리하지 않으면 계속헤서 쌓이게되어 OutOfMemmory Exception이 발생할 수 있습니다. 이를 방지하기 위하여 JVM에서는 주기적으로 사용하지 않는 객체를 수집하여 정리하는 GC를 진행합니다.

         

        JAVA에서 GC의 도입의 전제 가설 (weak generational hypothesis)

        • 대부분의 객체는 금방 접근 불가능 상태 (unreachable)가 된다.
        • 오래된 객체에서 젋은 객체로의 참조는 아주 적게 존재한다.

        아래와 같이 객체를 생성하고 해당 객체의 어떤 동작을 10,000번 수행하는 반복문에서 마지막 한번을 제외 한 9,999번의 객체들은 생성 후 바로 접근 불가능 상태가 된다. 이를 unreachable 상태라고 하며, GC가 발생하지 않으면 10,000개의 객체 현재 참조 되지 않지만 메모리를 점유 하고 있다고 볼 수 있다.

        for (int i = 0; i < 10000; i++) {
            SampleObj obj = new SampleObj();
            obj.doTest(i);
        }

        또 아래와 같이 객체를 생성하고 다른 메소드나 클래스에 해당 객체를 전달 후 에는 다시 참조 하지 않는 경우가 대 부분이다. 이를 오래된 객체에서 젊은 객체로의 참조는 아주 적게 발생 한다라고 말 할 수 있다.

        SampleObj obj = new SampleObj();
        obj.setValue(1);
        doTest2(obj);

        JVM의 메모리 영역

        GC의 수행영역별 구분

        Eden영역부터 Servior영역까지를 Young 영역이라고 부릅니다. 크게 Young, Old, Perm 영역으로 나누어 지는데 JDK 8버전 부터는 이 영역이 사라졌습니다.

        GC는 수행되는 영역에 따라 두가지로 구분 할 수 있다.

        GC가 수행되는 영역에 따라 Minor GC 와 Major GC(Full GC) 로 구분한다.
        • Minor GC Eden Survivor1,2 Young Generation 영역의 객체를 메모리에서 삭제한다.
        • Major GC Minor GC과정에서 삭제 되지 않고, Old Generation영역으로 옮겨진 객체 중 미사용 된다고 판단되는 객체를 메모리에서 삭제한다.

        SWT(Stop-The-World)

        GC를 이해하고자 할 때 중요한 요소가 있다.

        GC가 발생하고 이를 수행하는 과정에서 진행중인 쓰레드는 멈추고 GC를 수행하는 쓰레드만 동작하게 되는 데 이를 Stop-The-World 라 부른다. 이 Stop-the-world가 경우에 따라서는 시스템 장애로 이어 질 수 있기 때문에 GC와는 떼어 놓을 수 없다.

        메모리 영역이 크게 설정 되어 있으면, GC가 발생하는 횟수를 줄일 수 있지만, GC 수행 시간은 증가 하기 때문에 최적화가 필요 할 수 밖에 없는 이유이다.

        STW (Stop-The-World)
        • GC가 발생하면 JVM은 어플리케이션을 멈추고, GC를 실행하는 쓰레드만 동작하게 되고 이를 Stop-The-World 라고 한다.
        • STW가 발생하는 동안은 어플리케이션이 중지 되기 때문에 장애로 이어 질 수 있다.

        GC 발생 시나리오

        GC는 기본적으로 다음과 같은 시나리오로 동작한다.

        객체가 생성되면 Eden 영역에 위치 하게 된다.

        Eden영역이 가득차게 되면 Minor GC가 발생하여 참조가 없는 객체는 삭제되고, 참조 중인 객체는 Suvvivor 영역으로 이동한다.

        Survivor영역이 가득차게 되면 Minor GC가 발생하고 참조가 없는 객체는 삭제되고, 참조 중인 객체는 다른 Survivor2영역으로 이동한다.

        Survivor 영역에서의 GC과정을 반복 하며, 계속 참조 중인 객체는 OLD 영역으로 이동한다.

         

        예외) Eden 영역에서 Survivor 영역으로 이동 할 때 객체가 남아있는 영역보다 큰 경우 OLD 영역으로 이동한다.

         

        GC 수행 방식에 따른 종류와 변화

        JVM 버전의 변화에 따라 여러가지 GC방식이 추가 되고 발전되어 왔다.

        버전별로 지원하는 GC는 차이가 존재하며, 서비스 상황에 따라 필요한 GC방삭을 JVM옵션을 통한 설정으로 사용이 가능하다.

        GC의 종류는 다음과 같다.

         

        Serial Garbage Collector

        - 주로 32비트 JVM에서 돌아가는 싱글쓰레드 어플리케이션에서 사용 (별도로 지정하지 않는 경우 기본 GC)
        - Minor GC 뿐 아니라 Major GC인 경우도 올스탑(stop-the-world)하는 싱글쓰레드 방식.

         

         

        Parallel Collector(=Throughput Collector)

        - 64비트 JVM이나 멀티 CPU 유닉스 머신에서 기본 GC로 설정되어 있음.
        Minor
        GC와 MajorGC 모두 멀티쓰레드를 사용.
        - MinorGC 뿐 아니라 Major GC인 경우도 올스탑
        (stop-the-world)

         

         

        CMS Collector (Concurrent Mark-Sweep)

        - Initial Mark 단계에서참조 상태인 객체를 짧은 시간에Marking ,올스탑 없이 Concurrent Mark단계에서 참조상태 객체를 확인. Remark단계에서 변경되거나 추가된 객체를 확인. Concurrent Sweep 단계애서참조 되지 않는 객체를 정리.
        - 
        CPU리소스를 많이 사용, 메모리 파편화가 단점
        .

         

        G1 Collector (Garbage First)

        - 기존 Young, Old 영역의 개념과 다른 Heap Resion 개념을 도입함.
        - 하나 이상의 Resion 에서 객체를 복사해 다른 Resion으로 이동 시키는 방식.
        - 
        CMS와 비슷한 방식으로  동작 시작. Heap 전역적으로 Marking 하고, 가장 많은 공간이 있는 곳 부터 메모리 회수 진행. 이 부분 때문에 Garbage First 라는 이름이 붙었다.
        - CMS Collector
         CPU리소스 및 메모리 파편화의 단점 해결.

         

        참조

        https://d2.naver.com/helloworld/1329

         

        'Java' 카테고리의 다른 글

        [Java] Java 8 에서의 변경사항  (0) 2022.05.05
        추상클래스 vs 인터페이스  (0) 2022.04.07
        Java & Spring 버전 특징 정리  (0) 2022.04.04
        객체의 래퍼클래스(WrapperClass)  (0) 2021.09.16
        IntelliJ IDEA 단축키  (0) 2021.01.28

        목차

          자바 스트림의 종류

          스트림에 대한 내용은 크게 세 가지로 나눌 수 있습니다.

          1. 생성하기 : 스트림 인스턴스 생성.
          2. 가공하기 : 필터링(filtering) 및 맵핑(mapping) 등 원하는 결과를 만들어가는 중간 작업(intermediate operations).
          3. 결과 만들기 : 최종적으로 결과를 만들어내는 작업(terminal operations).
          • 생성하기
            • 배열 / 컬렉션 / 빈 스트림
            • Stream.builder() / Stream.generate() / Stream.iterate()
            • 기본 타입형 / String / 파일 스트림
            • 병렬 스트림 / 스트림 연결하기
          • 가공하기
            • Filtering
            • Mapping
            • Sorting
            • Iterating
          • 결과 만들기
            • Calculating
            • Reduction
            • Collecting
            • Matching
            • Iterating

          자바 Streams

          자바 8에서 추가한 스트림(Streams)은 람다를 활용할 수 있는 기술 중 하나입니다. 자바 8 이전에는 배열 또는 컬렉션 인스턴스를 다루는 방법은 for 또는 foreach 문을 돌면서 요소 하나씩을 꺼내서 다루는 방법이었습니다. 간단한 경우라면 상관없지만 로직이 복잡해질수록 코드의 양이 많아져 여러 로직이 섞이게 되고, 메소드를 나눌 경우 루프를 여러 번 도는 경우가 발생합니다.

          스트림은 '데이터의 흐름’입니다. 배열 또는 컬렉션 인스턴스에 함수 여러 개를 조합해서 원하는 결과를 필터링하고 가공된 결과를 얻을 수 있습니다. 또한 람다를 이용해서 코드의 양을 줄이고 간결하게 표현할 수 있습니다. 즉, 배열과 컬렉션을 함수형으로 처리할 수 있습니다.

          또 하나의 장점은 간단하게 병렬처리(multi-threading)가 가능하다는 점입니다. 하나의 작업을 둘 이상의 작업으로 잘게 나눠서 동시에 진행하는 것을 병렬 처리(parallel processing)라고 합니다. 즉 쓰레드를 이용해 많은 요소들을 빠르게 처리할 수 있습니다.

           

          자바 스트림 사용에서 주의할 점

          지금까지 스트림을 생성하는 방법과 필요에 맞게 가공하는 중간 연산(Intermediate Operations) 그리고 원하는 결과를 구하는 단말 연산(Terminal Operations)에 대해서 알아보았고 스트림을 활용하는 여러 가지 예제도 살펴보았습니다.

          이전보다 더 간결한 코드를 얻게 되었고 비교적 최신 기술을 적용했기 때문에 스트림 API를 활용하는 것이 더 좋아 보일 수 있습니다. 하지만 경우에 따라서 다를 수 있고 오히려 성능이 더 좋지 않을 수 있습니다.

          이번 포스팅에서는 스트림 API를 사용할 때 주의할 부분과 적용할 때 고민해보면 좋을 포인트를 정리해봅시다.

           

          스트림 재사용

          흔하게 접할 수 있는 실수 하나는 스트림을 재사용하는 것입니다. 스트림을 컬렉션처럼 사용했다가 적지않게 겪은 것 같습니다.

          // 문자열 스트림 생성
          Stream<String> langNames = Stream.of("Java", "C++", "Python", "Ruby");
          
          // 스트림 내 모든 요소 출력
          langNames.forEach(System.out::println);
          
          // "Java" 만 제외한 스트림을 다시 생성... Exception이 발생한다.
          Stream<String> filtered = langNames.filter(lang -> !lang.equals("Java"));
          filtered.forEach(System.out::println);
          

           

          위 코드를 실행하면 IllegalStateException이 발생합니다.

          java.lang.IllegalStateException: stream has already been operated upon or closed

          스트림은 오직 한 번만 소비될 수 있기 때문에 사용한 이후에 다시 사용하는 경우 에러를 발생시킬 수 있습니다.

           

          스트림은 무조건 좋다?

          기존의 배열이나 컬렉션을 반복하는 for-loop 문장을 스트림의 foreach로 변경하는 경우도 많습니다. 하지만 성능을 비교해보면 무조건적으로 스트림이라고 성능적으로 빠른 것은 아닙니다.

          10만개의 랜덤값이 들어있는 배열에서 가장 큰 값을 찾는 코드가 있습니다. 첫 번째는 기본 for-loop를 이용하여 찾고 두 번째 방법으로 Stream을 이용해서, 마지막으로 병렬 수행이 가능한 Parallel Stream으로 수행했을 때의 수행 속도 차이를 비교해봅시다.

          // new Random().nextInt로 초기화된 배열로 가정
          int[] intArray;
          
          // Case 1: for-loop
          int maxValue1 = Integer.MIN_VALUE;
          for (int i = 0; i < intArray.length; i++) {
              if (intArray[i] > maxValue) {
                  maxValue1 = intArray[i];
              }
          }
          
          // Case 2: Stream
          int maxValue2 = Arrays.stream(intArray)
              .reduce(Integer.MIN_VALUE, Math::max);
          
          // Case 3: Parallel Stream
          int maxValue3 = Arrays.stream(intArray).parallel()
              .reduce(Integer.MIN_VALUE, Math::max);    
          
          측정 시간은 각 Case에 수행 전과 후에 System.nanoTime()로 측정했으며, TimeUnit 클래스를 사용하여 밀리초 단위로 변환했습니다.
          // nanoseconds to milliseconds
          TimeUnit.MILLISECONDS.convert((endTime - startTime), TimeUnit.NANOSECONDS)
          
           

          출력 결과는 어떻게 나올까요? 기본 for-loop를 사용했을 때 가장 빠릅니다.

          for-loop: 8ms
          Stream: 123ms
          Parallel Stream: 15ms
          
           

          관련 참고자료를 인용하면, 단순 for-loop의 경우 오버헤드가 없는 단순한 인덱스 기반 메모리 접근이기 때문에 Stream을 사용했을 때보다 더 빠르다고 합니다. (“It is an index-based memory access with no overhead whatsoever.”)

          또, 컴파일러의 관점에서 오랫동안 유지해온 for-loop의 경우 최적화를 할 수 있으나 반대로 비교적 최근에 도입된 스트림의 경우 for-loop와 같은 정교한 최적화를 수행하지 않습니다.

          가독성

          소제목은 ‘가독성(readability)’ 이지만 스트림을 사용하면서 주의해야 한다기보다는 조금 고민해볼만한 부분이라고 할 수 있습니다. 특히 다른 사람들과 같이 개발을 진행할 때 말이지요. 코드 리뷰를 하다보면 “코드가 라인 수가 줄어들었고 가독성이 좋아졌다.” 라고 하지만 이는 어떻게 보면 팀원 모두가 스트림 API 사용에 익숙해야 하는 조건이 필요합니다.

          개인적으로도 Java 8 버전 이상의 코드 스타일이 익숙하지 않아서 리뷰에 어려움이 있었던 적이…

          문자열이 들어있는 배열에서 특정 문자열을 찾고 출력하는 코드는 아래와 같이 두 가지 방법으로 작성할 수 있습니다.

          // array
          String[] languages = {"Java", "C", "Python", "Ruby", "C++", "Kotlin"};
                  
          // for-loop
          String result = "";
          for (String language : languages) {
              if (language.equals("Java")) {
                  result = language;
                  break;
              }
          }
          if(result != null && result != "") {
              System.out.println(result);
          }
          
          // Stream
          Arrays.stream(languages)
                  .filter(lang -> lang.equals("Java"))
                  .findFirst().ifPresent(System.out::println);
          
           

          자바8의 스트림을 이용하여 여러 if 조건문과 결과를 담는 부수적인 변수 할당도 사라졌습니다. 짧고 간결한 코드가 되는 것에는 전적으로 공감할 수 있습니다. 메서드의 네이밍도 명확하기 때문에 이해하기도 쉽습니다. 물론 이부분은 앞서 말한 것처럼 스트림 API에 익숙해야하며 개인차이가 있을 수도 있습니다.

          무한 스트림

          기존에 생성된 배열이나 컬렉션을 통해서 스트림을 생성하는 경우에는 이슈가 없을 수 있으나, 특정 조건에 따라서 스트림을 생성하는 경우에는 무한 스트림(Infinite Streams)이 생성될 수 있습니다. 심지어 요소 개수에 제한을 걸었음에도 불구하고요.

          Stream.iterate(0, i -> (i + 1) % 2)
                  .distinct()
                  .limit(10)
                  .forEach(System.out::println);
          System.out.println("코드 실행 종료");
          
           

          위 코드의 각 라인을 설명하면 아래와 같습니다.

          • iterate: 2로 나머지 연산을 했으므로 0과 1을 반복적으로 생성합니다.
          • distinct: 각각 단일 0과 1을 유지합니다.
          • limit: 10개의 스트림 크기 제한이 생깁니다.
          • forEach: 생성된 스트림 요소를 모두 출력합니다.

          위 코드에서 distinct 연산자는 스트림을 생성하는 iterate 메서드에서 0과 1만 생성된다는 것을 알지 못합니다. 따라서 요소의 개수를 10개로 제한하는 limit 연산에 도달할 수 없습니다.

          그러므로 이어지는 forEach를 이용한 요소 출력도 진행되지 않으며, 모든 코드가 종료된 것을 출력하는 문장도 수행되지 않습니다. 코드는 종료되지 않고 계속 리소스를 차지하게 됩니다.

           

          변수 접근

          스트림을 이용하면서 람다(lambda) 또는 메서드 참조(method references)를 사용하는 경우에는 지역 변수(local variables)에 접근할 수 없습니다.

          int sumForLambda = 0;
          for (int i = 0; i < 5; i++) {
              // it works!
              sumForLambda += i;
          }
          
          int sumForloop = 0;
          IntStream.range(0, 5).forEach(i -> {
              // compile error
              sumForloop += i;
          });
           

          그리고 스트림의 파이프라인에서 연결된 각 단계의 값들에 접근할 수 없습니다. peek 메서드로 연산 사이의 결과를 확인하고 싶지만 불가능합니다.

          Arrays.stream(array)
                  .filter(first -> first % 2 == 0)
                  .filter(second -> second > 3)
                  .peek(value -> {
                      // compile error, can't access second filter's variable
                      int printValue = value + second;
                      System.out.println(printValue);
                  })
                  .sum();
           

          참고로 peek 메서드의 경우 스트림의 결과를 구하는 단말 연산(Terminal Operations)이 실행되지 않으면 메서드 자체가 실행되지 않습니다. 위의 예제에서는 sum 메서드가 단말 연산으로 실행되었습니다.

          스트림의 동작 순서

          스트림을 사용할 때는 동작 방식을 이해할 필요가 있습니다. 아래와 같이 3개의 요소를 가진 배열로 스트림 연산을 수행하는 코드가 있습니다. 그리고 연결된 각 메서드마다 출력문으로 메서드가 실행되었는지 확인합니다.

          Arrays.stream(new String[]{"c", "python", "java"})
                  .filter(word -> {
                      System.out.println("Call filter method: " + word);
                      return word.length() > 3;
                  })
                  .map(word -> {
                      System.out.println("Call map method: " + word);
                      return word.substring(0, 3);
                  }).findFirst();
          
           

          결과는 어떻게 나올까요? 3개의 요소에 대해서 메서드를 수행하므로 각각 3번의 호출이 이뤄질 것 같지만, 출력 결과는 그렇지 않습니다.

          Call filter method: c
          Call filter method: python
          Call map method: python
          
           

          위 결과를 통해서 스트림의 동작 방식을 유추할 수 있는데요. 스트림 내의 모든 요소가 중간 연산인 filter 메서드를 수행하는 것이 아니라 요소 하나씩 모든 파이프라인을 수행합니다.

          따라서 조금 더 자세히 살펴보면 아래와 같이 단계적으로 수행됩니다.

          • 배열의 첫 번째 요소 “c”
            • filter 메서드가 실행되지만 length가 3보다 크지 않으므로 다음으로 진행되지는 않음
            • “Call filter method: c” 출력
          • 배열의 두 번째 요소 “python”
            • filter 메서드가 실행되며 length가 3보다 크므로 다음으로 진행됨
            • “Call filter method: python” 출력
            • map 메서드에서 substring 메서드를 수행합니다.
            • “Call map method: python” 출력
            • 마지막 연산인 findFirst가 수행됩니다.
          • 조건에 맞는 하나의 결과를 찾았기 때문에 다음 요소인 “java”에 대해 연산을 수행하지 않습니다.
            • 최종 결과는 “pyth” 입니다.

          성능 개선

          이러한 특성을 잘 이용하면 스트림의 성능을 조금 더 개선할 수 있습니다. 아래와 같이 배열의 모든 문자열 요소를 대문자로 변환하는 코드가 있습니다. 그리고 수행된 결과에서 2개를 생략하여 리스트로 만듭니다.

          Arrays.stream(new String[]{"c", "python", "java"})
                  .map(word -> {
                      System.out.println("Call map method: " + word);
                      return word.toUpperCase();
                  })
                  .skip(2)
                  .collect(Collectors.toList());
          
          // Call map method: c
          // Call map method: python
          // Call map method: java
           

          map 메서드가 총 3번 호출되는 것을 알 수 있습니다. 여기서 메서드의 실행 순서를 변경하면 어떻게 될까요?

          Arrays.stream(new String[]{"c", "python", "java"})
                  .skip(2)
                  .map(word -> {
                      System.out.println("Call map method: " + word);
                      return word.toUpperCase();
                  })
                  .collect(Collectors.toList());
          
          // Call map method: java
          
           

          물론 filter 메서드와 같은 특정 조건을 추가해서 사용할 때는 skip 메서드의 위치에 따라서 결과가 달라질 수 있으므로 주의해야 합니다.

          List<String> list = Arrays.stream(new String[]{"abc", "abcd", "abcde", "abcdef"})
                  .filter(word -> word.length() > 3)
                  .map(word -> word.toUpperCase())
                  .skip(2)
                  .collect(Collectors.toList());
                  
          // ABCDEF 
          list.forEach(System.out::println);
          
          List<String> list2 = Arrays.stream(new String[]{"abc", "abcd", "abcde", "abcdef"})
                  .skip(2)
                  .filter(word -> word.length() > 3)
                  .map(word -> word.toUpperCase())
                  .collect(Collectors.toList());
                  
          // ABCDE
          // ABCDEF 
          list2.forEach(System.out::println);

          참고

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

           

          Java 스트림 Stream (1) 총정리

          이번 포스트에서는 Java 8의 스트림(Stream)을 살펴봅니다. 총 두 개의 포스트로, 기본적인 내용을 총정리하는 이번 포스트와 좀 더 고급 내용을 다루는 다음 포스트로 나뉘어져 있습니다. Java 스트

          futurecreator.github.io

           

          목차

            Spring 버전 별 특징

            • spring 3.0
            • spring 4.0
            • spring 5.0

            Spring 3.0

            spring 3.0 부터 java 5 가 지원된다. 기존에 유지하던 하위호환성에 Generic이나 가변인자(varargs) 등과 같은 개선사항이 추가된다.

            • 전체 프레임 워크를 하나의 spring.jar 파일로 제공하던 부분을 여러개의 jar 파일로 나누어 제공한다.
            • SPEL(Spring Expression Language)가 도입되었다.
            • Rest API에 대한 지원이 추가 되었다.
            • xml 지원
            • Java annotation을 이용해서 DI의 지원
            • Java 5의 기능(제네릭, 가변 매개변수 등)을 사용하여 개정되었다.
              • 이로 인해서 BeanFactory 등 핵심 API가 업데이트 되었다.
            • @Async 주석을 통해 비동기 메서드 호출을 지원하기 시작했다.
            • 하나의 Spring.jar로 제공하던 것을 여러 Spring 모듈의 jar 파일로 나누어 제공하기 시작했다.
              (ex : spring-core, spring-web 등)
            • SPEL(Spring Expression Language) 가 도입되어 XML및 Annotation 기반 Bean정의에서 사용할 수 있게 되었다.
              • 이로 인해서 외부 프로퍼티 파일이나 환경변수에서 값을 가져오기 쉬워졌다.
            • Java 클래스로부터 @Configuration, @Bean 등의 Annotation을 사용해서 직접 메타 데이터를 설정하고, DI 지원을 받을 수 있다.
            • OXM(Object Xml Mapping)을 사용하여 Bean을 XML형태로 관리할 수 있게 되었다.
            • Rest API 에 대한 지원이 추가되었다.
              • 서버로서는 기존 MVC Framework 레벨에서 Annotation 기반 확장이 추가되었다.
              • 클라이언트로서는 RestTemplate 을 추가해 지원한다.
            • H2등의 Embeded Database를 지원한다.
            • 2016년 12월 31일부로 개발 및 지원이 종료되었다.

            Spring 4.0

            spring 3.0 이 java 5+ 버전들에 대해 지원을 한다면 spring 4.0 버전은 java 8의 특징을 적용할 수 있게 지원한다.

            • POM 설정을 도와준다.
            • Hibernate 3.6이상, EhCache 2.1 이상, Groovy 1.8이상, Joda-Time 2.0 이상 새로운 Dependency 지원
            • java6, java7, java8 의 고유 기능 지원 
              • 람다식, Optional, Callback, Interface 등의 기능을 스프링 레벨에서 사용가능
            • Java EE6, 7 에 대한 고려, JPA 2.0과 Servelet 3.0에 대한 지원이 포함
            • Groovy를 이용한 Bean 설정이 가능하다.
            • Core 컨테이너들의 기능 지원이 확대되었다. 먼저 Repository 들이 더 쉽게 Inject 될 수 있으며, 각종 Metadata Annotation 들을 이용한 Custom Annotation 작성이 가능하다. 
            • Web 개발 하기 위한 도구들이 생김. @RestController
            • Web Socket이나 STOMP 등의 프로토콜을 같이 지원한다.
            • 테스트 환경이 개선되었다.
            • Core Container 들의 기능 지원이 확대되었다.
              • 예를 들어, Spring Data Repository 를 사용하고 있다면 간단한 구현으로 주입할 수 있다. (@Autowired Repository<Customer> customerRepository)
              • meta-annotation 지원과 함께 custom-annotation 을 만들 수 있다.
              • Bean 관리가 더 용이해졌다.
                • @Order 어노테이션을 통해 배열과 리스트 형태의 Bean을 정렬 할 수 있다.
                • @Lazy 어노테이션을 통해 Lazy Injection이 가능하다.

            Spring 5.X 

            spring 5.X은 JDK 8+, 9등에 대해서 지원하며 Java8을 표준으로 사용한다.

            • 전체 프레임워크가 Java8을 기반 소스코드로 삼으며, 제네릭과 람다 등을 통해 가독성을 향상 되었다.
            • JDK 9와도 완벽 호환된다.
            • Jackson2.9, Protobuf 3, Reactor 3.1과의 호환 추가
            • Spring WebFlux 추가, 비동기와 넌 블로킹 이벤트 루프 모델 사용가능
            • Kotlin 지원
            • Junit 5 지원
            • 5.0.x 버전은 2020년 10월까지 지원되며, 5.1.x 버전과 5.2.x 버전은 각각 20년 10월, 21말까지 확발히 개발될 것이다. 

            Java 버전 별 특징 

            java 8 - 17 특징

            Java 8

            Java 8은 대규모 릴리스였다. 

            • Lambda
            • stream
            • interface default method
            • Optinal
            • new Data and Time API(LocalDateTime)

            Lambda

            Java 8 이전 익명 클래스의 사용을 람다를 이용하여 더욱 간결하고 직관적으로 구현

            before

            Runnable runnable = new Runnable(){
               @Override
               public void run(){
                 System.out.println(*"Hello world !"*);
               }
             };

            after

            Runnable runnable = () -> System.out.println(*"Hello world two!"*);

            Stream

            Java 8은 스트림 API를 통해 컬렉션을 처리하면서 발생하는 모호함과 반복적인 코드 문제와 멀티코어 활용 어려움이라는 두 가지 모두 해결

            List<String> list = Arrays.asList(*"franz"*, *"ferdinand"*, *"fiel"*, *"vom"*, *"pferd"*);
            list.stream()
                .filter(name -> name.startsWith(*"f"*))
                .map(String::toUpperCase)
                .sorted()
                .forEach(System.out::println);

            Java 9 

            Java 9는 다음과 같은 몇 가지 추가 사항이 포함된 상당히 큰 릴리즈

            모듈 시스템 등장 및 try-with-resources 문 또는 다이아몬드 연산자(<>) 확장, HTTP클라이언트와 같은 몇 가지 다른 개선 사항 존재

            컬렉션

            컬렉션에는 list, set, map을 쉽게 구성할 수 있는 몇 가지 추가 기능

            List<String> list = List.of(*"one"*, *"two"*, *"three"*);
            Set<String> set = Set.of(*"one"*, *"two"*, *"three"*);
            Map<String, String> map = Map.of(*"foo"*, *"one"*, *"bar"*, *"two"*);

            스트림

            takeWhile, dropWhile, iterate 메서드의 형태로 몇 가지 추가 기능

            Stream<String> stream = Stream.iterate(*""*, s -> s + *"s"*)
              .takeWhile(s -> s.length() < 10);

            Java 10

            가비지 컬렉션 등과 같은 Java 10에 몇 가지 변경 사항이 존재

            개발자로서 보게 될 유일한 실제 변경 사항은 로컬 변수 유형 추론이라고도 하는 "var" 키워드의 도입

            • var 키워드
            • 병렬 처리 가비지 컬렉션 도입으로 인한 성능 향상
            • JVM 힙 영역을 시스템 메모리가 아닌 다른 종류의 메모리에도 할당 가능

            지역 변수 유형 추론: var-keyword

            // Pre-Java 10
            String myName = "Marco";
            
            // With Java 10
            var myName = "Marco"

            Java 11

            Java 11은 개발자의 관점에서 볼 때 약간 작은 릴리스

            • Oracle JDK와 OpenJDK 통합
            • Oracle JDK가 구독형 유료 모델로 전환
            • 서드파티 JDK 로의 이전 필요
            • lambda 지역변수 사용법 변경
            • 기타 추가

            Strings & Files

            Strings and Files에는 몇 가지 새로운 메서드 추가

            *"Marco"*.isBlank();
            *"Mar\nco"*.lines();
            *"Marco  "*.strip();
            
            Path path = Files.writeString(Files.createTempFile(*"helloworld"*, *".txt"*), *"Hi, my name is!"*);
            String s = Files.readString(path);

            Run Source Files

            Java 10부터 Java 소스 파일 을 먼저 컴파일 하지 않고도 실행할 수 있다. 스크립팅을 향한 한 걸음

            ubuntu@DESKTOP-168M0IF:~$ java MyScript.java

            Java 12

            Java 12에는 몇 가지 새로운 기능과 정리가 포함 되어 있지만

            • 언급할 가치가 있는 것은 유니코드 11 지원과 새로운 스위치 표현식의 preview 뿐

            Java 13

            여기 에서 전체 기능 목록을 찾을 수 있지만 기본적으로 유니코드 12.1 지원과 두 가지 새롭거나 개선된 preview 기능(향후 변경될 수 있음)이 제공

            스위치 표현식(preview)

            이제 스위치 표현식이 값을 반환 가능하며 fall-through/break 문제 없이 표현식에 람다 스타일 구문을 사용 가능

            before

            switch(status) {
              case SUBSCRIBER:
                *// code block*break;
              case FREE_TRIAL:
                *// code block*break;
              default:
                *// code block*}

            after(람다 스타일)

            boolean result = switch (status) {
                case SUBSCRIBER -> true;
                case FREE_TRIAL -> false;
                default -> throw new IllegalArgumentException(*"something is murky!"*);
            };

            Multiline Strings (Preview)

            \n 같은 표현 안써도 인식함

             

            String htmlBeforeJava13 = *"<html>\n"* +
                          *"    <body>\n"* +
                          *"        <p>Hello, world</p>\n"* +
                          *"    </body>\n"* +
                          *"</html>\n"*;
            String htmlWithJava13 = *"""
                          <html>
                              <body>
                                  <p>Hello, world</p>
                              </body>
                          </html>
                          """*;

            Java 14

            • 스위치 표현시 표준화
            • instanceof 패턴 매칭 (preview)
            • record (data object) 선언 기능 추가 (preview)

            스위치 표현(Standard)

            버전 12 및 13에서 preview 였던 스위치 표현식 이 이제 표준화 되었다.

            int numLetters = switch (day) {
                case MONDAY, FRIDAY, SUNDAY -> 6;
                case TUESDAY                -> 7;
                default      -> {
                  String s = day.toString();
                  int result = s.length();
                  yield result;
                }
            };

            record(preview)

            Java로 많은 상용구를 작성하는 고통을 완화하는 데 도움이 되는 레코드 클래스가 있다.

            데이터, (잠재적으로) getter/setters, equals/hashcode, toString만 포함하는 이 Java 14 이전 클래스

            final class Point {
                public final int x;
                public final int y;
            
                public Point(int x, int y) {
                    this.x = x;
                    this.y = y;
                }
            }
                *// state-based implementations of equals, hashCode, toString// nothing else*

            레코드를 사용

            record Point(int x, int y) { }

            유용한 NullPointerExceptions

            마지막으로 NullPointerExceptions는 정확히 어떤 변수가 null 인지 설명한다 .

            author.age = 35;
            ---
            Exception in thread *"main"* java.lang.NullPointerException:
                 Cannot assign field *"age"* because *"author"* is null

            Pattern Matching For InstanceOf (Preview)

            이전에는 다음과 같이 instanceof 내부에서 객체를 캐스팅 필요

            if (obj instanceof String) {
                String s = (String) obj;
                *// use s*}

            이제 이 작업을 수행하여 캐스트를 효과적으로 삭제 가능

            if (obj instanceof String s) {
                System.out.println(s.contains(*"hello"*));
            }

            Java 15

            • Text-Blocks / Multiline Strings
            • Records & Pattern Matching(2차 preview, 상단 Java 14 참조)
            • 스케일링 가능한 낮은 지연의 가비지 컬렉터 추가(ZGC)
            • 레코드 (2차 preview, 상단 Java 14 참조)
            • Sealed Classes - Preview
            • Nashorn JavaScript Engine 제거

            Text-Blocks / Multiline Strings

            Java 13의 실험 기능으로 도입된 여러 줄 문자열은 이제 프로덕션 준비 완료

            String text = *"""
                            Lorem ipsum dolor sit amet, consectetur adipiscing \
                            elit, sed do eiusmod tempor incididunt ut labore \
                            et dolore magna aliqua.\
                            """*;

            Sealed Classes - Preview

            • 상속 가능한 클래스를 지정할 수 있는 봉인 클래스가 제공된다.
            • 상속 가능한 대상은 상위 클래스 또는 인터페이스 패키지 내에 속해 있어야 한다.
            public abstract sealed class Shape
                permits Circle, Rectangle, Square {...}

            즉, 클래스가 public인 동안 하위 클래스로 허용되는 유일한 Shape 클래스들은 Circle, Rectangle 및 Square 이다.

             

            Java 16

            • Pattern Matching for instanceof
            • Unix-Domain Socket Channels
            • Foreign Linker API - Preview
            • Records & Pattern Matching

            Unix-Domain Socket Channels

            이제 Unix 도메인 소켓에 연결할 수 있다(macOS 및 Windows(10+)에서도 지원됨).

             socket.connect(UnixDomainSocketAddress.of(
                    *"/var/run/postgresql/.s.PGSQL.5432"*));

            Foreign Linker API - Preview

            JNI(Java Native Interface)에 대한 계획된 교체로, 기본 라이브러리에 바인딩할 수 있다(C 생각).

             

            Java 17

            Java 17은 Java 11 이후의 새로운 Java LTS(장기 지원) 릴리스

            • Pattern Matching for switch (Preview)
            • Sealed Classes (Finalized)
            • Foreign Function & Memory API (Incubator)
            • Deprecating the Security Manager

             

            Pattern Matching for switch (Preview)

            이제 객체를 전달하여 기능을 전환하고 특정 유형을 확인할 수 있다.

            public String test(Object obj) {
            
                return switch(obj) {
            
                case Integer i -> *"An integer"*;
            
                case String s -> *"A string"*;
            
                case Cat c -> *"A Cat"*;
            
                default -> *"I don't know what it is"*;
            
                };
            
            }

            Sealed Classes (Finalized)

            Java 15에서 preview 제공되었던 기능 완료

            Foreign Function & Memory API (Incubator)

            Java Native Interface(JNI)를 대체한다. 기본 함수를 호출하고 JVM 외부의 메모리에 액세스할 수 있다. 지금은 C가 C++, 포트란과 같은 추가 언어 지원 계획을 가지고 있다고 생각

            Deprecating the Security Manager

            자바 1.0 이후로 보안 관리자가 존재해 왔었지만 현재는 더 이상 사용되지 않으며 향후 버전에서는 제거될 예정

             

            Spring Boot

            Spring Boot 1.1 (Release 2014.05)

            • Java 1.6 이상
            • Spring Framework 4.0.5
            • Tomcat 7.0.54, Hibernate 4.3.1

            Spring Boot 1.5 (Release 2017.01)

            • Java 8 이상
            • Spring Framework 4.3
            • Tomcat 8.5, Hibernate 5.0
            • ConfigurationProperties 에 JSR303 지원

            Spring Boot 2.0.0 (Release 2018.03)

            • Java 8 , Java 9 tested
            • Spring Framework 5.0
            • Tomcat 8.5, Hibernate 5.2

            Spring Boot 2.1.0(Release 2018.10)

            • Java 11 Support (Java 8 ~ 11)
            • Spring Framework 5.1
            • Tomcat 9, Hibernate 5.3

             

             

            참고

            https://velog.io/@ljo_0920/java-%EB%B2%84%EC%A0%84%EB%B3%84-%EC%B0%A8%EC%9D%B4-%ED%8A%B9%EC%A7%95

             

            'Java' 카테고리의 다른 글

            [Java] Java 8 에서의 변경사항  (0) 2022.05.05
            추상클래스 vs 인터페이스  (0) 2022.04.07
            GC (Garbage Collector)  (0) 2022.04.06
            객체의 래퍼클래스(WrapperClass)  (0) 2021.09.16
            IntelliJ IDEA 단축키  (0) 2021.01.28

            목차 돌아가기

            목차

              퍼사드(Facade)란?

              • 퍼사드 : 건물의 입구쪽 전경이라는 뜻으로 외벽에선 안이 어떻게 생겼는지 알 수없다는 의미

              퍼사드(Facade) 패턴

              복잡한 서브 시스템 의존성을 최소화하는 방법

              클라이언트가 사용해야 하는 복잡한 서브 시스템 의존성을 간단한 인터페이스로 추상화 할 수 있다.

              레거시 코드

              package com.patten.degine_patten.facade._01_before;
              
              import javax.mail.Message;
              import javax.mail.MessagingException;
              import javax.mail.Session;
              import javax.mail.Transport;
              import javax.mail.internet.InternetAddress;
              import javax.mail.internet.MimeMessage;
              import java.util.Properties;
              
              /**
               * 실제로 동작하지 않는 코드
               * 가장 간단한 이메일을 보내는 소스 코드
               * 특정한 자바가 제공하는 라이브러리에 의존성이 많은 코드 : SOLID 등 자바가 지향하는 것과 다른 느낌
               */
              public class Client {
              
                  public static void main(String[] args) {
                      String to = "keesun@whiteship.me";
                      String from = "whiteship@whiteship.me";
                      String host = "127.0.0.1";
              
                      Properties properties = System.getProperties();
                      properties.setProperty("mail.smtp.host", host);
              
                      Session session = Session.getDefaultInstance(properties);
              
                      try {
                          MimeMessage message = new MimeMessage(session);
                          message.setFrom(new InternetAddress(from));
                          message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
                          message.setSubject("Test Mail from Java Program");
                          message.setText("message");
              
                          Transport.send(message);
                      } catch (MessagingException e) {
                          e.printStackTrace();
                      }
                  }
              }

              해당 코드에서 java로 이메일을 보내는 코드를 작성한 것이다. 하지만 해당 코드는 너무 javax.mail 이라는 인터페이스에 의존적이다. 

              퍼사드 패턴 적용 추상화 고민하기

              • 메일을 보내는(sender) 클래스
              • 메일 설정
              • 메일 메시지

              이메일을 보내는(sender)

              센더가 의존을 담당한다. (이게 뭐하는 거지..? 조삼모사 아니냐?) 아니다. 의미가 있다. send하는 기능이 여러 곳에서 한다? 약간 util 성으로 의미가 생기게된다.

              package com.patten.degine_patten.facade._02_after;
              
              import javax.mail.Message;
              import javax.mail.MessagingException;
              import javax.mail.Session;
              import javax.mail.Transport;
              import javax.mail.internet.InternetAddress;
              import javax.mail.internet.MimeMessage;
              import java.util.Properties;
              
              public class EmailSender {
              
                  private EmailSettings emailSettings;
              
                  public EmailSender(EmailSettings emailSettings) {
                      this.emailSettings = emailSettings;
                  }
              
                  /**
                   * 이메일 보내는 메소드
                   * @param emailMessage
                   */
                  public void sendEmail(EmailMessage emailMessage) {
                      Properties properties = System.getProperties();
                      //properties.setProperty("mail.smtp.host", host);
                      properties.setProperty("mail.smtp.host", emailSettings.getHost()); //host정보는 setting에
              
                      Session session = Session.getDefaultInstance(properties);
              
                      try {
                          MimeMessage message = new MimeMessage(session);
                          //message.setFrom(new InternetAddress(from));
                          message.setFrom(new InternetAddress(emailMessage.getFrom())); //from 정보는 메시지에
                          //message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
                          message.addRecipient(Message.RecipientType.TO, new InternetAddress(emailMessage.getTo())); //to 정보는 메시지에
                          message.addRecipient(Message.RecipientType.CC, new InternetAddress(emailMessage.getCc()));
                          message.setSubject(emailMessage.getSubject());
                          //message.setText("message");
                          message.setText(emailMessage.getText());
              
                          Transport.send(message);
                      } catch (MessagingException e) {
                          e.printStackTrace();
                      }
                  }
              
              
              }

              이메일 메시지

              가장 많은 부분을 담당하는데 이메일에 필요한 내용과 관련된 정보를 담당한다.

              public class EmailMessage {
              
                  private String from;
                  private String to;
                  private String cc;
                  private String bcc;
                  private String subject;
                  private String text;
              
                  public String getFrom() {
                      return from;
                  }
              
                  public void setFrom(String from) {
                      this.from = from;
                  }
              
                  public String getTo() {
                      return to;
                  }
              
                  public void setTo(String to) {
                      this.to = to;
                  }
              
                  public String getSubject() {
                      return subject;
                  }
              
                  public void setSubject(String subject) {
                      this.subject = subject;
                  }
              
                  public String getText() {
                      return text;
                  }
              
                  public void setText(String text) {
                      this.text = text;
                  }
              
                  public String getCc() {
                      return cc;
                  }
              
                  public void setCc(String cc) {
                      this.cc = cc;
                  }
              
                  public String getBcc() {
                      return bcc;
                  }
              
                  public void setBcc(String bcc) {
                      this.bcc = bcc;
                  }
              }

              이메일 설정

              host같은 기본 이메일 설정을 담당한다.

              public class EmailSettings {
              
                  private String host;
              
                  public String getHost() {
                      return host;
                  }
              
                  public void setHost(String host) {
                      this.host = host;
                  }
              }

              이메일 메인

              기존과 변화를 살펴보면 메인에서 import 했던 부분들이 사라졌다. 바로 import를 안하지만, 퍼사드에 의존하게 된다.

              public class Client {
              
                  public static void main(String[] args) {
                      EmailSettings emailSettings = new EmailSettings();
                      emailSettings.setHost("127.0.0.1");
              
                      EmailSender emailSender = new EmailSender(emailSettings);
              
                      EmailMessage emailMessage = new EmailMessage();
                      emailMessage.setFrom("keesun");
                      emailMessage.setTo("whiteship");
                      emailMessage.setCc("일남");
                      emailMessage.setSubject("오징어게임");
                      emailMessage.setText("밖은 더 지옥이더라고..");
              
                      emailSender.sendEmail(emailMessage);
                  }
              }

              퍼사드(Facade) 패턴의 장점과 단점

              복잡한 서브 시스템 의존성을 최소화하는 방법

              장점

              서브 시스템에 대한 의존성을 한곳으로 모을 수 있다. 결국 코드 읽는게 더 편해지는 것이다. 즉 추상화 정도가 강해진다.

              또한 진입 장벽이 낮다.

              단점

              퍼사드 클래스가 서브 시스템에 대한 모든 의존성을 가지게 된다.

               

              퍼사드 패턴 예제 - Spring

              패턴이 적용 되었다는 것은 보는 각도에 따라 다르게 보일 수 있다. 해당 예제는 브릿지패턴의 예제랑 같지만 보는 각도에 따라 퍼사드라고 볼 수 있다.

              package com.patten.degine_patten.facade._03_java;
              
              import org.springframework.jdbc.support.JdbcTransactionManager;
              import org.springframework.mail.MailSender;
              import org.springframework.mail.javamail.JavaMailSenderImpl;
              import org.springframework.transaction.PlatformTransactionManager;
              
              public class FacadeInSpring {
              
                  public static void main(String[] args) {
                      MailSender mailSender = new JavaMailSenderImpl();
              
                      PlatformTransactionManager platformTransactionManager = new JdbcTransactionManager();
                  }
              }

               

              'Java > *****디자인패턴' 카테고리의 다른 글

              [싱글톤 패턴] Spring에서 싱글톤을 사용하는 이유  (0) 2022.05.05
              빌더 패턴  (0) 2022.04.01
              싱글톤 (Singleton) 패턴  (0) 2022.04.01
              디자인 패턴  (0) 2022.03.31
              디자인 패턴 싱글톤 패턴 개념 정리  (0) 2022.03.18

              목차

                빌더 패턴 이란?

                동일한 프로세스를 거쳐 다양한 구성의 인스턴스를 만드는 방법

                빌더 패턴을 사용하면 객체의 생성을 깔금하고 유연하게 할 수 있다.

                Effective Java의 두번째 규칙의 제목이 바로 생성자의 인자가 많을 때는 빌더(Builder) 패턴을 이용하라

                빌더 패턴 아키텍처

                가장 중요한 부분은 Director 부분이며 클라이언트는 Director를 거친다.

                빌더 패턴 아닌 경우

                /**
                * 짧은 여행 : 짧은 여행이기 때문에 set해줄게 별로 없음
                */
                TourPlan shortTrip = new TourPlan();
                shortTrip.setTitle("오레곤 롱비치 여행");
                shortTrip.setStartDate(LocalDate.of(2021, 7, 15));
                
                /**
                * 긴 여행
                */
                TourPlan tourPlan = new TourPlan();
                tourPlan.setTitle("칸쿤 여행");
                tourPlan.setNights(2); //2박
                tourPlan.setDays(3);   //3일
                tourPlan.setStartDate(LocalDate.of(2020, 12, 9));
                tourPlan.setWhereToStay("리조트");
                tourPlan.addPlan(0, "체크인 이후 짐풀기");
                tourPlan.addPlan(0, "저녁 식사");
                tourPlan.addPlan(1, "조식 부페에서 식사");
                tourPlan.addPlan(1, "해변가 산책");
                tourPlan.addPlan(1, "점심은 수영장 근처 음식점에서 먹기");
                tourPlan.addPlan(1, "리조트 수영장에서 놀기");
                tourPlan.addPlan(1, "저녁은 BBQ 식당에서 스테이크");
                tourPlan.addPlan(2, "조식 부페에서 식사");
                tourPlan.addPlan(2, "체크아웃");

                문제점

                • set 해줘야 하는 값이 꼬일 수있다.
                  • 2박 3일을 입력하기 위해선 setNights(2), setDays(3) 처럼 입력해야하는데 둘 중 하나를 빼 먹을 수 있다.
                • 생성자 초기 선언 할 때 너무 장황하다.
                • 그러다 보면 생성자를 조정할 수 있는데, 사용자 입장에서 어떤 생성자를 써야할지 모호해질 수 있다. 
                더보기

                초기 선언이 너무 장황한 예시

                 

                빌더 패턴 구현

                Interface - TourPlanBulder

                public interface TourPlanBuilder {
                
                    TourPlanBuilder nightsAndDays(int nights, int days);
                
                    TourPlanBuilder title(String title);
                
                    TourPlanBuilder startDate(LocalDate localDate);
                
                    TourPlanBuilder whereToStay(String whereToStay);
                
                    TourPlanBuilder addPlan(int day, String plan);
                
                    /**
                     * 데이터 검증을 할 수 있음.
                     * nights, days 중 하나만 들어갔다던지, 근교여행인데 굳이 필요없는 값이 들어간건 아닌지
                     * @return
                     */
                    TourPlan getPlan(); //데이터 검증
                }

                Class - DefalutTourBuilder

                /**
                 * 구현체 생성
                 */
                public class DefaultTourBuilder implements TourPlanBuilder {
                
                    private String title;
                
                    private int nights;
                
                    private int days;
                
                    private LocalDate startDate;
                
                    private String whereToStay;
                
                    private List<DetailPlan> plans;
                
                    /**
                     * this를 리턴한다는 것은 DefaultTourBulder가 리턴이 된다는 뜻
                     * @param nights
                     * @param days
                     * @return
                     */
                    @Override
                    public TourPlanBuilder nightsAndDays(int nights, int days) {
                        this.nights = nights;
                        this.days = days;
                        return this;
                    }
                
                    @Override
                    public TourPlanBuilder title(String title) {
                        this.title = title;
                        return this;
                    }
                
                    @Override
                    public TourPlanBuilder startDate(LocalDate startDate) {
                        this.startDate = startDate;
                        return this;
                    }
                
                    @Override
                    public TourPlanBuilder whereToStay(String whereToStay) {
                        this.whereToStay = whereToStay;
                        return this;
                    }
                
                    @Override
                    public TourPlanBuilder addPlan(int day, String plan) {
                        if (this.plans == null) {
                            this.plans = new ArrayList<>();
                        }
                
                        this.plans.add(new DetailPlan(day, plan));
                        return this;
                    }
                
                
                    /**
                     * 마지막에 인스턴스를 생성한다.
                     * @return
                     */
                    @Override
                    public TourPlan getPlan() {
                        return new TourPlan(title, nights, days, startDate, whereToStay, plans);
                    }
                }

                TourDirector

                public class TourDirector {
                
                    private TourPlanBuilder tourPlanBuilder;
                
                    public TourDirector(TourPlanBuilder tourPlanBuilder) {
                        this.tourPlanBuilder = tourPlanBuilder;
                    }
                
                    public TourPlan cancunTrip() {
                        return tourPlanBuilder.title("칸쿤 여행")
                                .nightsAndDays(2, 3)
                                .startDate(LocalDate.of(2020, 12, 9))
                                .whereToStay("리조트")
                                .addPlan(0, "체크인하고 짐 풀기")
                                .addPlan(0, "저녁 식사")
                                .getPlan();
                    }
                
                    public TourPlan longBeachTrip() {
                        return tourPlanBuilder.title("롱비치")
                                .startDate(LocalDate.of(2021, 7, 15))
                                .getPlan();
                    }
                }

                Main

                 public static void main(String[] args) {
                        TourDirector director = new TourDirector(new DefaultTourBuilder());
                        TourPlan tourPlan = director.cancunTrip();
                        TourPlan tourPlan1 = director.longBeachTrip();
                    }

                빌더 패턴 장점 & 단점

                장점

                • 빌더 패턴으로 생성자의 복잡함을 단순화 할 수 있다.
                •  

                단점

                자바와 스프링에서 찾아보는 패턴

                StringBuffer (Sync 사용)

                StringBuffer와 StringBuilder의 차이는 Sync(동기화)여부의 차이가 있다.

                 public static void main(String[] args) {
                        StringBuilder stringBuilder = new StringBuilder();
                        String result = stringBuilder.append("whiteship").append("keesun").toString();
                        System.out.println(result);
                }

                 

                StringBuilder (Sync 미사용)

                Stream.Builder

                UriComponents howToStudyJava = UriComponentsBuilder.newInstance()
                            .scheme("http")
                            .host("www.whiteship.me")
                            .path("java playlist ep1")
                            .build().encode();
                System.out.println(howToStudyJava);

                lombok

                @Builder
                public class LombokExample {
                
                    private String title;
                
                    private int nights;
                
                    private int days;
                
                    public static void main(String[] args) {
                        LombokExample trip = LombokExample.builder()
                                .title("여행")
                                .nights(2)
                                .days(3)
                                .build();
                    }
                
                }

                Spring

                   //URI를 만들어주는 Builder
                   public static void main(String[] args) {
                       UriComponents howToStudyJava = UriComponentsBuilder.newInstance()
                                .scheme("http")
                                .host("www.whiteship.me")
                                .path("java playlist ep1")
                                .build().encode();
                        System.out.println(howToStudyJava);
                    }

                 

                 

                 

                 

                 

                 

                 

                 

                 

                 

                 

                 

                 

                 

                 

                출처

                인프런

                김영한-코딩으로 학습하는 GoF 디자인 패턴

                + Recent posts