소소한개발팁
article thumbnail
반응형

개요

실무에서는 주로 자바 8 버전을 사용하였고 별도의 자바 버전을 업그레이드에서 사용하지 않았었는데 현재 사용하고 있는 스프링 부트 생성 사이트를 사용하던 중 기본 값이 17임을 확인하였고 이에 대해 업그레이드 된 자바의 기능들을 정리하고자 작성하였습니다. 해당 내용들은 ChatGpt 활용하여 정리하였습니다.

Java 8

람다 표현식(Lambda Expressions) 및 함수형 인터페이스(Functional Interfaces) 도입.

// 함수형 인터페이스 정의
interface MyFunction {
    int apply(int a, int b);
}

public class Main {
    public static void main(String[] args) {
        // 람다 표현식을 사용하여 함수형 인터페이스 구현
        MyFunction add = (a, b) -> a + b;
        MyFunction subtract = (a, b) -> a - b;

        // 함수형 인터페이스의 메서드 호출
        System.out.println("Addition: " + add.apply(5, 3)); // 출력: Addition: 8
        System.out.println("Subtraction: " + subtract.apply(5, 3)); // 출력: Subtraction: 2
    }
}

 

스트림 API(Stream API) 추가로 함수형 프로그래밍을 지원하고 컬렉션을 처리

import java.util.Arrays;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("John", "Jane", "Doe", "Alice", "Bob");

        // 스트림을 사용하여 컬렉션 처리
        names.stream()
             .filter(name -> name.startsWith("J"))
             .forEach(System.out::println);
    }
}

 


기본 메서드(Default Methods)와 정적 메서드(Static Methods)를 인터페이스에 추가

interface MyInterface {
    // 기본 메서드
    default void defaultMethod() {
        System.out.println("Default method implementation");
    }

    // 정적 메서드
    static void staticMethod() {
        System.out.println("Static method implementation");
    }
}

public class Main implements MyInterface {
    public static void main(String[] args) {
        Main obj = new Main();
        obj.defaultMethod(); // 출력: Default method implementation
        MyInterface.staticMethod(); // 출력: Static method implementation
    }
}

 

날짜와 시간을 다루는 java.time 패키지 추가

import java.time.LocalDate;

public class Main {
    public static void main(String[] args) {
        // 현재 날짜 구하기
        LocalDate currentDate = LocalDate.now();
        System.out.println("Current date: " + currentDate);

        // 특정 날짜 생성
        LocalDate christmas = LocalDate.of(currentDate.getYear(), 12, 25);
        System.out.println("Christmas: " + christmas);
    }
}

 

 

ParallelArray 기능 추가

import java.util.Arrays;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveAction;

public class ParallelArrayExample {
    public static void main(String[] args) {
        int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

        // 배열을 병렬로 처리하는 ParallelArray 생성
        ParallelArrayTask task = new ParallelArrayTask(array);
        ForkJoinPool pool = new ForkJoinPool();
        pool.invoke(task);

        // 결과 출력
        System.out.println("Processed array:");
        System.out.println(Arrays.toString(task.getArray()));
    }
}

// ForkJoinTask를 확장한 RecursiveAction을 사용하여 병렬 작업을 정의하는 클래스
class ParallelArrayTask extends RecursiveAction {
    // 임계값(threshold) - 이 값보다 작으면 순차적으로 처리하고, 크면 병렬로 처리함
    private static final int THRESHOLD = 3;
    private int[] array;
    private int start;
    private int end;

    // 배열과 작업 범위를 받는 생성자
    public ParallelArrayTask(int[] array) {
        this(array, 0, array.length);
    }

    public ParallelArrayTask(int[] array, int start, int end) {
        this.array = array;
        this.start = start;
        this.end = end;
    }

    @Override
    protected void compute() {
        // 작업을 순차적으로 수행해야 하는지 아니면 병렬로 처리해야 하는지 확인
        if (end - start <= THRESHOLD) {
            // 임계값보다 작으면 순차적으로 처리
            for (int i = start; i < end; i++) {
                array[i] *= 2; // 배열의 각 요소를 두 배로 변경
            }
        } else {
            // 임계값보다 크면 배열을 두 부분으로 나누어 병렬로 처리
            int mid = (start + end) / 2;
            ParallelArrayTask leftTask = new ParallelArrayTask(array, start, mid);
            ParallelArrayTask rightTask = new ParallelArrayTask(array, mid, end);
            
            // 병렬 작업 수행
            invokeAll(leftTask, rightTask);
        }
    }

    // 처리된 배열 반환
    public int[] getArray() {
        return array;
    }
}

 

대량의 데이터를 병렬로 처리하거나 멀티코어 환경에서 성능을 최적화해야 할 때 ParallelArray를 사용

 

Java 9

모듈 시스템인 Java Platform Module System (JPMS) 도입.

자동차를 예시로 모듈에 대하여 이해하기

