JavaRush /Java Blog /Random-JA /コーヒーブレイク#143。Java 17 のシールド クラス。シングルトンを実装する 4 つの方法

コーヒーブレイク#143。Java 17 のシールド クラス。シングルトンを実装する 4 つの方法

Random-JA グループに公開済み

Java 17 のシールされたクラス

出典: Codippa この記事では、Java 17 で導入された新機能であるシールド クラスと、その宣言方法と使用方法を例とともに見ていきます。 コーヒーブレイク#143。 Java 17 のシールド クラス。シングルトンを実装する 4 つの方法 - 1Sealed クラスは、Java 15 でプレビュー機能として最初に登場し、その後 Java 16 でも同じステータスで登場しました。この機能は、Java 17 ( JEP 409 )のリリースで完全に機能するようになりました。

シールされたクラスとは何ですか?

シールされたクラスを使用すると、サブクラスを制限または選択できます。クラスは、親クラスの許可された子クラスのリストに含まれていない限り、プライベート クラスを拡張できません。クラスは、sealedキーワードを使用してシールされます。sealed クラスの後には、それを拡張できるクラスのリストとともに、permitsキーワードを続ける必要があります。以下に例を示します。
public sealed class Device permits Computer, Mobile {
}
この宣言は、Device はComputer クラスMobileクラスによってのみ拡張できることを意味します。他のクラスがそれを拡張しようとすると、コンパイラ エラーがスローされます。sealed クラスを拡張するクラスの宣言には、finalsealed、またはnon-sealedキーワードが必要です。したがって、固定のクラス階層が存在します。これは子クラスの作成とどのように関係するのでしょうか?
  1. Final は、それ以上サブクラス化できないことを意味します。

  2. sealed は、子クラスを許可付きで宣言する必要があることを意味します。

  3. non-sealed は、ここで親子階層を終了することを意味します。

たとえば、コンピュータは、ラップトップ自体が密閉されていない限り、ラップトップおよびデスクトップのクラスを許可します。これは、ラップトップをAppleDellHPなど のクラスによって拡張できることを意味します。

シールド クラスを導入する主な目的は次のとおりです。

  1. これまでは、 finalキーワードを使用してクラスの拡張を制限することしかできませんでした。シールされたクラスは、許可リストに含めることによって、どのクラスを拡張できるかを制御します。

  2. また、どのクラスを子クラスにするかをクラスが制御できるようになります。

ルール

シールされたクラスを使用するときに覚えておくべきルールがいくつかあります。
  1. シールされたクラスは、 permits を使用してそれを拡張できるクラスを定義する必要があります。子クラスが親クラス内で内部クラスとして定義されている場合、これは必要ありません。

  2. 子クラスは、finalsealed、またはnon-sealedのいずれかである必要があります。

  3. 許可された子クラスは、親のシールされたクラスを拡張する必要があります。

    つまり、シールされたクラス A がクラス B を許可する場合、B は A を拡張する必要があります。

  4. シールされたクラスがモジュール内にある場合、子クラスも同じモジュール内にある必要があります。親のシールされたクラスが名前のないモジュール内にある場合は、同じパッケージ内にある必要があります。

  5. シールされたクラスを拡張できるのは、直接許可されたクラスのみです。つまり、A が B の拡張を許可するシール クラスである場合、B は C を許可するシール クラスでもあります。

    この場合、C は B を拡張することしかできませんが、A を直接拡張することはできません。

密閉されたインターフェース

シールされたクラスと同様に、インターフェイスもシールできます。このようなインターフェイスでは、その子インターフェイスまたはクラスの選択が許可される場合があり、許可を使用してそれを拡張できます。良い例を次に示します。
public sealed interface Device permits Electronic, Physical,
DeviceImpl {
}
ここで、Deviceインターフェイスにより、電子インターフェイスと物理インターフェイスがそれを拡張し、後続の実装のためにDeviceImplクラスを拡張できるようになります。

封印された記録

Sealed クラスは、Java 16 で導入されたエントリで使用できます。エントリは通常のクラスを拡張できないため、プライベート インターフェイスのみを実装できます。さらに、この表記は、final を意味します。したがって、エントリはサブクラス化できないため、permitsキーワードを使用できません。つまり、レコードには単一レベルの階層のみが存在します。以下に例を示します。
public sealed interface Device permits Laptop {
}
public record Laptop(String brand) implement Device {
}

