4章.クラスとインターフェース
15.クラス、メンバ変数のアクセシビリティは最小限にせよ
-
情報を隠す第一の目的は、システム構成から独立したものに切り分けておき、独立に開発、テスト、最適化、理解、変更等ができるようにしておくことである。
-
classのアクセシビリティには、publicかpackage-private(アクセス修飾子なし)しかない。
-
package-privateなクラスが1つのクラスからのみ呼ばれているのであれば、private static な入れ子クラスにすることを検討して、アクセスされる可能性を低くするべき。
-
Java9からは、アクセス制御の仕組みとして、module systemが採用されている。
16.publicなクラスにおいては、アクセサメソッドを用い、フィールドをpublicにはしないようにせよ
-
java.awtクラス内のPoint.javaなどはフィールド変数のアクセシビリティがpublicになっていたりするが、性能問題によりそうしているよう。
-
immutableなフィールド変数であれば、publicであっても害は少ないが、やめておいたほうが良い。
17.可変性を最小限にせよ
-
不変クラスは設計、使用、実装が可変クラスよりもしやすい。
-
不変クラスを作るにあたって、以下の5つをルールを順守するべし。
-
オブジェクトの状態を変更させるようなメソッドを提供しない
-
継承が不可能であるように作る
-
全てのフィールドをfinalにする
-
全てのフィールドをprivateにする
-
可変クラスへのアクセスをさせないようにする
-
-
不変クラスは本質的にスレッドセーフであり、同期が必要ない。
-
不変クラスのデメリットは、異なる値に対して別々のオブジェクトを作成することとなり、コストが高くつく点にある。これの解決策として、コンパニオンクラスなるものがある。具体例としては、不変クラスであるStringに対応する可変クラスのStringBuilderである。
-
不変クラスを継承させない方法として、クラスにfinal修飾子をつける以外の方法として、コンストラクタをprivateとして、ファクトリメソッドをpublicで作成する方法がある。
-
BigInteger,BigDecimalが実装された当時は、不変クラスは継承させないほうが良い、ということが分かっていなかったため、継承が可能。
-
可能な限り不変クラスに近づけるため、原則として、フィールドはprivate final にするべき。(CountDownLatchは好例)
18.継承よりコンポジションを選ぶべし
-
具象クラスをパッケージをまたいで継承するのは危険。起こりうる問題は以下のように2つある。
-
スーパークラスの実装はリリース毎に変わりうる。サブクラスはそれに合わせて変更を加えなければならないかもしれない。
-
スーパークラスに新しいメソッドが追加された場合にセキュリティホールとなる場合があるらしい。(全然ピンと来ない)
-
-
継承を回避するにあたっては、もともとスーパークラスになるはずだったクラスを private finalなフィールドとして取り込み(composition)、各メソッドでその取り込んだフィールドのメソッドを参照する(forwarding)ことによって実現できる。
-
↑の作りの欠点は、callback framework には向いていないということ。ラップされたオブジェクトはどれがラップしたオブジェクトであるかを認識しないため、自身への参照でサブクラス呼び出しをすることができない。
-
B is a A の関係が本当に成り立たない限り、B extends A はやめたほうが良い。これはJavaプラットフォームライブラリでも守れていなくて、Stack はVectorではないので、StackがVectorを継承するべきではないし、Properties と HashListの関係も同様である。
19.継承のための設計、ドキュメンテーションがをすべし。なければ継承を禁止すべし
-
オーバーライド可能なメソッドに関しては、自身の利用方法を書く必要がある。そのためのJavadoc用のアノテーションが@implSpecである。
-
一般に良いAPIは、何をするかを記述し、どのようにするかを記述しないが、継承を安全に行うにあたっては詳細な仕様が必要であるため記述する。
-
継承元となるクラスの設計をテストするには、実際にサブクラスを作ってみてテストするほかない。広く使われるライブラリを目指すのであれば、一度リリースしたprotectedなメソッドはほぼ変更不可能なので、必ずリリース前にサブクラスを作ってみて確かめてみるべき。
-
コンストラクタでオーラライドできるメソッドの呼び出しをしてはならない。なぜなら、サブクラスのコンストラクタの処理が走る前に、スーパークラスのコンストラクタの処理が必ず走るが、「サブクラスのコンストラクタ内で、サブクラスのフィールド初期化等の処理があり、オーバーライドしたメソッドがそのフィールドを用いた処理を行う」といった場合には、直感的でない挙動を示すため。
-
基本的に、継承元として設計するクラスには、Serializable,Clonableは実装させるべきではないが、やまれぬ事情でそうする場合には、readObjectメソッド、cloneメソッドの中で、オーバーライドされうるメソッドを呼んではならない。呼んでしまうと、クローンオブジェクトができていないのに、それに対して変更を加えようとする、みたいなことが起こり得るため。
20.抽象クラスよりインターフェースを優先せよ
-
現存しているクラスを、新しく誕生したインターフェースを実装するように改変するのは容易である。
-
インターフェースはmixinsを定義するのに最適である。
- mixinとは単独で動作することを意図しないコード(再利用したいコード)を予め定義しておき、必要に応じてクラスに混ぜ込む(Javaで言うとimplementsする)ことによって、処理の再利用を促す仕組みの事である。
https://ja.wikipedia.org/wiki/Mixin
http://equj65.net/tech/java8mixin/
-
non-primitive interface method ってなんだ?
-
interfaceの利点と抽象クラスの利点を伏せ持つ、骨格実装クラス(skeletal implementation class)がある。骨格実装クラスは、AbstractInterfaceと呼ばれているもので、CollectionsFWのAbstractCollectionやAbstractSetなどがそれにあたる。(本来ならこれらはSkeletalCollection,SkeletalSetと呼ばれるべきだが、慣習的にこう呼ばれている)
-
骨格実装クラスはimplementsしたインターフェースが提供しているメソッドの一部をオーバーライドし、残りのメソッドを骨格実装クラスを継承するクラスでのオーバーライドにゆだねるように作られる。この骨格実装クラスがないと、実装者は直接インターフェースをimplementsして、提供される全てのメソッドをオーバーライドしないといけないが、骨格実装クラスを挟むことによって、実装者がオーバーライドすべきメソッドが減る(誤ったオーバーライドがなされる危険性も減る)。これが利点という理解でOKなのか?
21.後々のことを考えてインターフェースは設計せよ
-
現存するインターフェースにメソッドを追加することにはリスクがある。
- 例として、Collection に removeIf がデフォルトメソッドとして追加されたが、 Collection を implements している Apatche Commonsライブラリの SynchronizedCollection でこの removeIf を呼び出した際には、同期されないメソッドとして呼び出され、SynchronizedCollection 使用における約束事が破られてしまう。Javaライブラリではこれと同様のことを防ぐため、Collections.synchronizedCollection において removeIfメソッドをオーバーライドしている。
22.インターフェースは型定義のためだけに用いよ
-
定数定義だけするようなインターフェース(constant interface)を作るべきでない。java.io.ObjectStreamConstants がやっているがマネするべきでない。以下理由。
-
そのうち定数が必要なくなった時でも、バイナリ互換性の維持のために implements し続けなければならない。(バイナリ互換性??)
-
定数インターフェースを implements したら名前空間が汚染される。
-
-
定数定義の別の合理的な方法は以下。
-
定数定義する値が現存するクラスと密接に結びついているとしたら、そこに加える。
-
定数定義する値が列挙するのがベストなものであればEnumを使う。
-
それ以外だと、ユーティリティクラスを使う。
-
// Constant utility class
public interface PhysicalConstants {
static final double AVOGADROS_NUMBER = 6.02214199e23;
static final double BOLTZMANN_CONSTANT = 1.3806503e-23;
static final double ELECTRON_MASS = 9.10938188e-31;
}
- ユーティリティクラスからたくさん定数を利用する場合はstatic importを使う。
// Use of static import to avoid qualifying constants
import static com.effectivejava.science.PhysicalConstants.*;
public class Test {
double atoms(double mols) {
return AVOGADROS_NUMBER * mols;
}
...
// Many more uses of PhysicalConstants justify static import