CarProject/
├── carFrameModule/          // 자동차 프레임 모듈을 나타내는 디렉터리
│   ├── src/                 // 자동차 프레임 모듈의 소스 코드를 포함하는 디렉터리
│   │   ├── module-info.java // 자동차 프레임 모듈을 정의하는 파일
│   │   └── com/             // 자동차 프레임 모듈의 패키지 디렉터리
│   │       └── example/     // 예제 패키지 디렉터리
│   │           └── CarFrame.java // 자동차 프레임 모듈 내의 클래스 파일
│   └── resources/           // 자동차 프레임 모듈의 리소스 파일을 포함하는 디렉터리
└── carPartModule/           // 자동차 부품 모듈을 나타내는 디렉터리
    ├── src/                 // 자동차 부품 모듈의 소스 코드를 포함하는 디렉터리
    │   ├── module-info.java // 자동차 부품 모듈을 정의하는 파일
    │   └── com/             // 자동차 부품 모듈의 패키지 디렉터리
    │       └── example/     // 예제 패키지 디렉터리
    │           └── CarPart.java // 자동차 부품 모듈 내의 클래스 파일
    └── resources/           // 자동차 부품 모듈의 리소스 파일을 포함하는 디렉터리

 

자동차 프레임 모듈 부분

// carFrameModule/src/module-info.java

module carFrameModule {
    // 자동차 프레임 모듈에 대한 정보 정의.
    exports com.example.carPartModule;
    opens com.example.carPartModule;
}

// carFrameModule/src/com/example/CarFrame.java

package com.example;

public class CarFrame {
    // 자동차 프레임에 관련된 클래스 내용 작성
    public void build() {
        System.out.println("차 프레임 부분");
    }
}

 

 CarFrame 클래스가 포함된 com.example.carframe 패키지를 모듈 외부로 공개합니다. 하지만 첫 번째 경우인 exports는 리플렉션을 통해 패키지에 접근할 수 없으며, 두 번째 경우인 Opens는 리플렉션을 통해 패키지에 접근할 수 있습니다.

 

* 리플렉션 : 프로그램이 실행되는 동안에 클래스의 정보를 가져오고, 메서드를 호출하고, 필드에 접근할 수 있도록 합니다.프로그램이 실행되는 동안에 클래스의 정보를 가져오고, 메서드를 호출하고, 필드에 접근할 수 있도록 합니다.

 

리플렉션 사용 예제

import java.lang.reflect.*;

public class Main {
    public static void main(String[] args) throws Exception {
        // 클래스 이름으로 클래스 객체를 가져옴
        Class<CarFrame> cls = Class.forName("com.example.carframe.CarFrame");

        // 클래스의 인스턴스 생성
        Object obj = cls.getDeclaredConstructor().newInstance();

        // 메서드 이름으로 메서드 객체를 가져옴
        Method method = cls.getDeclaredMethod("build");

        // 메서드 호출
        method.invoke(obj);
    }
}

 

리플렉션을 유용하게 사용할 수 있는 시점

  1. 런타임 시에 클래스의 인스턴스를 동적으로 생성해야 할 때.
  2. 클래스의 메서드를 동적으로 호출해야 할 때.
  3. 클래스의 필드에 동적으로 접근해야 할 때.
  4. 어노테이션(Annotation)을 분석하고 처리해야 할 때.

 

자동차 부품 모듈에서 자동차 프레임 모듈을 사용하는 방법

// carPartModule/src/module-info.java

module carPartModule {
    // 자동차 부품 모듈에 대한 정보 정의
    requires carFrameModule; // 자동차 부품 모듈은 자동차 프레임 모듈에 의존함
}

// carPartModule/src/com/example/carpart/CarPart.java

package com.example.carpart;
import com.example.carframe.CarFrame;

public class CarPart {
    private CarFrame frame; // 자동차 프레임을 포함하는 카 부품 클래스의 멤버

    public CarPart() {
        this.frame = new CarFrame(); // 카 부품 클래스가 생성될 때 자동차 프레임 인스턴스를 생성함
    }

    public void assemble() {
        System.out.println("Assembling car parts...");
        frame.build(); // 자동차 프레임의 build 메서드 호출
        // 부품 조립 로직 추가
    }
}

 

 

JShell이라는 REPL(Read-Eval-Print Loop) 도구 추가.

$ jshell
|  Welcome to JShell -- Version 9.0.4
|  For an introduction type: /help intro

jshell> int x = 10;
x ==> 10

jshell> int y = 20;
y ==> 20

jshell> int sum = x + y;
sum ==> 30

jshell> System.out.println(sum);
30

 

HTTP/2 클라이언트 추가

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class HttpClientExample {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
                .uri(new URI("https://jsonplaceholder.typicode.com/posts/1"))
                .build();

        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

        System.out.println(response.statusCode());
        System.out.println(response.body());
        
        //비동기
        client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
                .thenApply(HttpResponse::body)
                .thenAccept(System.out::println)
                .join();
                
    }
}

 