リフレクションのサポート

Java Reflection は、シールされたクラスのサポートを提供します。次の 2 つのメソッドがjava.lang.Classに追加されました。

1. getPermittedSubclasses()

これは、このクラス オブジェクトで許可されているすべてのクラスを含む配列java.lang.Classを返します。例:
Device c = new Device();
Class<? extends Device> cz = c.getClass();
Class<?>[] permittedSubclasses = cz.getPermittedSubclasses();
for (Class<?> sc : permittedSubclasses){
  System.out.println(sc.getName());
}
結論:
コンピューター モバイル

2.isSealed()

これは、呼び出されたクラスまたはインターフェイスがシールされている場合にtrueを返します。Java 17 で追加されたシールド クラスについてはここまでです。この記事が参考になれば幸いです。

シングルトンを実装する 4 つの方法

出典: Medium 今日は、シングルトン設計パターンを実装するいくつかの方法を学びます。 シングルトン設計パターンは Java プロジェクトで広く使用されています。ソケットやデータベース接続などのリソースへのアクセス制御を提供します。私はかつて、大手チップ会社の Web 開発者のポジションの面接でシングルトンの実装を求められました。Web 職の面接を受けるのは初めてで、あまり準備もしていなかったので、最も難しい解決策である遅延インスタンス化を選択しました。私のコードは 90% しか正しくなく、効率も不十分で、最終ラウンドで敗退してしまいました…そこで、私の記事があなたのお役に立てば幸いです。

早期インスタンス化

class Singleton {
    private Singleton() {}
    private static Singleton instance = new Singleton();

    public static Singleton getInstance() {
        return instance;
    }
}
オブジェクトは初期化時にすでに作成されているため、スレッドの安全性の問題はありませんが、誰も使用していないとメモリ リソースが無駄になります。

遅延実装

class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
遅延初期化パターンを使用する場合、オブジェクトはオンデマンドで作成されます。ただし、この方法にはスレッドの安全性の問題があります。2 つのスレッドが 5 行目で同時に開始されると、2 つのシングルトン インスタンスが作成されます。これを回避するには、ロックを追加する必要があります。
class Singleton {
    private Singleton() {}
    private static Singleton instance = null;

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized(Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
ダブルチェック ロック (DCL): 6 行目にはロックがないため、オブジェクトがすでに作成されている場合、この行は非常に迅速に機能します。なぜ、 instance == null をダブルチェックする必要があるのでしょうか? おそらく 7 行目で 2 つのスレッドが導入されているためです。最初のスレッドはオブジェクトを開始し、2 番目のスレッドは Singleton.class lockを待機しています。チェックがない場合、2 番目のスレッドは再びシングルトン オブジェクトを再作成します。ただし、この方法はスレッドにとって依然として危険です。9 行目は 3 行のバイトコードに分割できます。
  1. メモリを割り当てます。
  2. 初期化オブジェクト。
  3. オブジェクトをインスタンス参照に割り当てます。
JVM は順序が狂って実行される可能性があるため、仮想マシンは初期化前にオブジェクトをインスタンス参照に割り当てる場合があります。別のスレッドが!= nullインスタンスをすでに認識しているため、それを使用し始めて問題が発生します。したがって、インスタンスに volatile を追加する必要があります。コードは次のようになります。
class Singleton {
    private Singleton() {}
    private volatile static Singleton instance = null;

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized(Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

静的内部クラスの使用

public class Singleton {
  private Singleton() {}
  private static class SingletonHolder {
    private static final Singleton INSTANCE = new Singleton();
  }
  public static final Singleton getInstance() {
    return SingletonHolder.INSTANCE;
  }
}
SingletonHolder は静的な内部クラスであり、 getInstanceメソッドが呼び出された ときにのみ初期化されます。JVM の init クラスが<clinit> cmdを実行すると、JVM 自体が 1 つのスレッドだけがターゲット クラスで <clinit>を呼び出せるようにし、他のスレッドは待機します。

シングルトンとしての列挙型

public enum EnumSingleton {
    INSTANCE;
    int value;
    public int getValue() {
        return value;
    }
    public int setValue(int v) {
        this.value = v;
    }
}
デフォルトでは、enumインスタンスはスレッドセーフであるため、ロックの二重チェックについて心配する必要はなく、作成は非常に簡単です。
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION