Effective Javaを読むチャレンジ-項目15-

項目15 可変性を最小限にする

不変クラスの話。

不変クラスは、そのインスタンスが変更できないというクラス。今さらですが、インスタンスは生成されたオブジェクトの実体だと思ってください。簡単に言うとnewされたオブジェクトです。Javaクラスで有名な不変クラスは、Stringやボクシングされた基本データ型、BigIntegerとBigDecimalです。本書では不変クラスは以下の規則に従えとあります。

  1. オブジェクトの状態を変更するメソッドがない
  2. クラスが拡張できない
  3. すべてのフィールドがfinal
  4. すべてのフィールドがprivate
  5. 可変オブジェクトをフィールドにもつ場合、その参照は外部から取得させない

ところが、BigIntegerとBigDecimalはfinalなクラスではないので拡張できます。本書によると、これらのクラスが書かれた当時は不変クラスは実質的にfinalでなければならないという理解が広まっていなかったということです。互換性のために今さら変えられないので、引数のBigIntegerの不変性に依存したセキュリティを持つ処理はBigIntegerがそのサブクラスでないかどうかの型チェックが必要になります。

そのセキュリティ脆弱性の例はJavaセキュアコーディングスタンダード CERT/Oracle版にあります。BigIntegerの偽のサブクラスを可変にし、さらに無害なメソッドを悪意あるメソッドに書き換えてしまうことができます。さらにその書き換え可能なメソッド(modPow()メソッド)は暗号化技術でよく使われるので、無視できない問題だということです。恐ろしい話ですね。

また、すべてのフィールドがfinalという点は、パフォーマンスのために弱めることができるとあります。コストが高い計算結果を一時キャッシュするためにはfinalを外す必要があります。なんにせよ、重要なのはオブジェクトの状態を不変にし、そのクラスが実質的finalであるということです。

拡張できないクラスというのは、finalなクラスとするか、あるいはprivateかパッケージプライベートなコンストラクタしかもたないクラスにして、オブジェクトの生成はstaticファクトリメソッドで行うことです。

そうして作られた不変クラスについて、その特徴や欠点の話が続きます。その辺を読んでいきます。

本質的にスレッドセーフで、同期の必要がない

異なるスレッドから参照されても状態が変わらないのでスレッドセーフです。つまり際限なく共有できます。例えば頻繁に生成される値についてpublic static finalの定数にしたりできます。例えばBigInteger.ZEROなどです。

また、このことから不変クラスは防御的コピーを行う必要がなく、cloneメソッドのオーバーライドやコピーコンストラクタを用意する必要もなくなります。

不変オブジェクトを共有できるだけでなく、その内部を共有できる

本書では下記のBigInteger#negateメソッドを例にしています。BigIntegerは整数の数値部分を表すint配列と、符号を表すintを別々に内部で保持しています。negateメソッドはその数値部分の配列はそのままに、符号を反転したオブジェクトを生成します。

コードを見てお分かりだと思いますが、新しいオブジェクトの数値部分の配列(this.mag)には、元のオブジェクトの数値部分の配列の参照を渡しています。つまり、negateメソッドで生成されたオブジェクトは内部を共有しています。これは、不変クラスでないとできないことです(参照元を変更される心配がないので)。

他のオブジェクトに対する素晴らしい建築ブロックを作り出す

この素晴らしい建築ブロックという言葉が何のことかさっぱり分かりませんが、その例として「一旦、マップやセットに入ってしまったオブジェクトが、マップやセットの不変式を壊してしまうような変化を心配する必要はありません。」とあります。

マップやセットの不変式ってどういう意味なんでしょうか。javaで不変式と聞くと「a<b」のようなものを思い浮かべるのですが・・・。この段落に書かれている内容はまったく分かりません。

欠点:個々の異なる値に対して別々のオブジェクトを必要とする

不変クラスのオブジェクト生成にコストがかかる場合、値が変わるたびに新たなオブジェクトを生成する不変クラスはパフォーマンスを悪くします。

可変性を最小限にするということ

最終的に、この項目で言いたいことは以下のとおり。

  • クラスは特に可変にする理由がない限り、基本的に不変にする
  • クラスを不変にできない場合は、その可変性を限りなく制限する
  • finalにできるフィールドはすべてfinalにする

現実的にこのことを遵守するのは難しい気がしますが、できるだけ考えとして持っておきたいところです。

広告
  • LINEで送る