HTTP/2 클라이언트가 추가되어 HTTP/2를 지원합니다.

 

* HTTP/2와 HTTP/1.1의 차이

 

성능 향상: HTTP/2는 여러 개의 요청을 동시에 처리할 수 있는 멀티플렉싱 기능을 제공하여 성능을 향상시킵니다. 이는 하나의 TCP 연결을 통해 여러 개의 요청과 응답을 처리할 수 있도록 합니다. 따라서 웹 페이지의 로딩 시간을 줄일 수 있습니다.

헤더 압축: HTTP/2는 헤더 필드를 압축하여 대역폭을 절약하고 더 빠른 전송을 가능하게 합니다. 이는 불필요한 반복되는 헤더 정보의 크기를 줄여서 통신의 효율성을 높입니다.

서버 푸시: HTTP/2는 서버 푸시 기능을 제공하여 클라이언트의 요청 없이 서버가 리소스를 클라이언트로 전송할 수 있습니다. 이를 통해 웹 페이지의 로딩 시간을 더욱 줄일 수 있습니다.

기타 기능: HTTP/2는 기타 다양한 기능을 제공하는데, 예를 들어 이진 프레임 사용, 스트림 우선순위 지정, 흐름 제어 등이 있습니다.

 

더보기
이진 프레임 사용(Binary Frames):
HTTP/2는 데이터를 이진 형식으로 전송하고 처리합니다. 각각의 프레임은 특정 작업을 나타내며, 헤더와 페이로드를 가집니다. 예를 들어, 헤더 프레임은 헤더 정보를 전송하고, 데이터 프레임은 실제 데이터를 전송합니다.

스트림 우선순위 지정(Stream Prioritization):
HTTP/2에서는 각 스트림에 우선순위를 지정하여 특정 요청이나 응답을 더 높은 우선순위로 처리할 수 있습니다. 이를 통해 중요한 리소스에 대한 응답 시간을 개선하고 사용자 경험을 향상시킬 수 있습니다. 예를 들어, 웹 페이지의 중요한 자원에 대한 요청을 먼저 처리하도록 우선순위를 지정할 수 있습니다.

흐름 제어(Flow Control):
HTTP/2에서는 클라이언트와 서버 간의 데이터 흐름을 제어하기 위한 흐름 제어 기능을 제공합니다. 이를 통해 수신자가 자신의 처리 속도에 맞게 데이터를 처리할 수 있도록 조절할 수 있습니다. 이를 통해 네트워크 혼잡을 줄이고 성능을 최적화할 수 있습니다.

 

HTTP/2의 이진 프레임, 스트림 우선순위 지정 및 흐름 제어 기능은 주로 프로토콜 수준에서 작동하며, 일반적으로 개발자가 직접 제어할 필요가 없습니다. 하지만 이러한 기능은 HTTP/2를 통해 더 효율적으로 데이터를 전송하고 처리할 수 있도록 도와줍니다. 

  

개선된 Garbage Collector 인 G1(Garbage-First) GC.

public class G1GCExample {
    public static void main(String[] args) {
        // JVM 옵션을 사용하여 G1 GC 설정
        // -XX:+UseG1GC: G1 GC 사용
        // -Xms: 초기 힙 크기 설정
        // -Xmx: 최대 힙 크기 설정
        // -XX:MaxGCPauseMillis: GC 일시 정지 시간 제한 설정
        // -XX:G1HeapRegionSize: Heap 영역 크기 설정
        // 등등...

        // 아래는 예시이므로 실제 실행 시에는 적절한 옵션을 사용하세요.
        
        // 객체 생성을 위한 루프
        for (int i = 0; i < 100000; i++) {
            // 매 반복마다 새로운 객체를 생성
            Object obj = new Object();
        }
        
        // 가비지 컬렉션 실행
        System.gc();
        
        // 메시지 출력
        System.out.println("G1 가비지 컬렉션 실행 완료");
    }
}

 

분산된 가비지 컬렉션: G1 GC는 전체 힙 영역을 여러 개의 작은 영역으로 나누어 관리합니다. 이를 통해 전체 힙을 한 번에 처리하는 대신, 작은 영역에 대해서만 가비지 컬렉션을 수행하여 응답 시간을 개선합니다.

가비지 컬렉션 예측 및 제어G1 GC는 일정 시간 간격으로 힙 상태를 모니터링하고, 가비지 컬렉션을 수행할 때 예상되는 일정 시간 내에 완료될 수 있도록 동적으로 조절됩니다. 이를 통해 응답 시간을 보장하면서 가비지 컬렉션을 수행할 수 있습니다.

복제(stop-the-world) 수행: G1 GC는 전체 힙을 한 번에 처리하지 않고, 작은 영역을 순차적으로 처리합니다. 따라서 전체 힙에 대한 가비지 컬렉션 작업을 수행할 때 발생하는 중지 시간(stop-the-world)이 분산되어 발생하므로, 일관된 응답 시간을 유지할 수 있습니다.

