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

192 Views Asked by At

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.

2

There are 2 best solutions below

0
Nikolas Charalambidis On BEST ANSWER

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
    
1
Sash Sinha On

I don't think there is an inbuilt way to do that currently.

When you are creating your own method, you should make sure to check that the class is not final to isolate only the cases where the class is explicitly marked non-sealed:

import java.lang.reflect.Modifier;

public class Main {
  public static boolean isExplicitlyNonSealed(Class<?> clazz) {
    boolean isClassSealed = clazz.isSealed();
    boolean isSuperSealed = clazz.getSuperclass() != null && clazz.getSuperclass().isSealed();
    boolean isAnyInterfaceSealed = false;
    for (Class<?> interfaceClass : clazz.getInterfaces()) {
      if (interfaceClass.isSealed()) {
        isAnyInterfaceSealed = true;
        break;
      }
    }
    return (!isClassSealed && (isSuperSealed || isAnyInterfaceSealed)) && !Modifier.isFinal(clazz.getModifiers());
  }

  public static void main(String[] args) {
    System.out.println("isExplicitlyNonSealed(NonSealedClass.class) = " + isExplicitlyNonSealed(NonSealedClass.class));
    System.out.println("isExplicitlyNonSealed(SealedClass.class) = " + isExplicitlyNonSealed(SealedClass.class));
    System.out.println("isExplicitlyNonSealed(FinalClass.class) = " + isExplicitlyNonSealed(FinalClass.class));
    System.out.println("isExplicitlyNonSealed(OrdinaryClass.class) " + isExplicitlyNonSealed(OrdinaryClass.class));
    System.out.println("isExplicitlyNonSealed(FinalSealedClass.class) = " + isExplicitlyNonSealed(FinalSealedClass.class));
    System.out.println("isExplicitlyNonSealed(SubSealedClass.class) = " + isExplicitlyNonSealed(SubSealedClass.class));
    System.out.println("isExplicitlyNonSealed(SubSubClass.class) = " + isExplicitlyNonSealed(SubSubClass.class));
    System.out.println("isExplicitlyNonSealed(ImplementingClass.class) = " + isExplicitlyNonSealed(ImplementingClass.class));
  }
}

sealed class SealedClass permits NonSealedClass, FinalSealedClass, SubSealedClass {}

non-sealed class NonSealedClass extends SealedClass {}

final class FinalClass {}

class OrdinaryClass {}

final class FinalSealedClass extends SealedClass {}

sealed class SubSealedClass extends SealedClass permits SubSubClass {}

non-sealed class SubSubClass extends SubSealedClass {}

sealed interface SealedInterface permits ImplementingClass {}

non-sealed class ImplementingClass implements SealedInterface {}

Output:

isExplicitlyNonSealed(NonSealedClass.class) = true
isExplicitlyNonSealed(SealedClass.class) = false
isExplicitlyNonSealed(FinalClass.class) = false
isExplicitlyNonSealed(OrdinaryClass.class) false
isExplicitlyNonSealed(FinalSealedClass.class) = false
isExplicitlyNonSealed(SubSealedClass.class) = false
isExplicitlyNonSealed(SubSubClass.class) = true
isExplicitlyNonSealed(ImplementingClass.class) = true