- 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 메소드 자동 생성
record 레코드명(컴포넌트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
- DB에서 조회한 데이터의 DTO로써 사용하기 적절하다.
- DTO는 대부분 불변성을 유지해야 하는 경우가 많으며
- 이러한 불변성은 동기화 없이도 데이터의 유효성을 보장한다.
- 이러한 불변성을 객체는 아래의 특성이 있어야 하며, 이는 record의 특성과 유사함.
- 각 변수는 private, final이어야 한다.
- getter 메소드를 제공해야 한다.
- 모든 변수를 포함하는 public 생성자가 있어야 한다.
- equal, hashCode, toString 메소드를 제공해야 한다.
- Entity의 역할로서 Record를 사용하는것은 권장하지 않는다.
- 레코드는 엔티티를 대응하기 위해 추가된 기능이 아니다.
- since java 17(JEP409)
- 좀 더 정교한 상속이 가능하도록 한다.
- 허용한(permit) 클래스와 인터페이스만 상속 가능.
- 코드의 가독성과 유지보수성을 향상시킬 수 있다.
상속의 활용 및 제한
- 상속을 통한 재사용성 확보 (일반적 목적)
- Modeling Possibilitis
- 도메인에 존재하는 여러 가능성을 모델링한다.
- 예를 들면 우리의 도메인에서는 교통수단이 차, 트럭만을 다룬다
- 자전거는 다루지 않는다.(도메인 정의 단계에서 도출한다.)
- 이 경우 Vehicle이라는 추상클래스는 자전거에 상속되어서는 안된다.(상속의 오남용을 막아야 한다)
- 이러한 경우 모르는 모든 서브 클래스를 감안하고, 검토하는 것(기존 상속)보다, 알려진 서브 클래스(sealed 이용)만을 고려하는 것이 코드의 명확성에 좀 더 집중할 수 있다.
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 {
}
*/
// 기존 방식
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");
}
주의 : ADT에 대한 내용은 아직 정확하지 않음. 정리중
- 함수형에서 표현되는 데이터 타입
- 일반적인 ADT는 다른 타입의 조합으로 이루어진 타입을 말하며, 종류에는 아래 두가지가 있다.
- 곱 타입(Produc type, Record, Tuple)
- 합 타입(Sum Type, Variants, Tagged Union)
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");
}
}
합 타입
곱 타입