세대별 가비지 컬렉션: G1 GC는 세대별 가비지 컬렉션 알고리즘을 사용하여, 영구 영역(PermGen 또는 Metaspace)과 새로운 객체 생성 영역(Eden)을 분리하여 관리합니다. 이를 통해 새로 생성된 객체와 오래된 객체에 대해 각각 다른 가비지 컬렉션 알고리즘을 적용할 수 있습니다.

설정 가능한 GC 정책: G1 GC는 다양한 설정 옵션을 제공하여 사용자가 가비지 컬렉션의 동작을 세밀하게 조정할 수 있습니다. 이를 통해 특정 애플리케이션에 최적화된 가비지 컬렉션 정책을 설정할 수 있습니다.

 

이전에는 System.gc()를 호출하여 가비지 컬렉터에게 힙을 정리하도록 강제할 수 있었지만, Java 9에서는 이 기능이 변경되어 System.gc()를 호출해도 가비지 컬렉터가 즉시 실행되지는 않습니다. 대신, 이 요청은 가비지 컬렉터가 즉시 실행될 것을 보장하지 않으며, 불완전한 참조 제거와 같은 실험적인 기능을 활성화하는 데 사용될 수 있습니다.

 

불완전한 참조 제거(Experimental feature).

// 불완전한 참조 제거(Experimental feature) 예제
public class IncompleteReferenceRemovalExample {
    public static void main(String[] args) {
        // 객체 생성 후 변수를 null로 설정하여 불완전한 참조를 만듭니다.
        Object obj = new Object();
        obj = null;
        // UninstantiatedObject 인스턴스가 즉시 GC 대상이 되도록 요청합니다.
        // 이는 실험적인 기능으로, 즉시 가비지 컬렉션을 유도합니다.
        System.gc();
    }
}

 

해당 기능을 이해하려면 기본적으로 가비지 컬렉터의 동작원리를 알고 있어야 한다고 생각합니다. 

객체를 생성할 경우 자바 메모리의 힙 영역에 해당 객체가 생성되며 가비지 컬렉터의 대상이 됩니다. 보통 가비지 컬렉터는 조건에 맞는 대상을 선정하여 제거하므로 메모리를 확보하는데 몇몇의 경우에 인식하지 못하는 경우가 있다고 합니다. 이럴 때 명시적으로 가비지 컬렉터에게 모니터링을 요청한다면 가비지 컬렉터가 인식할 수 있게 끔 동작한다는 것 같습니다.

 

Java 10

지역 변수 형식 추론(Local Variable Type Inference)을 위한 var 키워드 도입.

public class LocalVariableTypeInferenceExample {
    public static void main(String[] args) {
        // String 타입 변수를 선언하고 초기화
        String name = "John";
        
        // Java 10에서 도입된 var 키워드를 사용하여 지역 변수의 타입을 추론
        var age = 30;
        
        // 배열 초기화에 var 키워드 사용
        var numbers = new int[] {1, 2, 3, 4, 5};
        
        // 리스트 초기화에 var 키워드 사용
        var list = new ArrayList<String>();
        list.add("Apple");
        list.add("Banana");
        
        // 맵 초기화에 var 키워드 사용
        var map = new HashMap<Integer, String>();
        map.put(1, "One");
        map.put(2, "Two");
        
        // var 키워드를 사용한 변수의 타입은 컴파일러가 자동으로 추론함
        System.out.println("Name: " + name);
        System.out.println("Age: " + age);
        System.out.println("Numbers: " + Arrays.toString(numbers));
        System.out.println("List: " + list);
        System.out.println("Map: " + map);
    }
}

 

 

반응형

 

Java 11

로우 레벨의 스레드를 관리하기 위한 선언적인 API인 java.lang.ThreadLocal 추가.

import java.lang.ThreadLocal;

public class ThreadLocalExample {
    // 스레드별로 값이 고유하게 유지되는 ThreadLocal 변수를 정의합니다.
    private static ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "Initial Value");

    public static void main(String[] args) {
        // 현재 스레드의 ThreadLocal 변수에서 값을 가져와 출력합니다.
        System.out.println("Thread Local Value: " + threadLocal.get());

        // 현재 스레드의 ThreadLocal 변수에 새로운 값을 설정합니다.
        threadLocal.set("New Value");
        
        // 새로 설정된 값을 다시 가져와 출력합니다.
        System.out.println("Thread Local Value: " + threadLocal.get());
    }
}

 

멀티스레드 환경에서 스레드별로 고유한 값이 필요할 때 사용

 

웹 애플리케이션에서 사용자 세션 관리: 웹 애플리케이션에서 사용자의 세션 정보를 관리할 때 각각의 요청에 대해 스레드마다 사용자 세션을 저장할 수 있습니다. 이렇게 하면 스레드 간의 세션 정보를 공유할 필요가 없으며, 스레드별로 독립적으로 처리할 수 있습니다.

