Java
프로젝트를 진행하는 경우, IDE
에서 원하는 Java
버전을 선택할 수 있다. 원하는 SDK
를 설치하려는 경우, 언어 버전 혹은 언어 레벨 설정 시 권장되는 버전들이 있다는 것을 확인할 수 있다.
이러한 버전들을 LTS, Long Time Support
라 하며, 일반버전 대비 안정성에 중점을 두어, 장기적인 지원을 제공하는 버전을 뜻한다. 실제 LTS
버전의 경우, 업데이트가 최소한으로 이루어지며, 보안적인 요소도 훨씬 강력하다.
어플리케이션 서비스를 만드는 기업이나 기관의 경우, 기술의 업데이트로 인해 서비스가 영향을 미치는 문제를 피하기 위해, 기술 스택 선정 시 LTS
버전을 택하여 사용하기도 한다.
LTS
는 자바 뿐만 아니라 다수의 오픈소스에서도 많이 적용되고 있으며, 기술마다 다르지만 최소 3~5
년 정도의 지원을 제공한다. Java
의 경우 크게 8,11,17,21
이 LTS
버전에 속한다. 기능적으로 크게 추가된 것은 Java 8
이며, Java 11,17,21
은 최적화를 통해 성능을 높이는데 주력한 버전이다.
자바스크립트의 화살표함수와 유사하게, 함수를 하나의 식으로 표현한 것을 의미한다. 자바스크립트 만큼의 유연함을 제공하지는 못하나, 주로 함수 내 인자로 표현식을 정의하는 용도로 많이 사용된다. ex) 정렬, forEach, null 대체 반환값 등
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
은 데이터의 연속적인 흐름을 나타내는 개념으로, Java 8
에서는 Stream API
를 추가하여 컬렉션의 데이터를 필터링하거나 매핑하는 등 손쉬운 조작을 제공한다.
Stream
의 과정에는 크게 생성 - 중간연산 - 최종연산으로 이루어진다.
Stream
을 생성하는 것을 의미한다. ex) stream()
Stream
에 대한 조작, 처리가 이루어진다. ex) map(), filter()
Stream
데이터를 원하는 결과로 바꾼다. ex) collect()
Stream
은 생성 시 기존의 원본데이터를 읽어서, 새로운 Stream
객체를 생성한 후, Stream
객체에 대한 조작을 이룬후, 새로운 객체를 리턴한다.Stream
을 반환하기 때문에, 체이닝이 가능하다. 따라서 중간연산에 대한 결과를 가지고 새로운 중간연산을 진행할 수 있다.Stream
의 매개변수로 람다표현식이나 메서드 참조를 대입하는 것이 가능하다.Stream
에 대해 직접 스레드를 관리하지 않아도 된다.ex) parallelStream(), parallel()
> 병렬 처리의 경우 무조건 더 나은 결과를 보장한다고 할 수는 없다. 머리 스레드 환경에서의 컨텍스트 스위칭 비용이나, 데이터의 동기화 유무에서 문제가 발생할 수도 있다. 만약 `Stream` 에 대한 병렬처리를 진행하기를 고민한다면, 성능 개선이 유의미한지 예상치 못한 장애를 발생시키지는 않는지 등을 충분히 고민할 필요가 있다.
>
임의의 값이 존재할수도, 존재하지 않을수도 있는 상황을 다룰 때 사용하는 클래스이다. 주로 어플리케이션 생명주기 내에 DB
와의 통신 시 NullPointerException
을 방지하기 위해 사용한다.
Null
을 방어할 수 있다. Optional
값이 존재하는 경우 해당 값을 추출하거나, null
인 경우 이에 대한 대응책을 마련할 수 있다.Optional
클래스는 체이닝이 가능하며, 상황에 따라 기본값을 할당하는 메서드들을 제공한다. 예를들어, orElse()
을 통해 값이 null
인 경우 기본값을 할당할 수 있다.Optional<String> optionalName = Optional.ofNullable(getName());
String name = optionalName.orElse("Unknown");
Java8
의 경우, LocalDate
, LocalTime
, LocalDateTime
새로운 시간, 날짜 API
가 등장했다. 이는 기존의 [java.util.Date](http://java.util.Date)
및 java.util.Calendar
클래스 사용시 불편사항을 개선하기 위해서 였다.
Date
클래스는 가변성을 가지고 있다. 객체가 생성된 이후에도 연산을 통해, 값을 변경하는 것이 가능하다. 이 경우, 스레드가 동시에 접근할 때 안전하지 않은 상태를 만들 수 있다.Date
클래스는 월을 나타내는 숫가 0
부터 시작한다. 즉 0
이 1월이고, 11
이 12월이 된다.Calendar
의 get(Calendar.DAY_OF_WEEK)
는 함수에서 반환한 요일은 int
값으로 반환한다. 이때 일요일을 1
로 응답하는 것을 시작으로 다음 요일을 1
씩 증가한 값을 반환한다. 그러나, Date
의 getDay()
는 일요일은 0
으로 응답한다. 즉 두개의 클래스 사이에 요일 지정 반환 숫자가 일관성이 없다.기존 JDK
를 대체하는 날짜와 시간 API로 많이 사용되었던 라이브러리이다. 위의 Date, Canlerdar
의 문제를 해결하는 것은 물론, 날짜, 시간 시 나타날 수 있는 문제들을 해결했다.
추후 Java8
에서 등장한 Time API
는 Joda-Time
의 영향을 많이 받았다고 한다.
plusDays()
, plusSeconds()
등의 날짜 연산 메서드를 지원한다. 해당 메서드 모두 체이닝이 가능하여, 개발 시의 가독성을 높여준다.String
클래스에 새로운 메서드가 추가되었다.
true
를 반환한다. trim().isEmpty()
와 결과 동일하다.Collection
에 댛 toArray()
메서드를 통해 원하는 타입의 배열로 반환한다.
List<String> list = Arrays.asList("1", "2", "3");
String[] array = list.toArray(String[]::new);
System.out.println(Arrays.toString(array));
Java 9
에서 등록된 HTTP Client
가 표준 기능이 되었다. HTTP/1.1
및 HTTP/2.0
을 지원하며, 기존 HTTP API
대비 전반적인 성능이 향상되었다.
기본 가비지 컬렉터가 변경되었다. Java 8
의 parallel GC
대비 GC
의 성능이 크게 높아졌다
자바스크립트의 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
기존 JDK
의 스레드는, 운영체제 (OS) 스레드를 활용하여, 사용 가능한 스레드의 수가 하드웨어 수준 보다 훨씬 적게 제한되어 있으며, 비용이 높아, 요청량에 비례하여 늘리기 어렵다. 즉 어플리케이션의 처리량은 스레드 풀을 감당할 수 있는 범위 내에서만 요청을 수용하는 것이 가능했다.
이러한 문제를 해결하기 위해 등장한 것이 Java 21
의 가상 스레드이다. 기존 OS
스레드를 사용하지 않고, JVM
레벨에서 내부 스케줄링을 통해 경량의 스레드를 제공한다.
기존 스레드의 문제를 대처하기 위한 요소로 Kotlin
진영의 coroutine
이 있다. coroutine
은 단일 스레드로 여러작업을 동시에 처리할 수 있도록 도와주는 기능을 제공하며, 대규모 요청에 대한 효율적인 처리 프로세스를 제공한다. Javascript
의 Promise
와 유사한 특징을 보인다.
Java 21
의 가상 스레드는 기술적 의존성 없이, 다수의 요청에 대한 리소스를 제어하기 위해 등장한 기술이라 할 수 있다.
기존 Collection
에 대한 접근 방식의 경우, List, Deque, SortedSet
등 클래스마다 요소를 가져오는 방식이 달랐다.
list.get(0), list.get(list.size()-1)
deque.getFirst(). deque.getLast()
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가 나와도 코틀린의 코루틴은 여전히 살아남을까?
잘보았습니다