5 분 소요

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 페이지를 참고해 주세요

모던 자바 인 액션 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 정리 첨부

Chapter 13. 디폴트 메서드(Default Method)

Optional 클래스의 ifPresentOrElse() 추가

  • 기존 Optional의 ifPresent()는 Optional 객체가 값을 담고 있는 경우만 처리
  • ifPresentOrElse()는 해당 객체가 값이 없을 경우 처리할 내용까지 정의가 가능
    • ifPresent() + 없을 때 대처법

관련된 내용 Notion 정리 첨부

Chapter 11. null 대신 Optional 클래스

신규 문자열 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);
	}

}

댓글남기기