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


83

クラスをJavaで不変にするために、次のことを行う必要があることを読みました。

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

ステップ3が必要なのはなぜですか?なぜクラスにマークを付ける必要があるのfinalですか?


java.math.BigIntegerクラスは例であり、その値は不変ですが、最終的なものではありません。
Nandkumar Tekale 2012

@Nandkumarを持ってBigIntegerいる場合、それが不変であるかどうかはわかりません。それはめちゃくちゃなデザインです。/java.io.Fileはもっと興味深い例です。
トムホーティン-タックライン2012

1
サブクラスがメソッドをオーバーライドすることを許可しない-これを行う最も簡単な方法は、クラスをfinalとして宣言することです。- -より洗練されたアプローチは、ファクトリメソッドでプライベートコンストラクタと構造のインスタンスを作ることですdocs.oracle.com/javase/tutorial/essential/concurrency/...
ラウル・

1
@mkobitFileは、不変であると信頼されている可能性が高く、TOCTOU(Time Of Check / Time Of Use)攻撃につながる可能性があるため、興味深いものです。信頼できるコードFileは、が許容可能なパスを持っていることを確認してから、そのパスを使用します。サブクラス化されたものFileは、チェックと使用の間で値を変更できます。
トムホーティン-タックライン2016

1
Make the class finalまたはすべてのサブクラスが不変であることを確認します
O.Badr

回答:


138

クラス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をマークすると、これが発生しなくなります。

お役に立てれば!


13
ミュータブルを実行する場合m = new Mutable(4); m.setValue(5); ここでは、Immutableクラスオブジェクトではなく、Mutableクラスオブジェクトで遊んでいます。したがって、Immutableクラスが不変でない理由はまだ混乱しています
Anand

18
@ anand-Immutable引数を取る関数があると想像してください。なので、Mutableその関数にオブジェクトを渡すことができますMutable extends Immutable。その関数の内部では、オブジェクトは不変だと思いますが、関数の実行時に値を変更するセカンダリスレッドを作成することもできます。Mutable関数が格納するオブジェクトを提供し、後でその値を外部で変更することもできます。言い換えると、関数が値が不変であると想定している場合、可変オブジェクトを提供して後で変更できるため、簡単に壊れてしまう可能性があります。それは理にかなっていますか?
templatetypedef

6
@ anand-Mutableオブジェクトを渡すメソッドはオブジェクトを変更しません。問題は、そのメソッドが、オブジェクトが実際には不変であるのに不変であると想定する可能性があることです。たとえば、このメソッドは、オブジェクトが不変であると見なすため、のキーとして使用できると想定する場合がありますHashMap。次に、を渡してMutable、オブジェクトがキーとして格納されるのを待ってから、オブジェクトを変更することで、その関数を壊すことができMutableます。HashMapオブジェクトに関連付けられたキーを変更したため、その中のルックアップは失敗します。それは理にかなっていますか?
templatetypedef

3
@ templatetypedef-はい、私は今それを手に入れました..それは素晴らしい説明だったと言わなければなりません..それを理解するのに少し時間がかかりました
Anand

1
@ SAM-その通りです。ただし、オーバーライドされた関数がその変数を再び参照することはないため、実際には問題ではありません。
templatetypedef

47

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

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

この引数を論理的に極端なものにする場合は、すべてのメソッドを作成する必要finalがあります。そうしないと、サブクラスがスーパークラスのコントラクトに準拠しない方法でメソッドをオーバーライドする可能性があります。ほとんどのJavaプログラマーがこれをばかげていると見なしているのは興味深いことですが、不変のクラスが必要であるという考えには何とか問題はありませんfinal。これは、一般にJavaプログラマーが不変性の概念に完全に慣れていないことと関係があるのではないかと思います。おそらくfinal、Javaのキーワードの複数の意味に関連するある種のあいまいな考え方です。

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

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

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

とはいえ、不変であるかどうかに関係なく、ほとんどのクラスはおそらくである必要がありますfinal効果的なJavaSecond Editionの項目17:「継承のための設計と文書化またはそれを禁止する」を参照してください。

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


3
それの価値があるというのJava「を期待」を指摘、しかし、2つのオブジェクト与えられたことを、強制しないXY、の値はX.equals(Y)不変で(限りとなりますXし、Y同じオブジェクトを参照し続けます)。ハッシュコードに関しても同様の期待があります。コンパイラが同値関係とハッシュコードの不変性を強制することを誰も期待してはならないことは明らかです(それは単純にできないため)。あるタイプの他の側面に強制されることを人々が期待すべき理由は見当たらない。
スーパーキャット2012

1
また、コントラクトが不変性を指定する抽象型があると便利な場合が多くあります。たとえば、座標ペアが与えられると、を返す抽象的なImmutableMatrixタイプがある場合がありますdoubleGeneralImmutableMatrix配列をバッキングストアとして使用するを導出することもできますが、たとえばImmutableDiagonalMatrix、対角線に沿ってアイテムの配列を単純に格納するものもあります(アイテムX、Yを読み取ると、X == yの場合はArr [X]、それ以外の場合はゼロになります) 。
スーパーキャット2012

2
私はこの説明が一番好きです。常に不変のクラスを作成するとfinal、特に拡張を目的としたAPIを設計する場合に、その有用性が制限されます。スレッドセーフの観点から、クラスを可能な限り不変にすることは理にかなっていますが、拡張可能に保ちます。のprotected final代わりにフィールドを作成できprivate finalます。次に、不変性とスレッドセーフの保証に準拠するサブクラスに関するコントラクトを(ドキュメントで)明示的に確立します。
curioustechizen 2013年

