개요
오늘은 Java 14부터 도입된 Record와 Java 15에서 preview로 시작된 Sealed Classes 에 대해서 알아보겠습니다.
참고로 Sealed Classes 는 Java 17부터 정식적으로 확정된 것같습니다.
JDK 17 Release Notes, Important Changes, and Information
These notes describe important changes, enhancements, removed APIs and features, deprecated APIs and features, and other information about JDK 17 and Java SE 17. In some cases, the descriptions provide links to additional detailed information about an issu
www.oracle.com
Record
Record는 불변(immutable) 데이터 객체를 생성하기 위한 새로운 유형의 클래스입니다. 주로 데이터를 운반하는 목적으로 사용되며, 기존의 많은 코드를 줄여줍니다.
Record 적용 전
public class Student {
private final String name;
private final int age;
public Student(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;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Student[name=" + name + ", age=" + age + "]";
}
}
Record 적용 후
public record Student(String name, int age) { }
- 모든 필드가 private final로 선언됩니다
- 자동으로 equals(), hashCode(), toString() 메서드가 생성됩니다
- 생성자와 접근자(getter)가 자동으로 생성됩니다
- 상속이 불가능합니다
public static void main(String[] args) {
Student student = new Student("철수", 20);
System.out.println("student = " + student);
System.out.println("student.name() = " + student.name());
System.out.println("student.age() = " + student.age());
// student = Student[name=철수, age=20]
// student.name() = 철수
// student.age() = 20
}
Sealed Classes
Sealed Classes는 상속을 제한하는 새로운 방법을 제공합니다. 클래스나 인터페이스가 어떤 클래스에 의해 상속(extends)/구현(implements) 될 수 있는지 명시적으로 선언할 수 있습니다.
public sealed class Shape permits Circle, Rectangle, Square {
// 공통 속성 및 메서드
}
public final class Circle extends Shape {
private final double radius;
// Circle 구현
}
public final class Rectangle extends Shape {
private final double width;
private final double height;
// Rectangle 구현
}
public final class Square extends Shape {
private final double side;
// Square 구현
}
permits 한 클래스에서 sealed class를 구현하지 않거나, permits 에 명시되지않은 클래스에서 sealed 클래스를 구현하려고하면 에러가 발생합니다.
public sealed class Shape permits Circle, Rectangle, Square { // 에러!!
// 공통 속성 및 메서드
}
public class Circle {
private final double radius;
// Circle 구현
}
public sealed class Shape permits Circle, Rectangle, Square {
// 공통 속성 및 메서드
}
public final class Triangle extends Shape { // 에러!!
}
- 상속/구현하는 클래스는 final, non-sealed, sealed 중 하나를 선언해야 합니다.
- sealed class와 permit 된 subclass는 한 패키지 내에 존재해야 합니다.
키워드 | 설명 |
final | 더 이상의 상속을 금지 |
sealed | 제한된 상속 |
non-sealed | 상속 허용 |
final
public final class Circle extends Shape {
// Circle 구현
}
sealed
public sealed class Rectangle extends Shape permits ColoredRectangle {
// Rectangle 구현
}
non-sealed
public non-sealed class Square extends Shape {
// Square 구현
}
Sealed Classes 를 써야하는 이유가 있을까?
먼저 Sealed Classes를 도입하면 좋은 점을 알아보겠습니다.
타입 안정성 강화
- 상속 가능한 클래스를 명확히 제한해서 타입 안정성을 높힙니다.
- 컴파일 시점에서 가능한 모든 하위 타입을 알 수 있습니다.
- switch 문법에서 하위 클래스가 모두 존재한다는 것을 컴파일러가 보장합니다. (switch JAVA 21 문법)
도메인 모델링 개선
- 도메인 모델의 제약조건을 코드로 표현할 수 있습니다
- 특정 타입이 가질 수 있는 모든 변형을 명시적으로 정의할 수 있습니다
sealed로 온라인 결제 시스템에서 지원하는 결제 수단을 예시로 만들어보겠습니다.
public sealed interface PaymentMethod permits
CreditCard,
BankTransfer,
DigitalWallet {
boolean process(int amount);
String getPaymentInfo();
}
public final class CreditCard implements PaymentMethod {
private final String cardNumber;
private final String expiryDate;
private final String cvv;
@Override
public boolean process(int amount) {
// 신용카드 결제 처리 로직
return true;
}
@Override
public String getPaymentInfo() {
return "Card: " + cardNumber.substring(12);
}
}
public final class BankTransfer implements PaymentMethod {
private final String accountNumber;
private final String bankCode;
@Override
public boolean process(int amount) {
// 계좌이체 처리 로직
return true;
}
@Override
public String getPaymentInfo() {
return "Bank: " + bankCode + "-" + accountNumber;
}
}
public final class DigitalWallet implements PaymentMethod {
private final String walletId;
@Override
public boolean process(int amount) {
// 디지털 월렛 결제 처리 로직
return true;
}
@Override
public String getPaymentInfo() {
return "Wallet: " + walletId;
}
}
// 결제 처리 서비스
public class PaymentService {
public String processPayment(PaymentMethod method, int amount) {
// JDK 21 switch 문법
return switch (method) {
case CreditCard card ->
"신용카드 결제: " + card.getPaymentInfo();
case BankTransfer transfer ->
"계좌이체: " + transfer.getPaymentInfo();
case DigitalWallet wallet ->
"디지털 월렛: " + wallet.getPaymentInfo();
}; // 모든 결제 수단이 처리됨을 컴파일러가 보장
}
}
- 지원하는 결제 수단을 명확하게 제한할 수 있습니다.
- 새로운 결제 수단 추가 시 컴파일러가 관련된 모든 코드를 체크합니다.
- switch 문에서 모든 케이스를 처리했는지 컴파일 시점에 확인할 수 있습니다.
'Language > JAVA' 카테고리의 다른 글
[Java] Stream - 병렬 스트림 (0) | 2024.11.26 |
---|---|
[JAVA] StringTokenizer 에 대해서 (0) | 2024.11.14 |
[JAVA] 상속(Inheritance)과 복합(Composition) (0) | 2024.11.12 |
[JAVA] String + 연산을 왜 쓰지말아야할까 (0) | 2024.09.24 |
[JAVA] for문과 향상된 for문은 어떤 차이가 있을까? (0) | 2024.09.24 |