web-dev-qa-db-ja.com

Javaで不変クラスfinalを宣言するのはなぜですか?

Javaで class immutable を作成するには、次のようにする必要があります。

  1. セッターを提供しないでください
  2. すべてのフィールドをプライベートとしてマークする
  3. クラスを最終決定する

手順3が必要な理由クラスfinalをマークする必要があるのはなぜですか?

72
Anand

クラスfinalをマークしないと、一見不変のクラスを突然変更可能にする可能性があります。たとえば、次のコードを検討してください。

public class Immutable {
     private final int value;

     public Immutable(int value) {
         this.value = value;
     }

     public int getValue() {
         return value;
     }
}

ここで、次のことをするとします。

public class Mutable extends Immutable {
     private int realValue;

     public Mutable(int value) {
         super(value);

         realValue = value;
     }

     public int getValue() {
         return realValue;
     }
     public void setValue(int newValue) {
         realValue = newValue;
     }

    public static void main(String[] arg){
        Mutable obj = new Mutable(4);
        Immutable immObj = (Immutable)obj;              
        System.out.println(immObj.getValue());
        obj.setValue(8);
        System.out.println(immObj.getValue());
    }
}

Mutableサブクラスでは、getValueの動作をオーバーライドして、サブクラスで宣言された新しい可変フィールドを読み取ります。その結果、最初は不変に見えるクラスは実際には不変ではありません。このMutableオブジェクトをImmutableオブジェクトが予想される場所に渡すことができます。これにより、オブジェクトが本当に不変であると仮定すると、コードに非常に悪いことが起こります。基本クラスfinalをマークすると、これが発生しなくなります。

お役に立てれば!

122
templatetypedef

多くの人が信じていることに反して、不変クラスfinalを作成することはnotが必要です。

不変クラスを作成するための標準引数finalは、これを行わないと、サブクラスが可変性を追加できるため、スーパークラスの規約に違反する可能性があるということです。クラスのクライアントは不変性を想定しますが、その下から何かが変化すると驚くでしょう。

この引数を論理的な極端にすると、allメソッドをfinalにする必要があります。そうしないと、サブクラスがスーパークラスのコントラクトに準拠しない方法。興味深いのは、ほとんどのJavaプログラマーがこれをばかげていると見ていることですが、不変クラスはfinalである必要があるという考えには何とかして大丈夫です。一般に、Javaプログラマーと関係があるのではないかと思われます。不変性の概念に完全に満足しておらず、おそらくfinalキーワードの複数の意味に関連するある種のあいまいな考えJava。

スーパークラスのコントラクトに準拠することは、コンパイラーによって強制できる、または常に実行する必要があるものではありません。コンパイラーは、コントラクトの特定の側面を強制できます(例:メソッドの最小セットとその型シグネチャ)しかし、コンパイラーによって実施できない典型的な契約の多くの部分があります。

不変性はクラスのコントラクトの一部です。これは、クラス(およびすべてのサブクラス)ができないことを実行することを示しているため、人々が慣れているものとは少し異なります。ほとんどのJava(および一般的にOOP)プログラマーは、契約を、クラスcanが何をするかではなく、何をするかに関するものと考える傾向があると思いますitできませんできません。

不変性は、単一のメソッドだけでなく、インスタンス全体にも影響を及ぼしますが、これはJavaのequalsおよびhashCodeの動作と実際にはそれほど変わりません。これらの2つのメソッドには、Objectで特定のコントラクトがレイアウトされています。このコントラクトは、これらのメソッドcannotが行うことを非常に注意深くレイアウトします。このコントラクトは、サブクラスでより具体的になります。 equalsまたはhashCodeを契約に違反する方法でオーバーライドするのは非常に簡単です。実際、これら2つのメソッドのうち1つだけを他のメソッドなしでオーバーライドした場合、契約に違反している可能性があります。これを避けるために、equalshashCodefinalObjectと宣言する必要がありますか?ほとんどの人はそうすべきではないと主張するだろう。同様に、不変クラスfinalを作成する必要はありません。

ただし、不変であろうとなかろうと、ほとんどのクラスは、おそらくshouldfinalでなければなりません。 Effective Java Second EditionItem 17:「継承のための設計と文書化、または禁止する」を参照してください。

したがって、ステップ3の正しいバージョンは次のようになります。「クラスをファイナルにするか、サブクラス化の設計時に、すべてのサブクラスが不変であり続ける必要があることを明確に文書化します。」

37

クラス全体を最終的にマークしないでください。

他の回答のいくつかで述べられているように、不変クラスを拡張できる正当な理由があるため、クラスをfinalとしてマークすることは常に良い考えではありません。

プロパティを非公開および最終としてマークし、「契約」を保護する場合は、ゲッターを最終としてマークすることをお勧めします。

このようにして、クラスの拡張を許可することができます(はい、おそらく可変クラスによっても可能です)が、クラスの不変の側面は保護されます。プロパティはプライベートであり、アクセスできません。これらのプロパティのゲッターは最終的なものであり、オーバーライドできません。

不変クラスのインスタンスを使用する他のコードは、それが渡されるサブクラスが他のアスペクトで可変であっても、クラスの不変アスペクトに依存できます。もちろん、クラスのインスタンスを取得するため、これらの他の側面についても知りません。

