Thursday, April 28, 2022

Bruce Eckel on Java interfaces and sealed classes

With the introduction of default and static methods in interfaces, Java lets you write method code in an interface that you might not want to be public.

With the introduction of default and static methods in interfaces, Java made it possible to write method code in an interface that you might not want to be public. In the code below, Old, fd(), and fs() are default and static methods, respectively. These methods are called only by f() and g(), so you can make them private.

Java Interfaces Classes, Oracle Java Sealed Classes, Oracle Java Exam Prep, Oracle Java Career, Java Skills, Oracle Java Preparation

// interfaces/PrivateInterfaceMethods.java

// {NewFeature} Since JDK 9

interface Old {

  default void fd() {

    System.out.println("Old::fd()");

  }

  static void fs() {

    System.out.println("Old::fs()");

  }

  default void f() {

    fd();

  }

  static void g() {

    fs();

  }

}

class ImplOld implements Old {}

interface JDK9 {

  private void fd() { // Automatically default

    System.out.println("JDK9::fd()");

  }

  private static void fs() {

    System.out.println("JDK9::fs()");

  }

  default void f() {

    fd();

  }

  static void g() {

    fs();

  }

}

class ImplJDK9 implements JDK9 {}

public class PrivateInterfaceMethods {

  public static void main(String[] args) {

    new ImplOld().f();

    Old.g();

    new ImplJDK9().f();

    JDK9.g();

  }

}

/* Output:

Old::fd()

Old::fs()

JDK9::fd()

JDK9::fs()

*/

(Note: The {NewFeature} comment tag excludes this example from the Gradle build that uses JDK 8.)

JDK9 turns fd() and fs() into private methods using the feature finalized in JDK 9. Note that fd() no longer needs the default keyword, as making it private automatically makes it default.

Sealed classes and interfaces

An enumeration creates a class that has only a fixed number of instances. JDK 17 finalizes the introduction of sealed classes and interfaces, so the base class or interface can constrain what classes can be derived from it. This allows you to model a fixed set of kinds of values.

// interfaces/Sealed.java

// {NewFeature} Since JDK 17

sealed class Base permits D1, D2 {}

final class D1 extends Base {}

final class D2 extends Base {}

// Illegal:

// final class D3 extends Base {}

The compiler produces an error if you try to inherit a subclass such as D3 that is not listed in the permits clause. In the code above, there can be no subclasses other than D1 and D2. Thus, you can ensure that any code you write will only ever need to consider D1 and D2.

You can also seal interfaces and abstract classes.

// interfaces/SealedInterface.java

// {NewFeature} Since JDK 17

sealed interface Ifc permits Imp1, Imp2 {}

final class Imp1 implements Ifc {}

final class Imp2 implements Ifc {}

sealed abstract class AC permits X {}

final class X extends AC {}

If all subclasses are defined in the same file, you don’t need the permits clause. In the following, the compiler will prevent any attempt to inherit a Shape outside of SameFile.java:

// interfaces/SameFile.java

// {NewFeature} Since JDK 17

sealed class Shape {}

final class Circle extends Shape {}

final class Triangle extends Shape {}

The permits clause allows you to define the subclasses in separate files, as follows:

// interfaces/SealedPets.java

// {NewFeature} Since JDK 17

sealed class Pet permits Dog, Cat {}

// interfaces/SealedDog.java

// {NewFeature} Since JDK 17

final class Dog extends Pet {}

// interfaces/SealedCat.java

// {NewFeature} Since JDK 17

final class Cat extends Pet {}

Subclasses of a sealed class must be modified by one of the following:

◉ final: No further subclasses are allowed.

◉ sealed: A sealed set of subclasses is allowed.

◉ non-sealed: This is a new keyword that allows inheritance by unknown subclasses.

The sealed subclasses maintain strict control of the hierarchy.

// interfaces/SealedSubclasses.java

// {NewFeature} Since JDK 17

sealed class Bottom permits Level1 {}

sealed class Level1 extends Bottom permits Level2 {}

sealed class Level2 extends Level1 permits Level3 {}

final class Level3 extends Level2 {}

Note that a sealed class must have at least one subclass.

A sealed base class cannot prevent the use of a non-sealed subclass, so you can always open things back up.

// interfaces/NonSealed.java

// {NewFeature} Since JDK 17

sealed class Super permits Sub1, Sub2 {}

final class Sub1 extends Super {}

non-sealed class Sub2 extends Super {}

class Any1 extends Sub2 {}

class Any2 extends Sub2 {}

Sub2 allows any number of subclasses, so it seems like it releases control of the types you can create. However, you strictly limit the immediate subclasses of the sealed class Super. That is, Super still allows only the direct subclasses Sub1 and Sub2.

A JDK 16 record (described in a previous article in this series) can also be used as a sealed implementation of an interface. Because a record is implicitly final, it does not need to be preceded by the final keyword.

// interfaces/SealedRecords.java

// {NewFeature} Since JDK 17

sealed interface Employee

  permits CLevel, Programmer {}

record CLevel(String type)

  implements Employee {}

record Programmer(String experience)

  implements Employee {}

The compiler prevents you from downcasting to illegal types from within a sealed hierarchy.

// interfaces/CheckedDowncast.java

// {NewFeature} Since JDK 1

sealed interface II permits JJ {}

final class JJ implements II {}

class Something {}

public class CheckedDowncast {

  public void f() {

    II i = new JJ();

    JJ j = (JJ)i;

    // Something s = (Something)i;

    // error: incompatible types: II cannot

    // be converted to Something

  }

}

You can discover the permitted subclasses at runtime using the getPermittedSubclasses() call, as follows:

// interfaces/PermittedSubclasses.java

// {NewFeature} Since JDK 17

sealed class Color permits Red, Green, Blue {}

final class Red extends Color {}

final class Green extends Color {}

final class Blue extends Color {}

public class PermittedSubclasses {

  public static void main(String[] args) {

    for(var p: Color.class.getPermittedSubclasses())

      System.out.println(p.getSimpleName());

  }

}

/* Output:

Red

Green

Blue

*/

Source: oracle.com

Related Posts

0 comments:

Post a Comment