[자바] JDK 17 까지의 변천사 by TestCode
References
2023년 10월 20일 기준 Java는 현재 4개의 LTS(Long Term Support) 버전을 제공하고 있다.
- JDK 8
- JDK 11
- JDK 17: Spring Boot 3.0 부터 JDK 17 이상을 사용해야 함
- 프리코스에서 JDK 17을 도입한 이유가 본 과정의 웹 프로그래밍 단계에서 Spring Boot 3.0 이상을 사용하기 때문인거 같음
- JDK 21
첫 LTS 버전 JDK 8 이후인 9 부터 11, 11 부터 17 사이의 주요 변경사항을 테스트를 통해 학습하고 정리해 본다.
JDK 8 내용을 자세히 확인하고 싶으면 아래 Notion 페이지를 참고해 주세요
📌 JDK 9 ~ JDK 11 주요 업데이트
Collection Factory Method 기능 강화
- List, Set, Map 인터페이스에 immutable(불변) 생성할 수 있는 새로운 기능 추가
- 불변 객체: 한번 할당하면 내부 데이터를 변경할 수 없다.
- 하지만 재할당이 가능함
- 불변 객체: 한번 할당하면 내부 데이터를 변경할 수 없다.
- 불변 컬렉션: 데이터 추가, 삭제, 수정을시도하면 UnsupportedOperationException 발생
- 각 인터페이스에 불변 컬렉션을 반환해주는 기능들이 추가
- of()
- emptyList(), emptySet(), emptyMap()등 다양한 불변 컬렉션을 생성해줌
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class CollectionFactoryMethodInJdk11Test {
/**
* List.of(): 불변 리스트(Immutable List) 생성
* - 값 추가, 삭제, 변경 불가능
* - 하지만 재할당 가능
*/
@Test
@DisplayName("List Factory Method")
void listFactory_test() {
// given
List<Integer> list = List.of(1, 2, 3);
List<Integer> before = List.of(1, 2, 3, 4);
// when & then
assertThatThrownBy(() -> list.add(4))
.isInstanceOf(UnsupportedOperationException.class);
assertThatThrownBy(() -> list.remove(0))
.isInstanceOf(UnsupportedOperationException.class);
assertThatThrownBy(() -> list.set(0, 0))
.isInstanceOf(UnsupportedOperationException.class);
before = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
assertThat(before).containsExactly(1, 2, 3, 4, 5);
}
@Test
@DisplayName("Collections가 제공하는 empty collection")
void test() {
// given
List<Integer> emptyList = Collections.emptyList();
// when & then
assertThatThrownBy(() -> emptyList.add(1))
.isInstanceOf(UnsupportedOperationException.class);
assertThat(emptyList.isEmpty()).isTrue();
}
}
Interface private method
- 인터페이스 내 private method를 사용할 수 있다
- 인터페이스를 구현하는 클래스에서 구현에 대한 세부 정보를 숨길 수 있다(은닉화 & 캡슐화)
- private method를 사용하면 default method 간 코드를 공유하고 중복을 줄일 수 있다
- 코드 가독성과 유지보수성 향상은 덤
public interface MyInterface {
default void publicMethod() {
// 다양한 로직 수행
privateMethod();
}
default void anotherPublicMethod() {
// 다른 로직 수행
privateMethod();
}
private void privateMethod() {
// 중복되는 로직
}
}
관련된 내용 Notion 정리 첨부
Optional 클래스의 ifPresentOrElse() 추가
- 기존 Optional의 ifPresent()는 Optional 객체가 값을 담고 있는 경우만 처리
- ifPresentOrElse()는 해당 객체가 값이 없을 경우 처리할 내용까지 정의가 가능
- ifPresent() + 없을 때 대처법
관련된 내용 Notion 정리 첨부
신규 문자열 method
- isBlank(): empty() + 공백의 문자열이 들어오면 true를 반환
- lines(): 개행문자(\n, \r, \r\n)를 기준으로 여러 줄의 문자열을 Stream
으로 반환 - strip(): 문자열 앞 뒤의 공백을 제거
- trim()과의 차이점: trim()은 ‘\u0020’ 이하의 공백만들 제거하지만, strip()은 유니코드의 공백들을 모두 제거
- 유니코드에는 스페이스(’\u0020’), 탭(’\u0009’) 외에도 더 많은 종류의 공백 문자들이 존재하는데, strip()을 사용하면 더 많은 종류의 공백을 제거할 수 있음
- stripLeading(): 문자열 앞의 공백을 제거
- stripTrailing(): 문자열 뒤의 공백을 제거
- repeat(): 인수로 받은 count 수 만큼 문자열을 반복해 반환
import static org.assertj.core.api.Assertions.assertThat;
import java.util.List;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
class StringMethodInJdk11Test {
/**
* isBlank(): empty() + 공백의 문자열이 들어오면 true를 반환
*/
@Test
@DisplayName("신규 문자열 Method 기능: isBlank()")
void isBlank_test() {
String empty = "";
String whitespaces = " ";
assertThat(empty.isBlank()).isTrue();
assertThat(whitespaces.isBlank()).isTrue();
}
/**
* lines(): 개행문자(\\n, \\r, \\r\\n)를 기준으로 여러 줄의 문자열을 Stream<String>으로 반환
*/
@Test
@DisplayName("신규 문자열 Method 기능: lines()")
void lines_test() {
// given
String multiLine = "Hello\\nWorld\\rJDK11\\r\\nCOOL";
// when
List<String> result = multiLine.lines()
.map(String::trim)
.toList();
// then
assertThat(result).containsExactly("Hello", "World", "JDK11", "COOL");
System.out.println("lines() result to list = " + result);
}
/**
* strip(): 문자열 앞 뒤의 공백들을 제거
* - 스페이스('\\u0020') 이하 공백만 제거하는 trim() 보다 더 많은 공백 문자들을 제거할 수 있음
* stripLeading(): 문자열 앞의 공백만 제거
* stripTrailing(): 문자열 뒤의 공백만 제거
*/
@Test
@DisplayName("신규 문자열 Method 기능: strip()")
void strip_test() {
// given
String input = " Hello\\u2003"; // \\u2003: Em Space 공백
// when
String strip = input.strip();
String stripLeading = input.stripLeading();
String stripTrailing = input.stripTrailing();
// then
assertThat(strip).isEqualTo("Hello");
assertThat(stripLeading).isEqualTo("Hello\\u2003");
assertThat(stripTrailing).isEqualTo(" Hello");
}
/**
* repeat(): 인수로 받은 count 수 만큼 문자열을 반복해 반환
*/
@Test
@DisplayName("신규 문자열 Method 기능: repeat()")
void repeat_test() {
// given
String abc = "abc";
// when
String result = abc.repeat(2);
// then
assertThat(result).isEqualTo("abcabc");
}
}
📌 JDK 12 ~ JDK 17 주요 업데이트
Record Data Class
public 레코드명(헤더) {
바디
}
- 불변(immutable) 객체를 생성하는 새로운 유형의 클래스
- 불변 객체이기 때문에 모든 값은 생성자를 통해 주입해야 한다
- abstract로 선언할 수 없음
- 기존 클래스에서 구현해야 했던 toString(), equals(), hashcode() 메서드를 자동으로 구현해주며, 모든 인스턴스 필드를 초기화해주는 생성자가 생성된다
- 모든 필드가 private final 로 선언되기 때문에 다른 클래스를 상속받을 수 없음, 다만 인터페이스 구현은 가능
- No extends, Yes implements
- 바디 부분에 제공되는 메서드를 오버라이딩하거나 새로운 메서드를 추가로 작성할 수 있음
이제부터 name, age를 필드로 갖는 Member를 레코드를 사용하는 방법으로 변환해본다.
public class Member {
private final String name;
private final int age;
public Member(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Member member = (Member)o;
return age == member.age && Objects.equals(name, member.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Member{" +
"name='" + name + '\\'' +
", age=" + age +
'}';
}
}
레코드로 변환하면…
public record Member(String name, int age) {
}
- 마법처럼 단 2줄로 끝난다
- toString()처럼 제공되는 메서드를 기존 클래스처럼 원하는 대로 재정의 가능
public record Member(String name, int age) {
@Override
public String toString() {
return "이름 = " + name + ", 나이 = " + age;
}
}
Record Data Class 테스트해보기
- 주의!! 레코드에서 getter를 사용할 때는 필드명만 사용하면 된다(like 빌더 패턴)
- getName()이 아니라 name()
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
class MemberTest {
@Test
void record_test() {
// given
Member member1 = new Member("이름", 10);
Member member2 = new Member("이름", 10);
Member member3 = new Member("이름", 20);
// when
String memberName = member1.name();
int memberAge = member1.age();
// then
// getter 테스트
assertThat(memberName).isEqualTo("이름");
assertThat(memberAge).isEqualTo(10);
// equals() & hashcode() 테스트
assertThat(member1).isEqualTo(member2);
assertThat(member1).isNotEqualTo(member3);
System.out.println(member1);
}
}
댓글남기기