20
Justin Ohms

最終的なものではない場合、セッターの提供、プライベート変数のシャドウイング、基本的な変更可能化など、誰でもクラスを拡張して好きなことを実行できます。

5
digitaljoel

それは、クラスを拡張する他のクラスを制限します。

finalクラスは他のクラスによって拡張できません。

クラスが不変にしたいクラスを拡張する場合、継承の原則により、クラスの状態が変わる可能性があります。

「変更される可能性がある」ことを明確にしてください。サブクラスは、メソッドのオーバーライド(templatetypedef/Ted Hop answerなど)を使用するなど、スーパークラスの動作をオーバーライドできます。

4
kosa

不変クラスを作成するために、クラスをfinalとしてマークすることは必須ではありません。

Javaクラス自体「BigInteger」クラスは不変ですが、最終的なものではありません。

実際、不変性とは、オブジェクトが作成されたオブジェクトを変更できないという概念です。

JVMの観点から考えてみましょう。JVMの観点からは、すべてのスレッドはオブジェクトの同じコピーを共有する必要があり、スレッドはオブジェクトにアクセスする前に完全に構​​築され、構築後にオブジェクトの状態は変化しません。

不変性とは、オブジェクトの作成後にオブジェクトの状態を変更する方法がないことを意味します。これは、クラスが不変であることをコンパイラに認識させる3つの経験則によって実現されます。

  • 非プライベートフィールドはすべて最終的なものである必要があります
  • オブジェクトのフィールドを直接または間接的に変更できるメソッドがクラスにないことを確認してください
  • クラスで定義されたオブジェクト参照は、クラスの外部で変更できません

詳細については、以下のURLを参照してください

http://javaunturnedtopics.blogspot.in/2016/07/can-we-create-immutable-class-without.html

2
Tarun Bedi

あなたがそれを最終的なものにしないなら、私はそれを拡張し、それを変更不可能にすることができます。

public class Immutable {
  privat final int val;
  public Immutable(int val) {
    this.val = val;
  }

  public int getVal() {
    return val;
  }
}

public class FakeImmutable extends Immutable {
  privat int val2;
  public FakeImmutable(int val) {
    super(val);
  }

  public int getVal() {
    return val2;
  }

  public void setVal(int val2) {
    this.val2 = val2;
  }
}

これで、FakeImmutableをImmutableを期待するクラスに渡すことができ、期待されるコントラクトとして動作しません。

1
Roger Lindsjö

次のクラスがfinalではなかったとします:

public class Foo {
    private int mThing;
    public Foo(int thing) {
        mThing = thing;
    }
    public int doSomething() { /* doesn't change mThing */ }
}

サブクラスでさえmThingを変更できないため、明らかに不変です。ただし、サブクラスは変更可能です。

public class Bar extends Foo {
    private int mValue;
    public Bar(int thing, int value) {
        super(thing);
        mValue = value;
    }
    public int getValue() { return mValue; }
    public void setValue(int value) { mValue = value; }
}

タイプFooの変数に割り当て可能なオブジェクトは、mmutableであることが保証されなくなりました。これは、ハッシュ、平等、並行性などの問題を引き起こす可能性があります。

0
Ted Hopp

Equals()のデフォルトの意味は、参照等価と同じです。不変のデータ型の場合、これはほとんど常に間違っています。したがって、equals()メソッドをオーバーライドして、独自の実装に置き換える必要があります。 リンク

0
Anitha B S

デザイン自体には価値がありません。デザインは常に目標を達成するために使用されます。ここでの目標は何ですか?コードの驚きの量を減らしたいですか?バグを防ぎたいですか?盲目的にルールに従っていますか?

また、設計には常にコストがかかります。 名前にふさわしいすべてのデザインは、目標の矛盾があることを意味します

それを念頭に置いて、次の質問に対する答えを見つける必要があります。

  1. これにより、いくつの明らかなバグが防げますか?
  2. これにより、いくつの微妙なバグが防げますか?
  3. これにより他のコードがより複雑になる(=エラーが発生しやすくなる)頻度はどれくらいですか?
  4. これにより、テストが簡単になりますか、それとも難しくなりますか?
  5. プロジェクトの開発者はどれくらいいいですか?スレッジハンマーでどのくらいのガイダンスが必要ですか?

チームに多くのジュニア開発者がいるとします。彼らは、彼らの問題に対する良い解決策をまだ知らないという理由だけで、愚かなことを必死に試みます。クラスをfinalにすると、バグを防ぐことができます(良い)が、コード内のすべての場所でこれらすべてのクラスを変更可能なクラスにコピーするような「賢い」解決策を考え出すこともできます。

一方、どこでも使用された後にクラスfinalを作成することは非常に困難ですが、後で見つければfinalクラスをfinal以外にすることは簡単です。拡張する必要があります。

インターフェイスを適切に使用する場合、インターフェイスを常に使用し、必要に応じて後で変更可能な実装を追加することにより、「この変更可能にする必要がある」という問題を回避できます。

結論:この答えには「最良の」解決策はありません。それはあなたが喜んでいる価格とあなたが支払わなければならないものに依存します。

0
Aaron Digulla