4
+ 1-ブロッホの見積もりまでずっとあなたと一緒です。私たちはそれほど妄想的である必要はありません。コーディングの99%は協調的です。誰かが本当に何かをオーバーライドする必要がある場合でも、大したことはありません。私たちの99%は、StringやCollectionなどのコアAPIを作成していません。残念ながら、Blochのアドバイスの多くは、そのようなユースケースに基づいています。それらはほとんどのプログラマー、特に新しいプログラマーにとってはあまり役に立ちません。
ZhongYu 2015

不変のクラスを作成したいと思いfinalます。ただ、理由equalshashcode、私はそうする正当な理由がない限り、私は、不変クラスのために同じことを容認することを意味するものではありません。最終的にはできません、この場合には、私はマニュアルまたは防衛ラインとしてテストを使用することができます。
O.Badr

21

クラス全体をファイナルとしてマークしないでください。

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

プロパティをプライベートおよびファイナルとしてマークすることをお勧めします。「契約」を保護する場合は、ゲッターをファイナルとしてマークします。

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

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


1
不変のすべての側面を別のクラスにカプセル化し、構成によって使用したいと思います。単一のコードユニットに可変状態と不変状態を混在させるよりも、推論する方が簡単です。
toniedzwiedz 2014

1
完全に同意する。可変クラスに不変クラスが含まれている場合、構成はこれを実現するための非常に優れた方法の1つです。構造が逆の場合は、部分的な解決策として適しています。
ジャスティンオーム2015年

5

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


あなたは正しいですが、基本クラスのフィールドがすべてプライベートで最終的なものである場合、可変の動作を追加できるという事実が必然的に物事を壊す理由は明らかではありません。本当の危険は、サブクラスが動作をオーバーライドすることによって、以前は不変だったオブジェクトを変更可能なオブジェクトに変える可能性があることです。
templatetypedef

4

これにより、クラスを拡張する他のクラスが制約されます。

最終クラスを他のクラスで拡張することはできません。

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

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


2
それは本当ですが、なぜここで必要なのですか?
templatetypedef

@templatetypedef:あなたは速すぎます。私はサポートポイントで私の答えを編集しています。
kosa 2012

4
今のところあなたの答えはとても曖昧なので、私はそれが質問に全く答えないと思います。「継承の原則により、クラスの状態が変わる可能性がある」とはどういう意味ですか?なぜマークする必要があるのか​​をすでに理解していない限りfinal、この回答がどのように役立つかはわかりません。
templatetypedef

3

最終的にしない場合は、拡張して変更不可にすることができます。

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を期待する任意のクラスに渡すことができ、期待されるコントラクトとして動作しなくなります。


2
これは私の答えとほとんど同じだと思います。
templatetypedef

はい、投稿の途中で確認しました。新しい回答はありません。そして、投稿されたとき、あなたは私の1分前にいました。少なくとも、すべてに同じ名前を使用したわけではありません。
ロジャーLindsjö

1つの修正:FakeImmutableクラスでは、コンストラクター名はFakeImmutable NOT Immutableである必要があります
Sunny Gupta

2

不変のクラスを作成する場合、クラスをfinalとしてマークする必要はありません。

Javaクラス自体からそのような例の1つを取り上げましょう。「BigInteger」クラスは不変ですが、最終的なものではありません。

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

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

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

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

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

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


1

次のクラスがあるとします。

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class PaymentImmutable {
    private final Long id;
    private final List<String> details;
    private final Date paymentDate;
    private final String notes;

    public PaymentImmutable (Long id, List<String> details, Date paymentDate, String notes) {
        this.id = id;
        this.notes = notes;
        this.paymentDate = paymentDate == null ? null : new Date(paymentDate.getTime());
        if (details != null) {
            this.details = new ArrayList<String>();

            for(String d : details) {
                this.details.add(d);
            }
        } else {
            this.details = null;
        }
    }

    public Long getId() {
        return this.id;
    }

    public List<String> getDetails() {
        if(this.details != null) {
            List<String> detailsForOutside = new ArrayList<String>();
            for(String d: this.details) {
                detailsForOutside.add(d);
            }
            return detailsForOutside;
        } else {
            return null;
        }
    }

}

次に、それを拡張し、その不変性を破ります。

public class PaymentChild extends PaymentImmutable {
    private List<String> temp;
    public PaymentChild(Long id, List<String> details, Date paymentDate, String notes) {
        super(id, details, paymentDate, notes);
        this.temp = details;
    }

    @Override
    public List<String> getDetails() {
        return temp;
    }
}

ここでそれをテストします:

public class Demo {

    public static void main(String[] args) {
        List<String> details = new ArrayList<>();
        details.add("a");
        details.add("b");
        PaymentImmutable immutableParent = new PaymentImmutable(1L, details, new Date(), "notes");
        PaymentImmutable notImmutableChild = new PaymentChild(1L, details, new Date(), "notes");

        details.add("some value");
        System.out.println(immutableParent.getDetails());
        System.out.println(notImmutableChild.getDetails());
    }
}

出力結果は次のようになります。

[a, b]
[a, b, some value]

元のクラスがその不変性を維持している間にわかるように、子クラスは可変である可能性があります。したがって、設計では、クラスをfinalにしない限り、使用しているオブジェクトが不変であることを確認できません。


0

次のクラスがそうではなかったと仮定します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

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

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

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

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

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

一方、finalどこでも使用された後はクラスを作成するのは非常に困難ですが、クラスを拡張する必要があることがわかった場合はfinalfinal後でクラスを作成するのは簡単です。

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

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


0

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.