Java 17의 등장
Java 17 버전은 2021년 9월에 공개된 LTS(Long Term Supports) 버전으로 Java 11 LTS 등장이후 3에 등장한 LTS 버전이다.
이번 Java 17버전의 다양한 변화사항중 눈에 띄는 점은 새로운 객체지향 패러다임을 제시하는 문법이 추가되었다는 점인데, 각각 Record, Seald Class이다. 이번 글에서 각각의 문법에 대해 자세히 알아보려 한다.
Record
Record 클래스는 데이터를 보유하는 데 특화된 간결한 클래스 선언 방식이다.
전통적인 Java 클래스에서는 반복적으로 작성해야하는 보일러 플레이트(Boiler Plate)코드들이 다수 존재하는데, Record 클래스를 통해 해당 코드들을 자동으로 생산하도록 하여 개발자의 생산성을 크게 향상시킬 수 있도록 유도하고 있다.
실제 사용을 해보면 Kotlin의 data 키워드와 거의 비슷하다는걸 느낄 수 있다.
Record 클래스의 특징 및 장점
- 간결한 선언
핵심적인 데이터 필드만 선언하면 생성자, 접근자(Getter), equals(), hashCode(), toString() 메서드가 자동으로 생성된다. - 불변성(Immutabillity)
Record 클래스의 필드는 기본적으로 final로 선언되며, 외부에서 값을 변경할 수 있는 Setter 메서드를 제공하지 않는다.
Setter 메서드를 제공하지 않고, 불변객체로 만드는 것은 스레드 안전성을 높이고 예측 가능한 코드를 작성하는데 도움을 준다. - 명확한 의도
Record 클래스는 그 자체로 "데이터를 담는 객체"라는 의도를 명확하게 드러낸다. 이는 코드의 가독성을 높이고 유지보수를 용이하게 한다.
Record 클래스의 기본 문법과 예시
record Point(int x, int y) { }
위 코드는 x와 y라는 두 개의 int 필드를 갖는 Point라는 이름의 Record 클래스를 선언하고 있다.
컴파일러는 이 선언을 기반으로 자동으로 생성자, Getter, equals(), hashCode(), toString() 메서드를 생성한다.
Record 클래스 사용 예시
public class Main {
public static void main(String[] args) {
Point p1 = new Point(10, 20);
Point p2 = new Point(10, 20);
System.out.println(p1); // 출력: Point[x=10, y=20]
System.out.println(p1.x()); // 출력: 10
System.out.println(p1.y()); // 출력: 20
System.out.println(p1.equals(p2)); // 출력: true (내용 기반 비교)
System.out.println(p1.hashCode() == p2.hashCode()); // 출력: true
}
}
Record 클래스의 간결한 생성자
Record 클래스는 기본적으로 모든 필드를 파라미터로 받는 생성자가 자동으로 생성된다.
이는 컴파일 시점에 생성되기때문에 별도로 정의할 필요가 없으나, 커스텀 생성자를 만들고자할 때 해당 생성자는 매개변수 리스트를 생략하고, 필드 검증 로직을 간결하게 작성할 수 있다.
public record Person(String name, int age) {
public Person {
if (age < 0) {
throw new IllegalArgumentException("나이는 0 이상이어야 합니다.");
}
}
}
Static 선언
Record 클래스 내부에서도 static 필드, static 메서드를 정의하여 사용할 수 있다.
record Circle(double radius) {
private static final double PI = Math.PI;
public static double calculateArea(double radius) {
return PI * radius * radius;
}
public double circumference() {
return 2 * PI * radius;
}
}
인터페이스 구현
Record 클래스는 하나 이상의 인터페이스를 구현할 수 있다.
record Employee(String name, int id) implements Comparable<Employee> {
@Override
public int compareTo(Employee other) {
return this.id - other.id;
}
}
Record 클래스의 제약 사항
- Record 클래스는 java.lang.Record 클래스를 상속하므로 다른 클래스를 상속 할 수 없다.
- Record 클래스의 멤버 필드 구성은 매개변수로 선언된 필드에 의해 결정된다. static 필드를 제외한 별도의 멤버 필드를 선언할 수 없다.
Sealed 클래스
Seald 클래스는 클래스나 인터페이스의 상속 또는 구현을 명시적으로 허영된 클래스 또는 인터페이스로 제한하는 기능이다.
이를 통해 개발자는 클래스의 계층 구조를 더욱 엄격하게 제어하고, 컴파일 시점에 더욱 안전하고 효율적인 코드를 생성할 수 있다.
Sealed 클래스의 특징 및 장점
- 상속 제어
seald 키워드를 사용하여 클래스 또는 인터페이스를 선언하면, 해당 클래스 또는 인터페이스를 상속하거나 구현할 수 있는 클래스 또는 인터페이스를 permits 절을 사용하여 명시적으로 지정해야 한다. - 컴파일러 지원
Seald 클래스를 사용하면 컴파일러는 가능한 모든 하위 타입에 대해 알 수 있다. 예를 들어 permits 절로 추가되지 않은 클래스가 Seald 클래스를 구현하거나 상속하려하면 컴파일 시점에 확인후 에러를 발생시킬 수 있다. 즉 더욱 강력한 타입 검사를 가능하게 한다. - API 설계 명확성
클래스 계층 구조를 명확하게 정의하여 API의 사용 방법을 더 잘 이해할 수 있도록 돕는다. - 코드 안전성 향상
허용된 하위 타입만 존재함을 보장함으로써 예상치 못한 상속으로 인한 문제를 방지하고 코드의 안정성을 높인다.
Sealed 클래스의 기본 문법과 예시
sealed class Shape permits Circle, Rectangle, Triangle { }
위 코드는 Shape 라는 Sealed 클래스를 선언하고, Circle, Rectangle, Triangle 클래스만 Shape를 상속할 수 있도록 허용한다.
permits 절에 명시되지 않은 다른 클래스는 Shape를 상속할 수 없다.
Sealed 클래스 사용 예시
// Shape 클래스와 동일한 컴파일 단위 또는 모듈에 정의되어야 함
final class Circle extends Shape { double radius; }
sealed class Rectangle extends Shape permits Square { double width, height; }
final class Square extends Rectangle { double side; }
final class Triangle extends Shape { double base, height; }
public class Main {
public static double getArea(Shape shape) {
return switch (shape) {
case Circle c -> Math.PI * c.radius * c.radius;
case Rectangle r -> r.width * r.height;
case Square s -> s.side * s.side;
case Triangle t -> 0.5 * t.base * t.height;
};
}
public static void main(String[] args) {
Shape circle = new Circle();
Shape rectangle = new Rectangle();
// Shape unknownShape = new UnknownShape(); // 컴파일 에러 발생 (Shape를 상속할 수 없음)
// ...
}
}
Sealed 인터페이스
sealed interface Operation permits Add, Subtract, Multiply, Divide {}
final class Add implements Operation {}
final class Subtract implements Operation {}
final class Multiply implements Operation {}
final class Divide implements Operation {}
인터페이스도 sealed 키워드를 사용하여 봉인할 수 있다. 이 경우 permits 절을 사용하여 해당 인터페이스를 구현할 수 있는 인터페이스 또는 클래스를 명시적으로 지정해야한다.
Record 클래스와 Sealed 클래스의 조합
Record 클래스는 불변 데이터를 표현하는 데 적합하며, Sealed 클래스는 상속 가능한 타입을 제한하는 데 사용된다.
이 두 기능을 함께 사용하면 더욱 강력하고 안전한 객체 모델링이 가능하다. 예를 들어 Sealed 클래스를 사용하여 다양한 종류의 에러 타입을 정의하고, 각 에러 타입은 Record 클래스를 사용하여 구체적인 에러 정보를 담을 수 있다.
sealed class Result<T> permits Success, Failure {}
record Success<T>(T data) extends Result<T> {}
record Failure<T>(String errorMessage) extends Result<T> {}
public class Main {
public static Result<Integer> divide(int a, int b) {
if (b == 0) {
return new Failure<>("Cannot divide by zero");
}
return new Success<>(a / b);
}
public static void main(String[] args) {
Result<Integer> result1 = divide(10, 2);
Result<Integer> result2 = divide(5, 0);
switch (result1) {
case Success<Integer> s -> System.out.println("Result: " + s.data());
case Failure<Integer> f -> System.out.println("Error: " + f.errorMessage());
}
switch (result2) {
case Success<Integer> s -> System.out.println("Result: " + s.data());
case Failure<Integer> f -> System.out.println("Error: " + f.errorMessage());
}
}
}
'Java' 카테고리의 다른 글
[Java] 가비지 컬렉션(GC) 종류별 비교 및 선택 방법 (0) | 2025.04.11 |
---|---|
[Java] Java 원격 디버깅(Java Remote Debugging) (0) | 2025.01.08 |
[Java] JVM의 한계와 GraalVM 살펴보기 (1) | 2024.12.16 |
[Java] JVM Warm up - if(kakao)2022 (1) | 2024.11.11 |
[Java] 회원 시스템 분산 트랜잭션(JTA) 처리와 성능 이슈 해결 (1) | 2024.10.31 |