[Java] Java LTS - JDK 8,11,17,21의 변천사

·2024년 3월 14일
0

Java

목록 보기
5/5
post-thumbnail

Java LTS

Java 프로젝트를 진행하는 경우, IDE에서 원하는 Java 버전을 선택할 수 있다. 원하는 SDK 를 설치하려는 경우, 언어 버전 혹은 언어 레벨 설정 시 권장되는 버전들이 있다는 것을 확인할 수 있다.

이러한 버전들을 LTS, Long Time Support 라 하며, 일반버전 대비 안정성에 중점을 두어, 장기적인 지원을 제공하는 버전을 뜻한다. 실제 LTS 버전의 경우, 업데이트가 최소한으로 이루어지며, 보안적인 요소도 훨씬 강력하다.

어플리케이션 서비스를 만드는 기업이나 기관의 경우, 기술의 업데이트로 인해 서비스가 영향을 미치는 문제를 피하기 위해, 기술 스택 선정 시 LTS 버전을 택하여 사용하기도 한다.

LTS 는 자바 뿐만 아니라 다수의 오픈소스에서도 많이 적용되고 있으며, 기술마다 다르지만 최소 3~5 년 정도의 지원을 제공한다. Java 의 경우 크게 8,11,17,21LTS 버전에 속한다. 기능적으로 크게 추가된 것은 Java 8 이며, Java 11,17,21 은 최적화를 통해 성능을 높이는데 주력한 버전이다.

Java 8

lambda

자바스크립트의 화살표함수와 유사하게, 함수를 하나의 식으로 표현한 것을 의미한다. 자바스크립트 만큼의 유연함을 제공하지는 못하나, 주로 함수 내 인자로 표현식을 정의하는 용도로 많이 사용된다. ex) 정렬, forEach, null 대체 반환값 등

  • 장점 : 코드가 간결해지고, 작성 코드가 짧아 편리하다.
  • 단점 : 함수 내 인자로 사용되는 함수는 익명함수로, 재사용이 불가능하다. 동일한 함수를 재사용하고 싶다면 기존과 같이, 리턴 타입과 변수를 선언해야한다.

Java.util.function

Java 의 내장된 주요 함수형 인터페이스는 다음과 같다. Java 가 제공하는 함수형 인터페이스에는 디폴트 메서드가 정의되어 있어, 개발 시 상황에 따라 적절히 사용하기 편리하다.

Supplier<Integer> s = () -> 42;
System.out.println(s.get());

Runnable r = () -> System.out.println("go");
r.run();

List<Integer> l = new ArrayList<>();
Function<Integer, Integer> n = (num) -> num * num;
n.apply(3);

메서드 참조

메서드 참조 (Method Reference)는 말 그대로 메서드를 참조하여 매개 변수의 정보 및 리턴 타입을 알아내어, 람다식을 좀 더 생략하도록 할 수 있다. 타입스크립트를 사용해보았다면, 언어의 추론을 통해 알아서 인자 혹은 리턴 타입을 알아내는 것과 유사하다.

람다표현식을 메서드 참조식을 변경하려면 다음의 3가지 조건을 만족하면 된다.

  • 함수형 인터페이스의 매개변수 타입이 메서드의 매개변수 타입과 동일한 경우
  • 함수형 인터페이스의 매개변수 개수가 메서드의 매개변수 개수와 동일한 경우
  • 함수형 인터페이스의 반환타입이 메서드의 반환타입과 동일한 경우
List<String> players = participants.getPlayers().stream()
						.map(player -> player.getName())
						.collect(Collectors.toList());

List<String> players = participants.getPlayers().stream()
						.map(Participant::getName)
						.collect(Collectors.toList());

Stream API

Stream 은 데이터의 연속적인 흐름을 나타내는 개념으로, Java 8 에서는 Stream API 를 추가하여 컬렉션의 데이터를 필터링하거나 매핑하는 등 손쉬운 조작을 제공한다.

Stream 의 과정에는 크게 생성 - 중간연산 - 최종연산으로 이루어진다.

  • 생성 : 주어진 자료구조를 기반으로 Stream 을 생성하는 것을 의미한다. ex) stream()
  • 중간연산 : 생성한 Stream 에 대한 조작, 처리가 이루어진다. ex) map(), filter()
  • 최종연산 : Stream 데이터를 원하는 결과로 바꾼다. ex) collect()

Stream 특징

  • 기존 데이터를 변경하지 않는다. Stream 은 생성 시 기존의 원본데이터를 읽어서, 새로운 Stream 객체를 생성한 후, Stream 객체에 대한 조작을 이룬후, 새로운 객체를 리턴한다.
  • 중간연산 메서드의 경우 동일한 Stream 을 반환하기 때문에, 체이닝이 가능하다. 따라서 중간연산에 대한 결과를 가지고 새로운 중간연산을 진행할 수 있다.
  • Stream 의 매개변수로 람다표현식이나 메서드 참조를 대입하는 것이 가능하다.
  • 데이터가 많은 경우, 내부적으로 데이터를 분할하여 병렬 처리를 이루도록 설계되어 있다. 개발자는 직접적으로 Stream 에 대해 직접 스레드를 관리하지 않아도 된다.
    ex) parallelStream(), parallel()
    > 병렬 처리의 경우 무조건 더 나은 결과를 보장한다고 할 수는 없다. 머리 스레드 환경에서의 컨텍스트 스위칭 비용이나, 데이터의 동기화 유무에서 문제가 발생할 수도 있다. 만약 `Stream` 에 대한 병렬처리를 진행하기를 고민한다면, 성능 개선이 유의미한지 예상치 못한 장애를 발생시키지는 않는지 등을 충분히 고민할 필요가 있다.
    > 

Optional

임의의 값이 존재할수도, 존재하지 않을수도 있는 상황을 다룰 때 사용하는 클래스이다. 주로 어플리케이션 생명주기 내에 DB 와의 통신 시 NullPointerException 을 방지하기 위해 사용한다.

Optional 특징

  • 값의 유무 표현이 가능하며, Null 을 방어할 수 있다. Optional 값이 존재하는 경우 해당 값을 추출하거나, null 인 경우 이에 대한 대응책을 마련할 수 있다.
  • Optional 클래스는 체이닝이 가능하며, 상황에 따라 기본값을 할당하는 메서드들을 제공한다. 예를들어, orElse() 을 통해 값이 null 인 경우 기본값을 할당할 수 있다.
    Optional<String> optionalName = Optional.ofNullable(getName());
    String name = optionalName.orElse("Unknown");

Time API

Java8 의 경우, LocalDate , LocalTime , LocalDateTime 새로운 시간, 날짜 API 가 등장했다. 이는 기존의 [java.util.Date](http://java.util.Date)java.util.Calendar 클래스 사용시 불편사항을 개선하기 위해서 였다.

기존 Date, Calendar의 문제점

  • Date 클래스는 가변성을 가지고 있다. 객체가 생성된 이후에도 연산을 통해, 값을 변경하는 것이 가능하다. 이 경우, 스레드가 동시에 접근할 때 안전하지 않은 상태를 만들 수 있다.
  • Date 클래스는 월을 나타내는 숫가 0 부터 시작한다. 즉 0 이 1월이고, 11 이 12월이 된다.
  • Calendarget(Calendar.DAY_OF_WEEK) 는 함수에서 반환한 요일은 int 값으로 반환한다. 이때 일요일을 1 로 응답하는 것을 시작으로 다음 요일을 1 씩 증가한 값을 반환한다. 그러나, DategetDay() 는 일요일은 0 으로 응답한다. 즉 두개의 클래스 사이에 요일 지정 반환 숫자가 일관성이 없다.

Joda-Time

기존 JDK 를 대체하는 날짜와 시간 API로 많이 사용되었던 라이브러리이다. 위의 Date, Canlerdar 의 문제를 해결하는 것은 물론, 날짜, 시간 시 나타날 수 있는 문제들을 해결했다.

추후 Java8 에서 등장한 Time APIJoda-Time 의 영향을 많이 받았다고 한다.

  • 지역 시간대 및 지정된 시간을 구분하는 것이 가능하며, 날짜와 시간을 별도의 클래스로 구분하는 것이 가능하다.
  • 월과 요일에 대한 상수 값의 일관성을 보장한다.
  • 불변 객체로서 안전성을 보장하며, plusDays() , plusSeconds() 등의 날짜 연산 메서드를 지원한다. 해당 메서드 모두 체이닝이 가능하여, 개발 시의 가독성을 높여준다.
  • 그레고리력, 율리우스력 뿐만 아니라, 이슬람교, 에티오피아의 달력 등 다양한 달력에 대한 클래스를 구현했다.

Java 11

String 클래스 메서드 추가

String 클래스에 새로운 메서드가 추가되었다.

  • strip() : 문자열 앞 뒤의 공백을 제거한다.
  • stripLeading() : 문자열 앞의 공백을 제거한다.
  • stripTrailing() : 문자열 뒤의 공백을 제거한다.
  • isBlank() : 문자열이 비어있거나 공백만 포함된 경우 true 를 반환한다. trim().isEmpty() 와 결과 동일하다.
  • repeat(int num) : 매개변수의 숫자만큼 문자열을 반복한다.

toArray()

Collection 에 댛 toArray() 메서드를 통해 원하는 타입의 배열로 반환한다.

List<String> list = Arrays.asList("1", "2", "3");
String[] array = list.toArray(String[]::new);
System.out.println(Arrays.toString(array));

HTTP2 Client

Java 9 에서 등록된 HTTP Client 가 표준 기능이 되었다. HTTP/1.1HTTP/2.0 을 지원하며, 기존 HTTP API 대비 전반적인 성능이 향상되었다.

G1 GC

기본 가비지 컬렉터가 변경되었다. Java 8parallel GC 대비 GC 의 성능이 크게 높아졌다

Java 17

텍스트 블록 기능 추가

자바스크립트의 template literal 과 유사한 기능으로, 이스케이프 시퀀스 없이 멀티 라인의 문자열을 작성하는 것이 가능하다.

// 문자열 조합
String query1 = "SELECT \"EMP_ID\", \"LAST_NAME\" FROM \"EMPLOYEE_TB\"\n" +
           "WHERE \"CITY\" = 'INDIANAPOLIS'\n" +
           "ORDER BY \"EMP_ID\", \"LAST_NAME\";\n";

// 텍스트 블록
String query = """
           SELECT "EMP_ID", "LAST_NAME" FROM "EMPLOYEE_TB"
           WHERE "CITY" = 'INDIANAPOLIS'
           ORDER BY "EMP_ID", "LAST_NAME";
           """;

스위치 표현식

기존 열거형에서 표현식을 사용하는 것이 가능해졌다.

private static void oldStyleWithBreak(Fruit fruit) {
//    switch (fruit) {
//        case APPLE, PEAR: 
//        // multivalue labels, and are indeed not supported prior to Java 14
//            System.out.println("Common fruit");
//            break;
//        case ORANGE, AVOCADO:
//            System.out.println("Exotic fruit");
//            break;
//        default:
//            System.out.println("Undefined fruit");
//    }
    
    switch (fruit) {
        case APPLE, PEAR -> System.out.println("Common fruit");
        case ORANGE, AVOCADO -> System.out.println("Exotic fruit");
        default -> System.out.println("Undefined fruit");
    }
}

컴팩트한 숫자 포맷 지원

NumberFormat fmt = NumberFormat.getCompactNumberInstance(Locale.ENGLISH, NumberFormat.Style.SHORT);
System.out.println(fmt.format(1000));
System.out.println(fmt.format(100000));
System.out.println(fmt.format(1000000));

[Output]
1K
100K
1M

fmt = NumberFormat.getCompactNumberInstance(Locale.ENGLISH, NumberFormat.Style.LONG);
System.out.println(fmt.format(1000));
System.out.println(fmt.format(100000));
System.out.println(fmt.format(1000000));

[Output]
1 thousand
100 thousand
1 million

Java 21

가상 스레드

기존 JDK 의 스레드는, 운영체제 (OS) 스레드를 활용하여, 사용 가능한 스레드의 수가 하드웨어 수준 보다 훨씬 적게 제한되어 있으며, 비용이 높아, 요청량에 비례하여 늘리기 어렵다. 즉 어플리케이션의 처리량은 스레드 풀을 감당할 수 있는 범위 내에서만 요청을 수용하는 것이 가능했다.

이러한 문제를 해결하기 위해 등장한 것이 Java 21 의 가상 스레드이다. 기존 OS 스레드를 사용하지 않고, JVM 레벨에서 내부 스케줄링을 통해 경량의 스레드를 제공한다.

Kotlin coroutine

기존 스레드의 문제를 대처하기 위한 요소로 Kotlin 진영의 coroutine 이 있다. coroutine 은 단일 스레드로 여러작업을 동시에 처리할 수 있도록 도와주는 기능을 제공하며, 대규모 요청에 대한 효율적인 처리 프로세스를 제공한다. JavascriptPromise 와 유사한 특징을 보인다.

Java 21 의 가상 스레드는 기술적 의존성 없이, 다수의 요청에 대한 리소스를 제어하기 위해 등장한 기술이라 할 수 있다.

Sequenced Collections

기존 Collection 에 대한 접근 방식의 경우, List, Deque, SortedSet 등 클래스마다 요소를 가져오는 방식이 달랐다.

  • List : list.get(0), list.get(list.size()-1)
  • Deque : deque.getFirst(). deque.getLast()
  • SortedSet : sortedSet.first(), sortedSet.last()

이를 위해, Collection 별 공통사항을 정의하는 인터페이스를 추가했다.

interface SequencedCollection<E> extends Collection<E> {
    // new method
    SequencedCollection<E> reversed();

    // methods promoted from Deque
    void addFirst(E);
    void addLast(E);

    E getFirst();
    E getLast();

    E removeFirst();
    E removeLast();
}

interface SequencedSet<E> extends Set<E>, SequencedCollection<E> {
    SequencedSet<E> reversed();    // covariant override
}

interface SequencedMap<K,V> extends Map<K,V> {
    // new methods
    SequencedMap<K,V> reversed();
    SequencedSet<K> sequencedKeySet();
    SequencedCollection<V> sequencedValues();
    SequencedSet<Entry<K,V>> sequencedEntrySet();
    V putFirst(K, V);
    V putLast(K, V);
    // methods promoted from NavigableMap
    Entry<K, V> firstEntry();
    Entry<K, V> lastEntry();
    Entry<K, V> pollFirstEntry();
    Entry<K, V> pollLastEntry();
}

참고
[자바] Java 8 버전 특징
Java 8 람다 표현식 자세히 살펴보기
[10분 테코톡] 깃짱, 이리내의 람다와 스트림
Java 스트림 Stream (1) 총정리 |Eric Han's IT Blog
[Java] Optional이란? Optional 개념 및 사용법 - (1/2)
[Java] LocalDate,LocalTime,LocalDateTime 총 정리
Java의 날짜와 시간 API
[Java] Java8과 Java11의 특징
우리팀이 JDK 17을 도입한 이유
[Coroutine] 결과를 반환받는 async & await
자바의 Virtual Thread가 나와도 코틀린의 코루틴은 여전히 살아남을까?

profile
새로운 것에 관심이 많고, 프로젝트 설계 및 최적화를 좋아합니다.

1개의 댓글

comment-user-thumbnail
2024년 3월 15일

잘보았습니다

답글 달기