record,sealed,ADT

느리게 따라가기·2023년 3월 19일
0

Java

목록 보기
1/1

1. record

  • since java 14 preview, 16 정식기능
  • JEP 359(https://openjdk.org/jeps/359)
  • Records provide a compact syntax for declaring classes which are transparent holders for shallowly immutable data
  • Dependency : Records go well with sealed types (JEP 360)
    records and sealed types taken together form a construct often referred to as algebraic data types

  • 클래스의 형태중 데이터에 한정된 특수한 형태라 볼수 있다.
  • 자바는 모던 언어에 비해 데이터를 표현하는데, "Java is too verbose" or has too much "ceremony"하다는 불만이 제기되어왔다.
    (final,setter, getter,생성자, toString, equals, hashCode등을 만들어주어야 함.)
  • 이런 불만으로 인해 Lombok등 3rd part 라이브러리를 이용하였다.
  • 이런 상황을 Language 레벨에서 제공하기 위해 추가된 기능이 record이다.

특징

  • 다른 클래스를 상속받을 수 없다.
  • abstract로 선언할 수 없으며 암시적으로 final로 선언된다.
  • 컴포넌트는 암시적으로 final로 선언된다.
  • 멤버 변수를 명시적으로 선언하지 않아도 자동으로 생성
  • get, hashCode, equals, toString 메소드 자동 생성

1.1 문법

record 레코드명(컴포넌트1, 컴포넌트2, ...) { }

  • 컴포넌트는 마치 클래스의 생성자와 같이 매개변수를 나열하면 되는데, 이를 컴포넌트(component)라고 호칭함

1.2 사용예

public record Person(String name, int age) {
    // 생성자에서 유효성 검사 등을 수행할 수 있습니다.
    // 유효성 검사가 필요 없다면 아래코드는 필요없다.
    public Person {
        if (name == null || name.isEmpty()) {
            throw new IllegalArgumentException("name must not be null or empty");
        }
        if (age <= 0) {
            throw new IllegalArgumentException("age must be positive");
        }
    }
}
Person person = new Person("Alice", 30);
System.out.println(person.name()); // "Alice"
System.out.println(person.age()); // 30
  • get,toString,equals가 자동을 생성됨.
  • set은 불변객체이므로 제공하지 않음.

1.3 활용

  • DB에서 조회한 데이터의 DTO로써 사용하기 적절하다.
  • DTO는 대부분 불변성을 유지해야 하는 경우가 많으며
  • 이러한 불변성은 동기화 없이도 데이터의 유효성을 보장한다.
  • 이러한 불변성을 객체는 아래의 특성이 있어야 하며, 이는 record의 특성과 유사함.
    • 각 변수는 private, final이어야 한다.
    • getter 메소드를 제공해야 한다.
    • 모든 변수를 포함하는 public 생성자가 있어야 한다.
    • equal, hashCode, toString 메소드를 제공해야 한다.
  • Entity의 역할로서 Record를 사용하는것은 권장하지 않는다.
  • 레코드는 엔티티를 대응하기 위해 추가된 기능이 아니다.

2. sealed

  • since java 17(JEP409)
  • 좀 더 정교한 상속이 가능하도록 한다.
  • 허용한(permit) 클래스와 인터페이스만 상속 가능.
  • 코드의 가독성과 유지보수성을 향상시킬 수 있다.

상속의 활용 및 제한

  • 상속을 통한 재사용성 확보 (일반적 목적)
  • Modeling Possibilitis
    • 도메인에 존재하는 여러 가능성을 모델링한다.
    • 예를 들면 우리의 도메인에서는 교통수단이 차, 트럭만을 다룬다
    • 자전거는 다루지 않는다.(도메인 정의 단계에서 도출한다.)
    • 이 경우 Vehicle이라는 추상클래스는 자전거에 상속되어서는 안된다.(상속의 오남용을 막아야 한다)
    • 이러한 경우 모르는 모든 서브 클래스를 감안하고, 검토하는 것(기존 상속)보다, 알려진 서브 클래스(sealed 이용)만을 고려하는 것이 코드의 명확성에 좀 더 집중할 수 있다.

2.2 사용예

public sealed interface Service permits Car, Truck{
  int getMaxServiceIntervalInMonths();
  default int getMaxDistanceBetweenServicesInKilometers() {
    return 100000;
  }
}

public abstract sealed class Vehicle permits Car, Truck{
  protected final String registrationNumber;
  public Vehicle(String registrationNumber){
    this.registrationNumber=registrationNumber;
  }
  public String getRegistrationNumber() {
        return registrationNumber;
    }
}

public final class Truck extends Vehicle implements Service {

    private final int loadCapacity;

    public Truck(int loadCapacity, String registrationNumber) {
        super(registrationNumber);
        this.loadCapacity = loadCapacity;
    }

    public int getLoadCapacity() {
        return loadCapacity;
    }

    @Override
    public int getMaxServiceIntervalInMonths() {
        return 18;
    }

}

public non-sealed class Car extends Vehicle implements Service {

    private final int numberOfSeats;

    public Car(int numberOfSeats, String registrationNumber) {
        super(registrationNumber);
        this.numberOfSeats = numberOfSeats;
    }

    public int getNumberOfSeats() {
        return numberOfSeats;
    }

    @Override
    public int getMaxServiceIntervalInMonths() {
        return 12;
    }

}

/*
//불가능
public non-sealed class Bicycle extends Vehicle implements Service {
}
*/

주의 사항

  • 하위 클래스는 반드시 sealed 클래스와 모듈이 동일해야 한다.
  • 모든 하위 클래스는 반드시 sealed 클래스를 상속(extend)해야 한다.
  • 모든 하위 클래스는 final, sealed 또는 non-sealed로 정의되어야 한다.

2.3 패턴 매칭

// 기존 방식
if (vehicle instanceof Car) {
    return ((Car) vehicle).getNumberOfSeats();
} else if (vehicle instanceof Truck) {
    return ((Truck) vehicle).getLoadCapacity();
} else {
    throw new RuntimeException("Unknown instance of Vehicle");
}

// 패턴 매칭
if (vehicle instanceof Car car) {
    return car.getNumberOfSeats();
} else if (vehicle instanceof Truck truck) {
    return truck.getLoadCapacity();
} else {
    throw new RuntimeException("Unknown instance of Vehicle");
}
  • instanceof를 이용한 객체 사용시 casting 없이 사용가능하다.
  • 여전히 if-else가 필요하다.
  • 추후 버전은 switch문으로 대응 가능할 예정(17버전 preview 상태).

3. ADT(대수적 자료형:Algebraic Data Types)

주의 : ADT에 대한 내용은 아직 정확하지 않음. 정리중

  • 함수형에서 표현되는 데이터 타입
  • 일반적인 ADT는 다른 타입의 조합으로 이루어진 타입을 말하며, 종류에는 아래 두가지가 있다.
  • 곱 타입(Produc type, Record, Tuple)
  • 합 타입(Sum Type, Variants, Tagged Union)

3.1 곱 타입(교집합:Product Type -> record, tuple)

  • 튜플 형태의 데이터
  • 여러 타입이 나타나는 형태 : ("JJRYU", 77, 'a')
  • 레코드 타입일 곱 타입을 표현할 수 있다.

3.2 합 타입(합집합:Sum Type -> variants, taged union)

  • BLOOD_TYPE : A | AB | B | O
  • 타입은 variant의 중 하나를 가질수 있으며, 타입을 종류는 Variants의 합이다.
  • sealed, enum 타입

3.3 곱타입과 합타입을 이용한 데이터 표현

  • 곱타입과 합타입의 조합으로 새로운 타입을 표현할수 있다.
  • 예제
    • 타입 : 곱타입 | 합타입
      • BLOOD_TYPE : (RH+,A)|(RH+,B)|(RH+,AB)|(RH+,O)|(RH-,A)|(RH-,B)|(RH-,AB)|(RH-,O)
    • 타입 : 합타입 | 합타입
      • ABO_TYPE : A | AB | B | O
      • RH_TYPE : RH+ | RH-
      • BLOOD_TYPE : ABO_TYPE | RH_TYPE

3.4 record, sealed를 이용한 ADT

public sealed interface Shape permits Shape.Circle, Shape.Rectangle {
    double area();

    record Circle(double radius) implements Shape {
        @Override
        public double area() {
            return Math.PI * radius * radius;
        }
    }

    record Rectangle(double width, double height) implements Shape {
        @Override
        public double area() {
            return width * height;
        }
    }
}
Shape circle = new Shape.Circle(10);
System.out.println(circle); // Circle[radius=10.0]
System.out.println(circle.area()); // 314.1592653589793

Shape rectangle = new Shape.Rectangle(20, 30);
System.out.println(rectangle); // Rectangle[width=20.0,height=30.0]
System.out.println(rectangle.area()); // 600.0

########## 패턴 매칭 snippet ############
static double area(Shape shape) {
    if (shape instanceof Circle circle) {
        return Math.PI * circle.radius() * circle.radius();
    } else if (shape instanceof Rectangle rectangle) {
        return rectangle.width() * rectangle.height();
    } else {
        throw new IllegalArgumentException("Unknown type");
    }
}
  • Shape은 Circle, Rectangle만 가능한 합 타입
  • Circle, Rectangle은 곱 타입
  • 패턴 매칭시 타입캐스트 없이 사용

결론

  • 함수형 프로그래밍 공부하자.
profile
두걸음 뒤에서.. 그래도 끝까지!!

0개의 댓글