Search code examples
javajava-17sealed

How to check if a class is explicitly non-sealed?


Is it possible to check if a class is explicitly marked as non-sealed like with MyClass.class.isSealed() and without checking its parent hierarchy? (And if so, how?)

Reason - I want to know if the info is somewhere in the class itself and not guess by checking if its final and looking up the parents. Maybe the language changes one day and this indirect condition may too.


Solution

  • There is, unfortunately, no dedicated method to find out whether a given class is explicitly defined as non-sealed and negating the result of isSealed method does not give you an accurate answer.

    If you know the rules of defining a class as non-sealed, you can construct the method that would do it for you.

    • The examined class/interface must not be final.
    • The examined class/interface must not be sealed
    • If the examined class is class, its direct superclass must be sealed.
    • If the examined class is interface, one of the direct superclasses must be sealed.

    You don't need to check the whole parent hierarchy but due to the definition of non-sealed, you have to inspect its direct supertype (or supertypes in case of interface).

    private static boolean isNonSealed(Class<?> clazz) {
        final boolean extendsSealedInterface = clazz.isInterface() && Arrays.stream(clazz.getInterfaces()).anyMatch(Class::isSealed);
        final boolean extendsSealedClass = clazz.getSuperclass() != null && clazz.getSuperclass().isSealed();
    
        return !clazz.isSealed()
            && (extendsSealedInterface || extendsSealedClass)
            && !Modifier.isFinal(clazz.getModifiers());
    }
    

    In a small test, only Reptile and Cat are and should be found non-sealed:

    List<Class<?>> classes = List.of(
        Animal.class, Mammal.class, Reptile.class, 
        Dog.class, Cat.class, SiameseCat.class);
    
    classes.forEach(clazz -> {
        System.out.printf("%10s : isSealed = %5s, parent.isSealed=%5s, isFinal=%5s -> isNonSealed=%s%n",
            clazz.getSimpleName(),
            clazz.isSealed(),
            clazz.getSuperclass().isSealed(),
            Modifier.isFinal(clazz.getModifiers()),
            isNonSealed(clazz));
    });
    
    • Classes:

      // Let's omit 'permits' as all the subclasses are in the same file.
      static sealed class Animal {}
      static sealed class Mammal extends Animal {}
      static non-sealed class Reptile extends Animal {}
      static final class Dog extends Mammal {}
      static non-sealed class Cat extends Mammal {}
      static class SiameseCat extends Cat {}
      

      Output, as you see, only Reptile and Dog are non-sealed:

          Animal : isInterface=false, isSealed =  true, isFinal=false, parentClass.isSealed=false, parentInterfaces.anyIsSealed=false -> isNonSealed=false
          Mammal : isInterface=false, isSealed =  true, isFinal=false, parentClass.isSealed= true, parentInterfaces.anyIsSealed=false -> isNonSealed=false
         Reptile : isInterface=false, isSealed = false, isFinal=false, parentClass.isSealed= true, parentInterfaces.anyIsSealed=false -> isNonSealed=true
             Dog : isInterface=false, isSealed = false, isFinal= true, parentClass.isSealed= true, parentInterfaces.anyIsSealed=false -> isNonSealed=false
             Cat : isInterface=false, isSealed = false, isFinal=false, parentClass.isSealed= true, parentInterfaces.anyIsSealed=false -> isNonSealed=true
      SiameseCat : isInterface=false, isSealed = false, isFinal=false, parentClass.isSealed=false, parentInterfaces.anyIsSealed=false -> isNonSealed=false
      
    • Interfaces:

      sealed interface Animal {}
      sealed interface Mammal extends Animal {}
      non-sealed interface Reptile extends Animal {}
      static final class Dog implements Mammal {}     // doggo remains a class
      non-sealed interface Cat extends Mammal {}
      interface SiameseCat extends Cat {}
      

      Output:

          Animal : isInterface= true, isSealed =  true, isFinal=false, parentClass.isSealed=false, parentInterfaces.anyIsSealed=false -> isNonSealed=false
          Mammal : isInterface= true, isSealed =  true, isFinal=false, parentClass.isSealed=false, parentInterfaces.anyIsSealed= true -> isNonSealed=false
         Reptile : isInterface= true, isSealed = false, isFinal=false, parentClass.isSealed=false, parentInterfaces.anyIsSealed= true -> isNonSealed=true
             Dog : isInterface=false, isSealed = false, isFinal= true, parentClass.isSealed=false, parentInterfaces.anyIsSealed= true -> isNonSealed=false
             Cat : isInterface= true, isSealed = false, isFinal=false, parentClass.isSealed=false, parentInterfaces.anyIsSealed= true -> isNonSealed=true
      SiameseCat : isInterface= true, isSealed = false, isFinal=false, parentClass.isSealed=false, parentInterfaces.anyIsSealed=false -> isNonSealed=false