Java 17: Sealed Classes and Interfaces (JEP 409)

Java 17: Sealed Classes and Interfaces (JEP 409)

Control the inheritance

Introduction

Sealed Classes are an important new feature introduced as a preview feature of JDK 15 and 16 but as a permanent candidate in JDK 17 (LTS). It helps you to have a finer controller over class hierarchy. Inheritance is the main pillar for OOPS but if we see the current framework, it can easily be misused. Any class can extend another unless it's not final. What if you as a developer want to allow few classes to extend it but not all. Sealed classes come to rescue you.

Let us look at sealed classes and their usage.

Do note that if you want to use sealed classes you must have JDK 15 + on the classpath.

Implementation

We will create a class Character and permit only Hero and Villain to extend it. We will also create child clesses of Hero and Villain

Below is the structure we will follow

sealedclassses.png

package sealed;

public sealed class Character permits Hero, Villan {
    public boolean hasPositiveVibes() {
        return true;
    }
}

We have added a method hasPositiveVibes just to demonstrate the usage. We will Override it in Villan class to send negative vibes. :)

Let us create Hero and Villain in the same package. Do note that any class permitted by a sealed class must be in the same package or else it will give the compilation error.

Hero.java

package sealed;

import sealed.Character;
import sealed.JusticeHammer;
import sealed.WhiteClaw;

public sealed class  Hero extends Character permits WhiteClaw, JusticeHammer {
}

Here Hero is again a sealed class and imposing us to tell all those allowed classes. Any class extending from a sealed class must be defined sealed, non-sealed or final.

  • sealed : Allows limited set classes to extend itself
  • non-sealed: Allows any other class to extend
  • final: Allows none

We can now have Whileclaw and JusticeHammer as a final class.

package sealed;

public final class WhiteClaw extends Hero {
}
package sealed;

public final class JusticeHammer extends Hero {
}

Now let us see an example of a non-sealed class. As we know that there are very few real heroes in the world fighting for a cause to so many bad guys. We can't allow everyone to be believed as Hero so we kept a tight control at Hero class and giving permission to only a few. There are many Villains who can break the law, so we have kept Villan as non-sealed.

Do note that we need not define the permit clause with `non-sealed classes. The classes extending to non-sealed can be in different packages as well

We also have overridden the hasPositiveVibes method of Character to send negative vibes from Villain(s)

Villain.java

package sealed;

public non-sealed class Villain extends Character  {
    @Override
    public boolean hasPositiveVibes(){
        return false;

    }
}
public class Outlaw extends Villain {
}
public class MrKiller extends Villain {
}

non-sealed classes can be extended from packages outside of the parent

Villains don't follow any order, they want to expand beyond earth. Mars is their first target. MrKiller has a son who now lives on Mars, he is MartianKiller. Let us create a package outside the sealed World.

package mars;

import sealed.MrKiller;

public class MartianKiller extends MrKiller {
}
  • This also shows non-sealed classes can be extended from packages outside of the parent.

The permit clause is optional if subclasses are in the same file.

Wait, there is something wrong, World is not only about superheroes and Villains but also about (Humans) Man, Women, and a few who don't want to reveal their identity in this chaotic order.

Let us add Human as Character. Now our Character looks like

public sealed class Character permits Hero, Villain,Humans {
    public boolean hasPositiveVibes() {
        return true;
    }

}

Also, let us look at Humans

package sealed;

/*  
    permits' clause has been removed as its permitted 
    classes have been defined in the same file.
*/
public sealed class Humans extends Character {
}

final class Man extends Humans {

}

final class Woman extends Humans {

}
  • As you can see, the permit clause lass in Human has been omitted. The permit clause is optional if subclasses are in the same file.

Now MartianKiller and Outlaw are Villains. At this point, we can test our code. Below Main class will create instances of Hero and Villian and test if they send positive or negative vibes.

package sealed;

public class Main {
    public static void main(String[] args) {
        Character killer= new MartianKiller();
        System.out.println(killer.hasPositiveVibes());

        Character claw= new WhiteClaw();

        System.out.println(claw.hasPositiveVibes());
    }
}

The output will be false and true.

TL;DR

  • All sealed classes must tell whom they permit
  • All classes extending to sealed classes has to be final/sealed/non-sealed
  • Any class extending non-sealed doesn't have to be explicitly sealed, non-sealed, or final
  • All sealed classes and their child must be in the same package
  • non-sealed classes can be extended from anywhere and have no location constraints
  • The permit clause is optional if subclasses are in the same file.
  • Do note that records can't be a sealed class but can extend one.

I hope this article helped you to understand sealed classes. Sealed interfaces are following the same principles as the sealed classes. If you liked the article, do let me know using a comment or you can share it with your friends.

Did you find this article valuable?

Support Kumar Pallav - The Coding Saint by becoming a sponsor. Any amount is appreciated!