Skip to main content

When I was a youngling learning to code Delphi on the hard knock streets of Joburg… one thing was made very clear by the compiler and teachers were that circular dependencies are wrong… just wrong. No code will compile. I appreciate we are about 800 years from that part and tech has moved on, so it can sort of circular dependencies, but to me, it still feels like a bad design decision.

Which brings me to the Java permits keyword (also known as Sealed Interfaces), this feature allows one to create an interface and “assign” classes to it and basically get a Union type like TypeScript has; example code (note this is an empty interface - but doesn’t have to be)

sealed interface Animal permits Dog, Cat {}

Now I can use Animal in my code and do a type check if it is a Dog or Cat and things just work. This also means the shape of Dog and Cat do not need to match each other (again, a Union type)… coolness. Again an example:

void speak(Animal animal) {
  switch (animal) {
    case Dog dog -> dog.bark();
    case Cat cat -> cat.meow();
  }
}

Here is the wild part for me, all the classes “joined” in this interface must also extend that interface… So Dog is an Animal, Cat is an Animal and Animal is both Cat and Dog. Example:

final class Dog extends Animal {
  public String bark() { ... }
}

or

final class Cat extends Animal {
  public String meow() { ... }
}

This means Animal needs to know what Cat & Dog are first, to set itself up, but, each of those classes need to know what Animal is first so that they can use it… it is a circular dependency by design. It seems like a bad - it works… just feels bad. I likely would’ve not had them extend the interface if it was my call. There is a perk to this though, you can force some shared shape in the interface

sealed interface Animal permits Dog, Cat {
  boolean isTailWagging;

and then you end up with both classed needing to implement it, à la

final class Dog extends Animal {
  public String bark() { ... }
  public boolean isTailWagging() {...}
}

or

final class Cat extends Animal {
  public String meow() { ... }
  public boolean isTailWagging() {...}
}

and then we use that without a type check

void speak(Animal animal) {
  if (!animal.isTailWagging()) {
    // animal is unhappy so will make a noise
    switch (animal) {
      case Dog dog -> dog.bark();
      case Cat cat -> cat.meow();
    }
  }
}

Though in my fictional design, one would just have two interfaces - one for the shared shape and one for the union… Anyway, I hope this little tangent into this was interesting, definitely good to see Java getting this sort of functionality (though it did get a while back to be fair), just need to see people using it now!