※本記事は、”Bruce Eckel on Java interfaces and sealed classes” の翻訳記事です。

2022年4月26日| 5分読む

Bruce Eckel


インタフェースにデフォルト メソッドと静的メソッドが導入されたことにより、公開したくないインターフェイスにメソッドコードを記述可能に

 

[このシリーズでは、Java 8以降にJava言語に追加された新機能について説明します。これは、2021年12月に公開された「On Java 8」の第2版に追加された資料から引用しています。これらの記事のソースコードおよびブック全体については、GitHubから、詳細なインストール手順とともに入手できます。—編集部]

インタフェースにdefaultメソッドと static メソッドが導入されたことにより、Javaでは、publicにはならない可能性のあるinterfaceにメソッド・コードを記述できるようになりました。次のコードでは、Old, fd(), およびfs()はそれぞれdefault メソッドとstaticメソッドです。これらのメソッドはf()およびg()によってのみ呼び出されるため、privateにできます。

// 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() { // 自動的に 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()
*/

(注:  {NewFeature} コメント・タグによって、JDK 8を使用するGradleビルドから、この例は除外されます。)

JDK9は、JDK 9でファイナライズされた機能を使用して、 fd() および fs()private メソッドに変換します。 fd()default キーワードを必要としなくなりました。 private を指定すると自動的にデフォルトになります。

 

シールドクラスおよびインタフェース

列挙は、固定数のインスタンスのみを持つクラスを作成します。JDK 17では、密封されたクラスおよびインタフェースが導入され、ベース・クラスまたはインタフェースによって導出可能なクラスが制限されます。これにより、値の固定セットをモデル化できます。

// interfaces/Sealed.java
// {NewFeature} Since JDK 17

sealed class Base permits D1, D2 {}

final class D1 extends Base {}
final class D2 extends Base {}
// 違法:
// final class D3 extends Base {}

permits句にリストされていないD3などのサブクラスを継承しようとすると、コンパイラはエラーを生成します。前述のコードでは、D1およびD2以外のサブクラスは存在できません。したがって、記述するコードがD1およびD2のみを考慮する必要があることを確認できます。

 

インタフェースおよび抽象クラスをシール(密封)することもできます。

// 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 {}

すべてのサブクラスが同じファイルに定義されている場合、 permits 句は必要ありません。次の場合、コンパイラは outside of SameFile.java以外のShapeを継承しようとしません:

// interfaces/SameFile.java
// {NewFeature} Since JDK 17

sealed class Shape {}
final class Circle extends Shape {}
final class Triangle extends Shape {}

permits 句を使用すると、次のように個別のファイルにサブクラスを定義できます:

// 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 {}

sealed クラスのサブクラスは、次のいずれかによって変更する必要があります:

  • final: これ以上のサブクラスは許可されません。
  • sealed: sealed サブクラスのセットが許可されます。
  • non-sealed: 不明なサブクラスによる継承を可能にする新しいキーワードです。

sealed サブクラスは、階層の厳密な制御を維持します。

// 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 {}

sealed クラスには少なくとも1つのサブクラスが必要です。

sealedベース・クラスは、シールされていないサブクラスを使用できないため、いつでも開くことができます。

// 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は任意の数のサブクラスを許可するため、作成できるタイプの制御を解放しているように見えます。ただし、 sealed クラス Superの直接のサブクラスを厳しく制限します。つまり、Superでは、ダイレクト・サブクラスSub1およびSub2のみが許可されます。

JDK 16 record (このシリーズの前の記事で説明)は、インタフェースの sealed 実装としても使用できます。レコードは暗黙的に finalであるため、 final キーワードの前に指定する必要はありません。

// 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 {}

コンパイラは、密封された階層内から不正な型にダウンキャストすることを防止します。

// interfaces/CheckedDowncast.java
// {NewFeature} Since JDK 17

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: 非互換の型:
    // II は Something に変換できない
  }
}

次のように、 getPermittedSubclasses() コールを使用して、実行時に許可されるサブクラスを検出できます:

// 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
*/

より詳細な情報