보안 관련 작업보안 토큰, 인증 정보 등과 같이 보안 관련된 정보를 스레드별로 유지해야 할 때 ThreadLocal을 사용할 수 있습니다. 이렇게 하면 각각의 스레드에서는 자신의 보안 정보를 안전하게 유지할 수 있습니다.

트랜잭션 관리: 데이터베이스 트랜잭션과 같은 작업에서 스레드별로 트랜잭션 상태를 관리할 수 있습니다. 각 스레드는 자신의 트랜잭션을 독립적으로 관리하고, 다른 스레드의 트랜잭션 상태에 영향을 받지 않습니다.

캐시: 각 스레드에 대해 별도의 캐시를 유지할 때 ThreadLocal을 사용할 수 있습니다. 이렇게 하면 각 스레드에서 캐시를 독립적으로 관리하고, 다른 스레드의 캐시에 영향을 주지 않습니다.

ThreadLocal을 사용할 때에는 주의해야 할 점

메모리 누수ThreadLocal에 저장된 값은 스레드가 종료되어도 가비지 컬렉션되지 않습니다. 따라서 ThreadLocal을 오랫동안 사용하거나 사용이 끝난 후에는 반드시 remove() 메서드를 호출하여 값이 메모리에 남지 않도록 해야 합니다.


과도한 사용: 너무 많은 ThreadLocal 변수를 사용하면 메모리 부담이 발생할 수 있으며, 스레드간의 값 공유가 불가능하므로 주의해야 합니다.

 

Z Garbage Collector (ZGC)

public class ZGCExample {
    public static void main(String[] args) {
        // 대량의 메모리를 사용하는 객체를 생성합니다.
        List<byte[]> memoryList = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            memoryList.add(new byte[1024 * 1024]); // 1MB 메모리를 가진 객체를 생성하여 리스트에 추가합니다.
        }

        // 대량의 메모리를 사용하는 객체를 제거합니다.
        memoryList.clear();

        // 메모리를 회수할 때까지 대기합니다.
        // 실제로는 명시적인 코드가 필요하지 않을 수 있으며, JVM이 메모리 부족 시 자동으로 가비지 컬렉션을 수행할 수 있습니다.
        System.gc();
    }
}

 

Z Garbage Collector (ZGC) 대량의 메모리를 사용하는 객체를 생성하고, 이후에 해당 객체를 제거합니다. 그리고 명시적으로 System.gc()를 호출하여 가비지 컬렉터에게 메모리 회수를 요청합니다. ZGC는 대량의 힙 크기와 매우 짧은 중지 시간을 제공하므로, 대규모 메모리 할당 및 회수 작업에서 유용하게 사용될 수 있습니다.

 

Java 12

Switch 표현식(Switch Expressions) 추가.

// Switch 표현식(Switch Expressions) 예제
public class SwitchExpressionExample {
    public static void main(String[] args) {
        // 날짜를 나타내는 변수
        int day = 3;
        // switch 표현식을 사용하여 주어진 날짜에 따라 날짜 유형을 결정합니다.
        String dayType = switch (day) {
            // case 라벨에서 값을 반환하여 각 날짜 유형을 정의합니다.
            case 1, 2, 3, 4, 5 -> "Weekday";
            case 6, 7 -> "Weekend";
            default -> "Invalid day";
        };
        // 결정된 날짜 유형을 출력합니다.
        System.out.println("Day type: " + dayType);
    }
}

 

Java 12부터는 switch 표현식이 case 라벨에서 값을 반환할 수 있으며, 간결한 구문을 사용하여 작성할 수 있습니다.

 

Java 13

Text Blocks 추가.

public class TextBlocksExample {
    public static void main(String[] args) {
        // Text Blocks 사용 예제
        String html = """
            <html>
                <body>
                    <p>Hello, world!</p>
                </body>
            </html>
            """;
        System.out.println("HTML: " + html);
    }
}


