Search
📁

⭐ Java 버전별 새로운 기능

자바의 LTS버전들

이렇게 자바의 표를 보면 LTS 라는게 있는데 Long Term Support 의 약자이다.
출시 후 장기 지원을 의미한다. (보통 3년에서 5년간의 장기 지원)
LTS 버전
8, 11, 17, 21
우선 LTS 버전을 사용해야지 개발자들은 안정성과 호환성을 보장받을 수 있음.
그래서 서비스 수명 연장되고 유지보수 비용도 줄어든다.
제일 가장 많이 쓰인 버전이 Java8 버전.
오라클이 자바를 인수하고 출시한 첫번째 LTS버전이기도 하고 lamba와 Stream API 같은 편리 기능이 추가된 시점.
내가 속한 프로젝트는 아직도 java8를 쓰고있고, 첫 개인 프로젝트에서는 java11로 시작했다가
현재는 스프링부트 3.0 이상 버전부터는 java17 이상만 지원이여서 개인 프로젝트에서는 17만 사용하고있다.
2024년 2월 현재의 LTS 버전은 Java 11이고 9월까지 지원 보장.
다음 LTS는 17버전이다. 17은 2029년 9월까지 보장 예정.
확실히 17이 서포트 기간이 길기도 하고 스프링부트 3.0때문에 스프링 부트를 쓰는 프로젝트는 지금 현재 많이 쓰고 있지않을까 싶다.
LTS 버전 위주로 추가된 대표 기능만 정리해보자.

Java 8

람다 표현식
스트림 API
Optional
함수형 인터페이스(Functional Interface) 도입 - 오직 1개의 추상 메소드를 갖는 인터페이스
// 람다 표현식 예시 List<String> names = Arrays.asList("John", "Jane", "Doe"); names.forEach(name -> System.out.println(name)); // 스트림 API 예시 List<String> namesWithJ = names.stream() .filter(name -> name.startsWith("J")) .collect(Collectors.toList());
Java
복사
잠만, 함수형인터페이스를 쓰는 이유
자바의 람다식은 함수형 인터페이스로만 접근이 되기 때문.

1. 람다 표현식

익명 함수를 간결하게 표현하는 방법.
함수를 변수처럼 다룰 수 잇게 해주는 기능. 함수형 인터페이스를 구현할 때 사용.
람다 표현식은 (매개변수) -> { 실행 코드 } 형식으로 표현.

람다 표현식 예제:

// 람다 표현식을 이용한 Runnable 구현 Runnable runnable = () -> System.out.println("Hello Lambda");
Java
복사

기존의 소스 예제:

// 익명 내부 클래스를 이용한 Runnable 구현 Runnable runnable = new Runnable() { @Override public void run() { System.out.println("Hello Anonymous Inner Class"); } };
Java
복사

언제 사용하면 좋을까

병렬 처리나 스트림 API와 같은 기능과 함께 사용하면 코드를 더 간단하고 효율적으로 작성할 수 있습니다.
 잠깐! 병렬 처리가 뭔데? 여러 작업을 한꺼번에 처리하는 것!

장점

간결한 코드
가독성 향상
함수형 프로그래밍 지원
병렬 및 스트림 처리

단점

과용시 코드 복잡해져서 가독성이 떨어질 수 있음
디버깅의 어려움(익명 함수로 정의되기 때문)
코드 유지보수의 어려움(익명 함수로 다른곳에서 재사용하기 힘듦)

2. Stream API

새로운 데이터 처리 API
컬렉션, 배열 등의 데이터 소스를 함수형 프로그래밍 방식으로 다룰 수 있도록 도와줌.
스트림은 데이터를 처리하는데 사용되며, 중간 연산과 최종 연산으로 구성.
중간 연산은 스트림을 변환하거나 필터링하는 작업을 수행하고,
최종 연산은 스트림의 요소를 소모하고 결과를 생성하는 작업을 수행한다.

스트림 API 예제:

List<String> names = Arrays.asList("John", "Jane", "Doe"); // 스트림을 이용한 데이터 필터링과 출력 names.stream() .filter(name -> name.startsWith("J")) //.forEach(name -> System.out.println(name)) 람다식으로 밑에처럼 바꿈. .forEach(System.out::println);
Java
복사
stream() 메서드를 호출하여 스트림을 생성.
스트림은 데이터를 연속적으로 처리하는 파이프라인.
그 후에는 filter 메서드를 호출하여 이름이 "J"로 시작하는 요소만 필터링.
이 메서드는 인자로 전달된 조건을 만족하는 요소만을 스트림에 포함시킴.
마지막으로 forEach 메서드를 호출하여 각 요소를 출력합니다. 이때 System.out::println은 메서드 레퍼런스로, 각 요소를 출력하는 데 사용.

기존의 소스 예제:

List<String> names = Arrays.asList("John", "Jane", "Doe"); // 기존의 방식으로 데이터 필터링과 출력 for (String name : names) { if (name.startsWith("J")) { System.out.println(name); } }
Java
복사

언제 사용하면 좋을까

데이터를 변환하거나 필터링해야 할 때
병렬 처리를 수행해야 할 때
함수형 프로그래밍 스타일을 사용하여 코드를 작성하고 싶을 때

장점

간결한 코드 작성과 가독성 향상
병렬 처리를 간단하게 구현할 수 있음
함수형 프로그래밍 스타일을 지원하여 코드의 유연성과 재사용성이 높아짐

단점

스트림의 사용이 익숙하지 않은 경우 학습 곡선이 높을 수 있음
일부 복잡한 데이터 처리 작업은 스트림 API로 표현하기 어려울 수 있음

3. Optional

null 처리의 복잡성을 줄이고, 더 명확하게 의도를 표현할 수 있게 해주는 래퍼 클래스.
Optional을 사용하면 null 가능성이 있는 값을 더 안전하게 처리할 수 있으며, 코드의 가독성을 향상시킴.
Optional<T>T 타입의 객체를 감싸는 래퍼 클래스로, 결과가 없을 수 있는 연산에 사용.
이 클래스는 결과가 있거나, 또는 결과가 없는 경우를 명시적으로 처리하기 위해 설계되었음.
Optional을 사용함으로써, null을 직접 다루는 대신 보다 표현력 있는 API를 통해 값의 존재 여부를 처리할 수 있다.

예제:

import java.util.Optional; public class OptionalExample { public static void main(String[] args) { // 값이 존재하는 Optional 객체 생성 Optional<String> hasValue = Optional.of("Hello, Optional!"); // null을 포함할 수 있는 Optional 객체 생성 Optional<String> mightBeNull = Optional.ofNullable(null); // 값이 존재하는 경우 출력, 그렇지 않은 경우 다른 문자열 출력 String result1 = hasValue.orElse("Default Value"); System.out.println(result1); // "Hello, Optional!" 출력 // 값이 존재하지 않는 경우, 기본값 출력 String result2 = mightBeNull.orElse("Default Value"); System.out.println(result2); // "Default Value" 출력 // ifPresent를 사용하여 값이 존재할 때만 동작 실행 hasValue.ifPresent(value -> System.out.println("값이 존재합니다: " + value)); // map을 사용하여 값이 존재하는 경우 변환 수행 Optional<Integer> length = hasValue.map(String::length); System.out.println("문자열 길이: " + length.orElse(-1)); // 문자열 길이 출력 } }
Java
복사

자바 8 이전: null 처리 방식 예시

public class User { private String name; public User(String name) { this.name = name; } public String getName() { return name; } } public class UserService { public User findUserByName(String name) { // 사용자 조회 로직 (단순화를 위해 하드코딩) if ("Alice".equals(name)) { return new User(name); } else { return null; // 사용자를 찾을 수 없는 경우 null 반환 } } public static void main(String[] args) { UserService userService = new UserService(); User user = userService.findUserByName("Alice"); if (user != null) { System.out.println("User found: " + user.getName()); // "User found: Alice" 출력 } else { System.out.println("User not found"); } } }
Java
복사

자바 8 이후: Optional을 사용한 방식 예시

import java.util.Optional; public class User { private String name; public User(String name) { this.name = name; } public String getName() { return name; } } public class UserService { public Optional<User> findUserByName(String name) { // 사용자 조회 로직 (단순화를 위해 하드코딩) if ("Alice".equals(name)) { return Optional.of(new User(name)); } else { return Optional.empty(); // 사용자를 찾을 수 없는 경우 빈 Optional 반환 } } public static void main(String[] args) { UserService userService = new UserService(); // "Alice"를 찾는 경우 Optional<User> userAlice = userService.findUserByName("Alice"); userAlice.ifPresent(u -> System.out.println("User found: " + u.getName())); // "User found: Alice" 출력 System.out.println("User: " + userAlice.orElse(new User("default")).getName()); // "User: Alice" 출력 // "Bob"을 찾는 경우 (빈 Optional 반환 예시) Optional<User> userBob = userService.findUserByName("Bob"); userBob.ifPresent(u -> System.out.println("User found: " + u.getName())); // ifPresent 내부의 람다 표현식은 실행되지 않음 (값이 없기 때문) // orElse를 사용하여 사용자가 없는 경우의 기본 처리 System.out.println("User: " + userBob.orElse(new User("default")).getName()); // "User: default" 출력 } }
Java
복사

언제 사용하면 좋을까

메소드 반환 값:
메소드가 결과를 반환할 수도 있고 아닐 수도 있는 경우에 Optional을 사용하는 것이 좋다.
파라미터 값이 null일 수 있는 경우를 대비하여:
메소드의 파라미터로 Optional을 사용함으로써, 호출자에게 입력 값의 선택성을 명시적으로 전달할 수 있다.
체이닝 연산:
Optionalmap, flatMap 등의 메소드를 사용하면 여러 단계에 걸친 연산을 체이닝할 때 null 체크를 우아하게 처리할 수 있다.

장점

NullPointerException 방지:
Optional을 사용함으로써 런타임에 NullPointerException을 발생시킬 가능성을 줄일 수 있다.
명시적인 API:
값이 있을 수도 있고 없을 수도 있다는 것을 API 수준에서 명시적으로 표현할 수 있다.
함수형 프로그래밍 지원:
map, flatMap, filter 등의 메소드를 통해 함수형 프로그래밍 기법을 적용할 수 있다.

단점

성능 저하:
간단한 상황에서 과도하게 사용하면 객체 생성으로 인한 미세한 성능 저하가 발생할 수 있다.
직렬화 불가능:
Optional은 직렬화할 수 없으므로, 직렬화가 필요한 상황에서는 사용할 수 없다.

4. 최종

java에서
람다식은 작업을 간단하게 나타내는 방법
스트림 API는 데이터를 처리하는 방법
이 2가지를 사용하면 병렬 처리의 작업을 빠르게 처리할 수 있다.
근데 그렇다고 항상 빠른건 아님.
오히려 오버헤드로 초례할 수 도 있고 작업들간에 의존성이 생기거나 데이터 공유헤야하는 경우에는 병렬 처리 때문에 복잡해질 수 있음. 작업의 특성과 데이터 간의 관계를 생각하고 쓰기.

Java 11

지역 변수 유형 추론, 문자열 API 개선, HTTP 클라이언트 도입.
var 키워드를 사용하여 지역 변수의 유형을 추론할 수 있음.
HTTP 클라이언트는 기존의 HttpURLConnection을 대체하는 새로운 API를 제공.
// 지역 변수 유형 추론 예시 var number = 10; // int로 추론됨 // HTTP 클라이언트 예시 HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.example.com")) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body());
Java
복사

1. 지역 변수의 유형 추론 (Local Variable Type Inference)

var 키워드를 사용하여 변수를 선언할 때 컴파일러가 유형을 추론

예제:

var message = "Hello, Java 11!"; var number = 10;
Java
복사

언제 사용하면 좋을까

코드의 가독성을 향상시키고 반복적인 유형 선언을 줄이고 싶을 때 사용.

장점

코드의 가독성을 향상
반복적인 유형 선언을 줄여줌.

단점

유형 추론이 과도하게 사용되면 코드의 가독성이 감소.

2. 문자열 관련 개선 사항

문자열에 대한 새로운 메서드가 추가

예제:

isBlank(): 문자열이 비어있거나 공백 문자만을 포함하는지 확인.
public class Main { public static void main(String[] args) { String str1 = ""; // 빈 문자열 String str2 = " "; // 공백 문자열 String str3 = "Hello World"; // 일반 문자열 // isBlank() 메서드를 사용하여 문자열이 비어있거나 공백 문자만 포함되어 있는지 확인 System.out.println("str1 is blank: " + str1.isBlank()); // 출력: str1 is blank: true System.out.println("str2 is blank: " + str2.isBlank()); // 출력: str2 is blank: true System.out.println("str3 is blank: " + str3.isBlank()); // 출력: str3 is blank: false } }
Java
복사

예제:

strip(), stripLeading(), stripTrailing(): 문자열의 앞뒤로 있는 공백을 제거.
public class Main { public static void main(String[] args) { String stringWithSpaces = " Hello "; // strip() 메서드를 사용하여 문자열의 앞뒤 공백을 제거 String trimmedString = stringWithSpaces.strip(); System.out.println("Trimmed string: '" + trimmedString + "'"); // 출력: Trimmed string: 'Hello' // stripLeading() 메서드를 사용하여 문자열의 앞쪽 공백을 제거 String trimmedLeadingString = stringWithSpaces.stripLeading(); System.out.println("Trimmed leading string: '" + trimmedLeadingString + "'"); // 출력: Trimmed leading string: 'Hello ' // stripTrailing() 메서드를 사용하여 문자열의 뒤쪽 공백을 제거 String trimmedTrailingString = stringWithSpaces.stripTrailing(); System.out.println("Trimmed trailing string: '" + trimmedTrailingString + "'"); // 출력: Trimmed trailing string: ' Hello' } }
Java
복사

예제:

repeat(int count): 문자열을 지정된 횟수만큼 반복하여 새로운 문자열을 생성.
public class Main { public static void main(String[] args) { String repeatedString = "Java ".repeat(3); // "Java Java Java " System.out.println(repeatedString); // 출력: Java Java Java } }
Java
복사

3. HTTP 클라이언트 API (HTTP Client API)

내장된 HTTP 클라이언트 API가 추가되어 네트워크 통신을 더욱 쉽게 사용 가능.

예제:

HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.example.com")) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body());
Java
복사

이전 버전의 예제:

펼치기

언제 사용하면 좋을까

간단한 HTTP 요청을 처리하고 싶을 때 사용하며, 외부 라이브러리를 추가로 사용하지 않고 싶을 때.

장점

외부 라이브러리 없이 간단하게 HTTP 요청을 처리할 수 있다.

단점

복잡한 HTTP 요청을 처리할 때는 외부 라이브러리보다는 편의성이 낮을 수 있다.

Java 14

switch 표현식 개선
instanceof 패턴 매칭
Null 안전 연산자
레코드 타입 도입
Test Blocks
instanceof를 사용한 타입 체크를 더욱 편리하게 할 수 있음.
레코드 타입은 불변의 데이터 컨테이너로 사용하기 편리.
// instanceof 패턴 매칭 예시 if (obj instanceof String str) { System.out.println(str.length()); } // 레코드 타입 예시 record Person(String name, int age) {} Person person = new Person("John", 30); System.out.println(person.name());
Java
복사

1. Switch 표현식 개선

여러 개의 블록을 사용할 수 있게 됨.

예제:

int day = 2; String dayType = switch (day) { case 1, 2, 3, 4, 5 -> "Weekday"; case 6, 7 -> "Weekend"; default -> "Invalid day"; };
Java
복사

이전 버전의 예제:

int day = 2; String dayType; switch (day) { case 1: case 2: case 3: case 4: case 5: dayType = "Weekday"; break; case 6: case 7: dayType = "Weekend"; break; default: dayType = "Invalid day"; }
Java
복사

장점

코드가 더 간결해지고 가독성이 향상

단점

이전 버전과의 호환성을 고려

2. Instanceof 패턴 매칭

Instanceof 패턴 매칭을 통해 Instanceof 연산자와 캐스팅을 함께 사용할 수 있다 .

예제:

Object obj = "Hello"; if (obj instanceof String str) { System.out.println(str.toUpperCase()); }
Java
복사

이전 버전의 예제:

Object obj = "Hello"; if (obj instanceof String) { String str = (String) obj; System.out.println(str.toUpperCase()); }
Java
복사

장점

코드가 더 간결해지고 가독성이 향상

단점

이전 버전과의 호환성을 고려

3. Null 안전 연산자

?. 을 사용해서 널 체크를 더 간결하게 할 수 있게 됨.

예제:

System.out.println(str?.length());
Java
복사

이전 버전의 예제:

String str = null; if (str != null) { System.out.println(str.length()); }
Java
복사

장점

널 체크를 더 간결하게 할 수 있음.

단점

이전 버전과의 호환성을 고려

4. Records

데이터 저장용 클래스를 더 간결하게 정의할 수 있음.

예제:

public record Person(String name, int age) {}
Java
복사

이전 버전의 예제:

public class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } // Getter, Setter, equals, hashCode, toString 등의 메서드 추가 }
Java
복사

장점

코드가 더 간결
데이터 저장용 클래스를 쉽게 정의

단점

이전 버전과의 호환성을 고려

언제 사용하면 좋을까

데이터 저장용 클래스를 간결하게 정의하고 싶을 때

5. Text Blocks

여러 줄로 이루어진 문자열을 더 쉽게 작성할 수 있게 해주는 Text Blocks가 추가

예제:

String html = """ <html> <body> <p>Hello, Java 14!</p> </body> </html> """;
Java
복사

이전 버전의 예제:

String html = "<html>\n" + " <body>\n" + " <p>Hello, Java 14!</p>\n" + " </body>\n" + "</html>\n";
Java
복사

장점

코드가 더 간결
여러 줄로 이루어진 문자열을 작성할 때 가독성이 향상

단점

이전 버전과의 호환성을 고려

언제 사용하면 좋을까

여러 줄로 이루어진 문자열을 작성할 때 사용.

Java 17

패턴 매칭의 확장
불변 컬렉션 생성기 (Sealed 클래스)
메모리 매핑 파일과 새로운 I/O API

1. 패턴 매칭의 확장 (Extended Pattern Matching for Switch)

Switch 표현식의 패턴 매칭 기능이 확장되어 사용자 정의 패턴과 함께 사용할 수 있게 됨.

예제:

String fruit = "Apple"; String result = switch (fruit) { case "Apple" -> "This is an apple"; case "Banana" -> "This is a banana"; case String s && s.length() > 5 -> "This fruit's name is longer than 5 characters"; default -> "Unknown fruit"; }; System.out.println(result);
Java
복사

이전 버전의 예제:

String fruit = "Apple"; String result; switch (fruit) { case "Apple": result = "This is an apple"; break; case "Banana": result = "This is a banana"; break; default: result = "Unknown fruit"; } System.out.println(result);
Java
복사

2. 불변 컬렉션 생성기 (Sealed Classes)

sealed 클래스를 사용하여 클래스를 봉인하고, 불변성을 가진 컬렉션을 생성

예제:

List<String> immutableList = List.of("Apple", "Banana", "Orange");
Java
복사

이전 버전의 예제:

List<String> immutableList = Arrays.asList("Apple", "Banana", "Orange");
Java
복사

장점

불변성을 유지하고자 할 때 유용
불변성이 보장되는 환경을 구축

단점

객체를 변경할 수 없어 특정 상황에서는 제한적

언제 사용하면 좋을까

불변성이 필요한 상황에서 사용

3. 메모리 매핑 파일과 새로운 I/O API (Memory-Mapped Files and New I/O API)

메모리 매핑 파일을 위한 새로운 I/O API가 도입.

예제:

try (FileChannel channel = FileChannel.open(Paths.get("data.txt"), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE)) { MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 1024); buffer.put("Hello, Java 17!".getBytes()); }
Java
복사
FileChannel.open() 메서드를 사용하여 파일을 열고, FileChannel 객체를 얻음.
channel.map() 메서드를 사용하여 파일을 메모리에 매핑. 이 때, MapMode.READ_WRITE로 지정하여 읽기와 쓰기가 가능하도록 함. 메모리 매핑은 파일의 일부 또는 전체를 메모리에 로드하는 것.
MappedByteBuffer 객체를 통해 메모리에 매핑된 파일을 조작할 수 있음.
buffer.put() 메서드를 사용하여 메모리에 매핑된 파일에 데이터를 기록.
"Hello, Java 17!" 문자열을 파일에 씀.

이전 버전의 예제:

File file = new File("data.txt"); RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw"); FileChannel channel = randomAccessFile.getChannel(); MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 1024); buffer.put("Hello, Java 17!".getBytes()); channel.close(); randomAccessFile.close();
Java
복사

장점

대용량 파일을 처리할 때 I/O 성능을 향상
메모리 사용을 최적화

단점

이전 버전과의 호환성을 고려

언제 사용하면 좋을까

대용량 파일을 다룰 때 또는 파일 입출력의 성능을 향상시키고자 할 때 사용.

참고