본문 바로가기

기초 지식/Java

[Java] Sealed Class

Java 17의 release note를 보다 보면 Sealed Class가 정식으로 확정된 것으로 보입니다. JDK 15부터 프리뷰로 추가되었던 것 같은데 한 번도 써본 적이 없네요. ㅎㅎ 오늘 알아보도록 하겠습니다.

 

Sealed Class

Sealed Class, Interface는 간단하게 상속하거나(extends), 구현(implements)할 클래스를 지정해두고 해당 클래스들만 상속 혹은 구현을 허용하는 키워드입니다.

 

public sealed interface CarBrand permits Hyundai, Kia{}

public final class Hyundai implements CarBrand {}
public non-sealed class Kia implements CarBrand {}

 

위처럼 선언하고 사용할 수 있습니다. 예시 코드에서 Hyundai, Kia를 제외한 다른 클래스가 구현을 하려고 하면 에러를 발생시킵니다.

 

permits가 아닌 클래스는 에러 발생

위의 예시 코드에서 특이한 점은 상속/구현하는 클래스들이 final 아니면 non-sealed로 선언되어 있다는 점입니다.

Sealed Class는 몇 가지 제약 사항을 두고 있습니다.

 

  • 상속/구현하는 클래스는 final, non-sealed, sealed 중 하나로 선언되어야 한다.
  • Permitted Subclass들은 동일한 module에 속해야 하며 이름이 지정되지 않은 module에 선언 시에는 동일한 package 내에 속해야 한다.
  • Permitted Subclass는 Sealed Class를 직접 확장해야 한다.

이러한 제약들의 공통적인 목표는 개발자가 코드를 작성하면서 어떠한 Super Class의 Sub Class들을 명확히 인지할 수 있어야 한다는 것을 목표로 합니다.

 

String getBrandName(CarBrand brand) {
    if (brand instanceof Hyundai) {
    	return "Hyundai";
    }
    
    if (brand instanceof Kia) {
    	return "Kia";
    }
    
    // 예측할 수 없는 결과 발생
    return null;
}

 

이 예시 코드에서 보듯이 만약 CarBrand 인터페이스가 Sealed Interface가 아니라면 Subclass들을 일일이 체크해서 그에 따라 결괏값이 나뉘는 경우가 있습니다. 이때 if 분기 처리에서 처리하지 못한 어떠한 CarBrand의 Subclass에 대한 값의 처리를 해주어야 합니다.

 

하지만 만약 CarBrand가 Sealed Interface라면 아래와 같은 코드로 변경할 수 있습니다.

 

String getBrandName(CarBrand brand) {
    if (brand instanceof Hyundai) {
    	return "Hyundai";
    }
    
    return "Kia";
}

 

어설픈 코드지만 한 가지 확실한건 CarBrand 객체가 들어온다면 해당 객체가 Hyundai 아니면 Kia 중에 하나라는건 확실히 할 수 있습니다.

 

Non-Sealed Class

위의 예제에서 한가지 의문점이 생깁니다. Non-Sealed Class로 선언하면 Sealed Class를 상속/구현할 수 있다는 점입니다.

 

non-sealed Class로 선언하고 이를 상속하는 클래스는 전혀 에러를 발생시키지 않습니다.

 

public non-sealed class Kia implements CarBrand {}

public class K8 extends Kia {}

// 가능
CarBrand brand = new K8();

 

이렇게 되면 기존의 목표를 이루지 못하는 것 아닐까요? 제가 생각하기에는 개발자가 인지하지도 못할 무분별한 확장을 막은 것이지 제한적이고 인식범위 내에 있는 확장은 막지 않은 것 같다고 생각이 들었습니다.

 

반응형