Java 13부터는 Text Blocks가 추가되어 여러 줄의 문자열을 쉽게 작성할 수 있습니다. """로 시작하고 끝나는 여러 줄의 문자열은 개행과 들여쓰기가 유지됩니다.

 

Java 14

Records 추가.

public class RecordsExample {
    public static void main(String[] args) {
        // Records 사용 예제
        Point point = new Point(10, 20);
        System.out.println("Point: " + point);

        // Records를 사용한 데이터 불변성 확인
        // point.x = 30; // 컴파일 에러: 불변성이 보장되므로 값을 변경할 수 없음

        // Records를 사용한 레코드 인스턴스의 필드 접근
        int x = point.x();
        int y = point.y();
        System.out.println("X coordinate: " + x + ", Y coordinate: " + y);

        // Records를 사용한 동등성 비교
        Point anotherPoint = new Point(10, 20);
        System.out.println("Is point equal to anotherPoint? " + point.equals(anotherPoint));

        // Records를 사용한 해시 코드 생성
        System.out.println("Hash code of point: " + point.hashCode());

        // Records를 사용한 toString() 메서드 호출
        System.out.println("String representation of point: " + point.toString());

        // Records를 사용한 패턴 매칭
        Object obj = "Hello, world!";
        if (obj instanceof Point p) {
            System.out.println("Pattern matching: " + p.x() + ", " + p.y());
        }
        
        // Records 클래스에 새로운 메서드 추가 및 호출 예제
        int sum = point.calculateSum(5);
        System.out.println("Sum of x and y: " + sum);

    }

    // Record 정의
    public record Point(int x, int y) {
        // Record 에 calculateSum 메서드 추가
        public int calculateSum() {
           // Records 의 필드와 새로운 지역 변수를 사용하여 계산
            return x() + y();
        }
    }
}

 

불변성(Immutability): Records는 불변(immutable) 데이터를 나타내는 데에 사용됩니다. 따라서 한 번 생성되면 그 값을 변경할 수 없습니다. Records는 final 키워드를 사용하여 정의되며, 모든 필드는 final로 설정됩니다. 이로써 불변성이 보장됩니다.

필드 및 생성자 자동 생성: Records는 간단한 데이터 컨테이너로서 필드와 이를 초기화하는 생성자를 자동으로 생성합니다. 이러한 생성자는 기본적으로 필드를 인자로 받아 초기화합니다. 별도의 생성자를 정의하지 않아도 되며, 필드명과 동일한 인자를 받는 생성자가 자동으로 생성됩니다.


toString(), equals(), hashCode() 메서드 자동 생성: Records는 자동으로 toString(), equals(), hashCode() 메서드를 생성하여 레코드 인스턴스의 내용을 문자열로 표시하고 동등성 비교 및 해시 코드 생성을 지원합니다. 이러한 메서드들은 필드의 값을 기반으로 생성되며, 필드의 순서에 따라 자동으로 생성됩니다.


상속 및 인터페이스 구현 : Records는 명시적으로 extends나 implements를 사용하여  다른 클래스를 상속하거나 인터페이스를 구현할 수 없습니다. Record 클래스는 final이므로 상속이 불가능 합니다. 또한 모든 필드가 final이므로 불변성이 보장됩니다.

추가 메서드 및 생성자 : Records는 추가 메서드나 생성자를 가질 수 없습니다. 필드 이외의 메서드를 정의할 수 없으며, 컴파일러가 자동으로 생성한 메서드만 사용할 수 있습니다.

 

instanceof 패턴 매칭(Instanceof Pattern Matching) 추가.

public class InstanceofPatternMatchingExample {
    public static void main(String[] args) {
        // instanceof 패턴 매칭 예제
        Object obj = "Hello, world!";
        if (obj instanceof String str) {
            System.out.println("String length: " + str.length());
        }
    }
}

 

 

Java 15

Sealed 클래스와 인터페이스(Sealed Classes and Interfaces) 추가.

// Sealed 클래스 정의: 자동차 (Vehicle)
public sealed class Vehicle permits KiaCar, HyundaiCar {
    // 기본 구현
    public void drive() {
        System.out.println("Vehicle is driving...");
    }

    public void stop() {
        System.out.println("Vehicle stopped.");
    }
}

// KiaCar 클래스
final class KiaCar extends Vehicle {
    @Override
    public void drive() {
        System.out.println("Kia car is driving...");
    }

    @Override
    public void stop() {
        System.out.println("Kia car stopped.");
    }
}

// HyundaiCar 클래스
final class HyundaiCar extends Vehicle {
    @Override
    public void drive() {
        System.out.println("Hyundai car is driving...");
    }

    @Override
    public void stop() {
        System.out.println("Hyundai car stopped.");
    }
}

// K5 클래스 (KiaCar를 상속)
final class K5 extends KiaCar {
    // KiaCar 클래스의 drive()와 stop() 메서드를 사용
}

// Sonata 클래스 (HyundaiCar를 상속)
final class Sonata extends HyundaiCar {
    // HyundaiCar 클래스의 drive()와 stop() 메서드를 사용
}

// Main 클래스
public class Main {
    public static void main(String[] args) {
        // 기아차 객체 생성
        Vehicle kiaCar = new K5();
        // 현대차 객체 생성
        Vehicle hyundaiCar = new Sonata();

        // 각각의 차량이 운전을 합니다.
        kiaCar.drive(); // 출력: Kia car is driving...
        kiaCar.stop();  // 출력: Kia car stopped.

        hyundaiCar.drive(); // 출력: Hyundai car is driving...
        hyundaiCar.stop();  // 출력: Hyundai car stopped.
    }
}

 

sealed 키워드는 클래스와 인터페이스를 제한된 하위 클래스나 구현체로 제어하기 위해 사용됩니다. permits 키워드는 sealed 클래스나 인터페이스가 허용하는 하위 클래스나 구현체를 명시합니다.

sealed 클래스나 인터페이스와 함께 사용되는 이점

 

하위 클래스나 구현체 제어: sealed 키워드를 사용하면 특정 클래스나 인터페이스의 하위 클래스나 구현체가 어떤 것들만 허용되는지 명시적으로 제어할 수 있습니다.

타입의 안정성: sealed 키워드를 사용하면 상속 또는 구현이 임의로 이루어지지 않으므로 타입 시스템의 안정성을 높일 수 있습니다.

추가 유연성: 하위 클래스나 구현체가 변경되어도 sealed 클래스나 인터페이스에 대한 수정이 필요하지 않습니다. 새로운 하위 클래스나 구현체를 추가할 때는 기존 클래스나 인터페이스의 수정 없이 사용할 수 있습니다.


sealed 클래스나 인터페이스에서 abstract 메서드를 선언하는 이유

 

sealed 클래스나 인터페이스는 하위 클래스나 구현체를 명시적으로 제어하기 위해 사용됩니다. 하지만 해당 클래스나 인터페이스에는 구현되지 않은 메서드가 존재할 수 있습니다. 이 메서드들은 하위 클래스나 구현체에서 구현되어야 하므로 abstract로 선언됩니다.

abstract 메서드를 포함한 sealed 클래스나 인터페이스를 사용하면 하위 클래스나 구현체에서 반드시 해당 메서드를 구현해야 하므로 코드의 일관성을 유지하고 실수를 방지할 수 있습니다.

 

Java 16

패턴 매칭(Pattern Matching) 개선.

public class PatternMatchingExample {
    public static void main(String[] args) {
        Object obj = "hello";

        // instanceof와 함께 사용하는 예제
        if (obj instanceof String) {
            String str = (String) obj;
            System.out.println("Length of the string: " + str.length());
        }

        // Java 16의 패턴 매칭 개선 예제
        if (obj instanceof String str) {
            System.out.println("Length of the string: " + str.length());
        }
    }
}

 

Unix 도메인 소켓(Unix Domain Socket) 추가.

import java.io.*;
import java.net.*;

public class UnixDomainSocketExample {
    public static void main(String[] args) {
        try (Socket socket = new Socket()) {
            socket.connect(new UnixSocketAddress("/path/to/socket"));
            // Unix 도메인 소켓과 통신하는 코드를 작성합니다.
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

기존의 TCP/IP 소켓과 달리 로컬 시스템에서만 사용할 수 있는 특수한 종류의 소켓입니다. 이러한 Unix 도메인 소켓을 사용하면 로컬 시스템 내의 프로세스 간에 효율적으로 통신할 수 있습니다.

 

로컬 통신의 간결성: Unix 도메인 소켓은 TCP/IP 소켓보다 더 간결하고 효율적인 로컬 통신을 제공합니다. 이는 로컬 시스템 내의 프로세스 간에 데이터를 빠르고 안전하게 전송할 수 있음을 의미합니다.

보안: Unix 도메인 소켓은 TCP/IP 소켓과는 달리 네트워크를 통해 접근할 수 없기 때문에 보안에 더 우수한 기능을 제공합니다. 이는 시스템 내의 프로세스 간 통신을 안전하게 보호할 수 있음을 의미합니다.

성능: Unix 도메인 소켓은 네트워크 스택을 거치지 않기 때문에 TCP/IP 소켓보다 더 빠른 성능을 제공할 수 있습니다. 이는 데이터 전송에 있어서 더 효율적인 솔루션을 제공할 수 있음을 의미합니다.

 

암호화 관련 개선 사항.

import java.security.*;
import javax.crypto.*;

public class EncryptionExample {
    public static void main(String[] args) throws Exception {
        // AES 알고리즘을 사용하여 암호화 및 복호화를 수행하는 예제입니다.
        
        // AES 암호화 알고리즘과 GCM (Galois/Counter Mode) 모드를 사용합니다.
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        
        // AES 암호화 키를 생성합니다.
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        SecretKey secretKey = keyGenerator.generateKey();

        // 원문을 암호화합니다.
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        byte[] encryptedData = cipher.doFinal("Hello, World!".getBytes());
        System.out.println("Encrypted data: " + new String(encryptedData));

        // 암호문을 복호화합니다.
        cipher.init(Cipher.DECRYPT_MODE, secretKey);
        byte[] decryptedData = cipher.doFinal(encryptedData);
        System.out.println("Decrypted data: " + new String(decryptedData));
    }
}

 

기존의 AES 알고리즘과 비교하여 개선된 점

 

성능 향상: AES/GCM 암호화 알고리즘의 성능이 향상되었습니다. 이는 암호화 및 복호화 작업에서 더 빠른 속도를 제공합니다. 따라서 대량의 데이터를 암호화하고 복호화할 때 더 빠른 처리 속도를 기대할 수 있습니다.

보안 강화: GCM (Galois/Counter Mode) 모드는 인증된 암호화를 제공하여 데이터 무결성을 보장합니다. 이는 암호문이 변경되었는지 여부를 확인하여 데이터 변조를 감지할 수 있습니다. 또한 이 모드는 반복되는 nonce (Number used ONCE)를 사용하여 같은 평문에 대한 암호문이 매번 다르게 생성되도록 보장합니다.

사용 편의성: GCM 모드는 인증과 암호화를 동시에 처리할 수 있어서 암호화된 데이터의 무결성을 쉽게 유지할 수 있습니다. 또한 NoPadding 옵션을 사용하여 추가적인 패딩 작업이 필요하지 않습니다.

 

GCM 이란 ?

더보기
GCM(Garbage Collection Monitor)은 Java 가상 머신에서 가비지 컬렉션(Garbage Collection) 활동을 모니터링하는 도구

가비지 컬렉션 이벤트의 빈도와 지속 시간: GCM은 가비지 컬렉션 이벤트가 언제 발생했는지와 각 이벤트의 지속 시간을 추적합니다. 이를 통해 어떤 유형의 가비지 컬렉션이 발생했는지, 얼마나 자주 발생하는지 등을 파악할 수 있습니다.

가비지 컬렉션의 유형: GCM은 가비지 컬렉션의 유형을 식별합니다. 예를 들어, 새로운 세대(Gen) 컬렉션, 전체 컬렉션 등이 있습니다. 각 유형의 컬렉션에 따라 메모리 누수 및 성능에 미치는 영향이 다를 수 있습니다.

가비지 컬렉션 오버헤드: GCM은 각 가비지 컬렉션 이벤트가 애플리케이션의 실행 시간에 미치는 오버헤드를 측정합니다. 이를 통해 가비지 컬렉션에 의해 애플리케이션 성능이 얼마나 영향을 받는지 확인할 수 있습니다.

 

Nonce 란?

더보기
한 번만 사용: Nonce는 한 번만 사용되어야 합니다. 같은 Nonce가 여러 번 사용되면 보안에 취약해질 수 있습니다.

임의성: Nonce는 무작위로 생성되어야 합니다. 이는 예측이 불가능하고 재사용이 불가능하도록 해야 합니다.

길이: Nonce의 길이는 충분히 크고 고정되어야 합니다. 이는 충돌이 발생할 가능성을 줄이고 보안을 강화합니다.

 

Java 17

Sealed 클래스 및 인터페이스(Sealed Classes and Interfaces)의 기능 향상.

// Sealed 클래스인 Car을 정의합니다.
sealed interface Car permits HyundaiCar, KiaCar {
    void drive();
}

// HyundaiCar enum 클래스를 정의합니다.
enum HyundaiCar implements Car {
    SONATA {
        @Override
        public void drive() {
            System.out.println("Hyundai Sonata is driving");
        }
    },
    AVANTE {
        @Override
        public void drive() {
            System.out.println("Hyundai Avante is driving");
        }
    }
}

// KiaCar enum 클래스를 정의합니다.
enum KiaCar implements Car {
    K3 {
        @Override
        public void drive() {
            System.out.println("Kia K3 is driving");
        }
    },
    K5 {
        @Override
        public void drive() {
            System.out.println("Kia K5 is driving");
        }
    }
}

public class SealedClassExample {
    public static void main(String[] args) {
        // Sealed 클래스의 하위 클래스들을 생성하여 drive() 메서드를 호출합니다.
        Car sonata = HyundaiCar.SONATA;
        Car avante = HyundaiCar.AVANTE;
        Car k3 = KiaCar.K3;
        Car k5 = KiaCar.K5;

        sonata.drive();   // Hyundai Sonata is driving
        avante.drive();   // Hyundai Avante is driving
        k3.drive();       // Kia K3 is driving
        k5.drive();       // Kia K5 is driving
    }
}

 

패턴 매칭(Pattern Matching) 및 기타 기능 개선.

// 기존 switch 문에서 instanceof 패턴 매칭 사용
public static int getShapeType(Shape shape) {
    return switch (shape) {
        case Circle c -> 1;
        case Rectangle r -> 2;
        case Triangle t -> 3;
        default -> 0;
    };
}

// Java 17의 새로운 기능인 switch 문으로 null 패턴 매칭 사용
public static String getResult(String input) {
    return switch (input) {
        case "A" -> "Apple";
        case "B" -> "Banana";
        case null -> "Unknown";
        default -> "Other";
    };
}

 

 

반응형

'컴퓨터 언어 > Java' 카테고리의 다른 글

JPA - OSIV 패턴  (0) 2023.09.23
JPA - N+1 문제란?  (0) 2023.09.12
JPA - JPQL 사용 방법  (0) 2023.09.12
JPA - 값 타입 사용 방법  (0) 2023.09.11
JPA - Proxy(프록시) 이해하기  (0) 2023.09.07
profile

소소한개발팁

@개발자 뱅

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!