Java

[Java 16, 17] record class, sealed class

자바 16 - record class

  • 데이터 전달을 위해 등장한 클래스 (DTO, Data Transfer Object)

AS-IS

  • DTO 클래스의 상속을 막기 위해 final class로 정의
  • 캡슐화, 변경 제한을 위해 필드에 private final 선언
  • 최초 값 초기화를 위해 매개변수를 가진 생성자 정의
  • 필드값에 접근하기 위한 getter 생성
  • 비교를 위한 equals(), hashcode() 재정의
  • 데이터 출력(로깅 등)을 위한 toString() 재정의
public final class AsIsDto { // 상속을 막기위한 final class 
    
    private final String username; // 캡슐화, 변경 방지를 위한 private final 필드
    private final int age;

    public AsIsDto(String username, int age) { // 값 초기화 및 객체 생성을 위한 생성자
        this.username = username;
        this.age = age;
    }
    
    public String getName() { // 필드 값 반환을 위한 getter
        return this.username;
    }
    
    public int getAge() {
        return this.age;
    }

    @Override
    public boolean equals(Object obj) { // 객체 비교를 위한 equals(), hashCode() 재정의
        if (this == obj) return true;
        if (obj == null || this.getClass() != obj.getClass()) return false;
        AsIsDto asIsDto = (AsIsDto) obj;
        return this.username.equals(asIsDto.username) && this.age == asIsDto.age;
    }

    @Override
    public int hashCode() {
        return Objects.hash(this.username, this.age);
    }

    @Override
    public String toString() { // 데이터 출력을 위한 toString() 재정의
        return "AsIsDto(name=" + this.getName() + ", age=" + this.getAge();
    }
}

 

 

TO-BE (record class)

  • record class는 내부적으로 Record 클래스를 상속받으므로 상속 불가능 (final class)
    • enum class와 동일하게 인터페이스는 구현 가능
  • 컴포넌트(필드)에 대해 자동으로 private final 생성
  • 필드를 매개변수로 갖는 생성자 자동 생성
  • 각 필드에 접근 가능한 필드명의 getter 자동 생성
  • equals(), hashCode(), toString() 자동 생성
    • 자동 생성되는 메서드들은 모두 재정의 가능
  • compact constructor를 통해 생성자에서 값의 검증 가능 (값 할당은 불가능)
    • 자동 생성된 생성자 호출 -> compact constructor 호출을 통해 검증하는 방식
  • 컴포넌트에 어노테이션을 붙일 경우 기본적으로 자동 생성되는 생성자의 매개변수, getter 메서드에도 모두 붙음
    • 특정 타겟에 대해서만 붙이기 위해서는 어노테이션에 @Target 메타 어노테이션을 통해 적용할 타겟 지정
  • 개발에 자주 사용되는 DTO를 간결하게 작성 가능
public record ToBeDto(
        // 컴포넌트
        @MyAnnotation String username, // private final String username
        int age
) {

    public ToBeDto { // compact constructor
        if (this.age() <= 0) {
            throw new IllegalArgumentException("나이는 1 이상이어야 합니다.");
        }
    }

    @Override
    public String username() { // 자동 생성된 메서드 재정의 가능
        return "내 이름은 " + username;
    }


}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD) // 필드에만 어노테이션 적용
@interface MyAnnotation {}

 

 

 

자바 17 - sealed class

  • 상속을 특정 하위 클래스에만 제한하기 위해 등장한 클래스
  • Animal 클래스를 상속받을 수 있는 클래스를 Dog, Cat 클래스로만 엄격하게 제한하고자 할 때 사용 가능
  • public sealed class ClassName permits SubClass1, SubClass2 형태로 상속 클래스 제한 가능
    • named module인 경우 SubClass는 같은 모듈 내 위치해야함
    • unnamed module인 경우 SubClass는 같은 패키지 내 위치해야함
    • SubClass가 같은 파일 내에 있는 경우 permits 생략 가능
  • 상속 받는 클래스는 final / sealed / non-sealed 키워드 중 하나를 붙여야 함
    • final : 재상속 불가능
    • sealed : 상속 가능. 새로운 계층의 sealed class로 동작 (3계층, A -> B -> C)
    • non-sealed : 상속 가능. 상위 클래스가 non-sealed 클래스를 상속받은 클래스를 추적할 수 없음
  • 인터페이스도 동일한 기능을 가지는 sealed interface로 정의 가능
  • 무분별한 / 추적 불가능한 상속으로 인한 하위 호환성 문제 방어 가능
  • 컴파일 시점에 하위 타입을 알 수 있어 추후 자바 21의 정식기능인 switch pattern matching과 함께 사용시 default 생략 가능 및 하위 타입 데이터 바로 사용 가능
// Car 클래스는 Taxi, Bus 클래스만 상속 가능
public sealed class Car permits Taxi, Bus {}

---- 

// Animal.java
// 같은 파일 내 위치할 경우 permits 생략 가능
public sealed class Animal {}

final class Cat extends Animal {} // 상속 불가능
non-sealed class Tiger extends Animal {} // 상속 가능. Animal에서 해당 클래스 상속받은 클래스 추적 불가
sealed class Dog extends Animal {} // 상속 가능.

final class Chihuahua extends Dog {} // Animal - Dog - Chihuahua 3계층 상속 구조

 

 


Reference

인프런 - 자바 9부터 자바 21까지