5-1 クラスとオブジェクト
5-1-1 クラスの概論
- Javaのオブジェクトはクラスを雛形として生成する。
- StringBuilderクラスからいくつでも実体化したStringBuilderオブジェクトを生成できる。
- ここの独立したStringBuilderオブジェクトは共通の性質を持つ。
- 文字列を保持できる性質
- 共通するメソッドで同じ操作が可能
5-1-2 フィールドとメソッド
- フィールドの概要
- どんな状態を持つかを指定するのがフィールドの役割。
// 書物クラスの定義例
class Book {
String title;
String author;
}
- メソッドの概要
- メソッドにはオブジェクトに対する処理が書かれています。使う側から見ると、オブジェクトに処理を依頼したり、オブジェクトの状態を知るために呼び出すのがメソッド。
class Book {
String title;
String author;
void printTitle() {
System.out.println(this.title);
}
}
// 書物クラスのメソッド利用例
Book book1 = new Book();
book1.title = "Peopleware";
book1.printTitle(); // => "Peopleware"を出力
5-1-3 オブジェクト
- メソッドはクラスにあるのかオブジェクトにあるのか?
- メソッドの実態がクラスにあり、各オブジェクトがメソッドへの矢印を持つ。
-
ただし、各オブジェクトにあるべきメソッドの実体がたまたまクラスにあるだけと考えるように!
- メソッド自体は固有の状態をもたないのでコピーしないだけ。
5-1-5 オブジェクト、インスタンス、クラス、型、雛型
それぞれの用語を整理している。
- 「型と雛型」の説明はあまり飲み込めなかった。
- 雛型としての型定義が先にあり、雛型を元に実体化したインスタンス群が共通の型を持つ。
- インターフェースや抽象クラスのように直接の実体化を許可しない型定義もある。
5-1-6 既存クラスの利用
- オブジェクトを生成
- new式による明示的な生成
- Stringリテラル表記および結合演算式による暗黙の生成
- オートボクシングによる生成
- リフレクションによる生成
- Objectクラスのcloneメソッドによる複製
5-1-7 オブジェクトのライフサイクル管理とファクトリパターン
オブジェクトのライフサイクルを適切に管理する次のような技法がある。
- 変数のスコープに注意して、不要に長い寿命のオブジェクトを減らす。
- オブジェクトの寿命を意識して、寿命の短いオブジェクトと寿命の長いオブジェクトを分離する。
- ファクトリパターンやDI(Dependency Injection)など、オブジェクト生成向けの技法を活用する。
- オブジェクトのライフサイクル管理をフレームワークなどの層に隠蔽する。
■ファクトリパターン
- ファクトリパターンの基本的な考え方は、オブジェクト生成を1つの役割と見なして、その機能を分離すること。
- ファクトリメソッドを提供する場合、コンストラクタのアクセス制御をprivateにすべきです。
class My {
// コンストラクタを直接呼べないようにprivateにする
privateg My() {}
// ファクトリメソッド
static My getInstance() {
// 以下の2行は1行でも書けます(return new My())
My my = new My();
return my;
}
}
// ファクトリメソッドの呼び出し側
My my = My.getInstance();
- メリット
- コンストラクタと異なり、自由にメソッド名をつけられるので、生成のために意味のある名前をつけられる。
- ファクトリメソッドは、必ずしも新規のオブジェクトを生成して返す実装にする必要はない。
- ファクトリメソッドの返り値の型を抽象型にできる。
5-1-8 クラス自体を利用(ユーティリティクラス)
- ユーティリティクラスの方針
- finalクラスにして無用な継承を禁止する。
- コンストラクタをprivateにして、インスタンス化を禁止する。
- すべてのメソッドとフィールドにstatic修飾子をつける。
5-2 クラス宣言
5-3 フィールド
5-3-1 フィールド宣言
- 特別な理由がなければ `private final' が修飾子が定石。
5-4 メソッド
5-4-7 可変長引数
- メソッド宣言の引数定義でパラメータ変数の型に...(ドット文字3つ)を書くことで、任意の数の実引数で呼べる。
class My {
// 可変長引数のメソッドの宣言例
void exec(String... messages) {
for (String s : messages) {
System.out.println(s);
}
}
}
// 可変長引数のメソッド呼び出し例
My my = new My();
my.exec(); // 実引数なしも可
my.exec("0");
my.exec("0", "1", "2");
- 可変長引数は最後の1つの引数にだけ使えます。
5-4-9 メソッドのオーバーロード
- 引数の型を変えると同名のメソッドでもコンパイルできる。これをメソッドのオーバーロードと呼ぶ。
class My {
void exec(String s) {
//
}
void exec(StringBuilder s) {
//
}
}
// オーバーロードしたメソッドの呼び出し側
My my = new My();
my.exec("abc");
my.exec(new StringBuilder("abc"));
5-4-10 メソッドのシグネチャ
- クラス内でメソッドを一意に区別する最小情報をシグネチャと呼ぶ。メソッドのシグネチャは次の2つで確定する。
- メソッド名
- 引数の型の並び
5-5 コンストラクタ
5-5-4 this呼び出しとsuper呼び出し
- コンストラクタ内からのメソッド呼び出し処理は避けるほうが無難。
- 共通処理をまとめるときはthis呼び出しを使う。
class Book {
// フィールド宣言などは省略
Book(String title, String author) {
this(title, author, 1000);
}
Book(String title, String author, int price) {
this.title = title;
this.author = author;
this.price = price;
}
}
- 同様の仕組みに、継承したクラスから継承元のコンストラクタを呼ぶsuper呼び出しがある。
class ProgrammingBook extends Book {
private final String language;
ProgrammingBook(String title, String author, int price, language){
super(title, author, price);
this.language = language;
}
}
- this呼び出しもsuper呼び出しも、コンストラクタ本体の最初の文でなければいけない。
5-5-5 デフォルトコンストラクタ
-
デフォルトコンストラクタは一切使わない!
- 1つでもコンストラクタを書き足すとデフォルトコンストラクタが消滅し、エラーを引き起こすことがある。
- 自分もやってしまいがちなので気をつけよう。。。
5-5-6 初期化ブロック
- すべてのコンストラクタの共通処理を記述可能
- 匿名クラスの初期化のため
class My {
private final int i;
{ // 初期化ブロック開始
i = 5;
System.out.println(i);
} // 初期化ブロック終了
}
5-5-7 オブジェクト初期化処理の順序
- フィールド変数にデフォルト値代入
- フィールド変数宣言時の初期化および初期化ブロックをコードで上から書かれた順に実行
- コンストラクタ呼び出し
コンストラクタや初期化ブロックからのメソッド呼び出しはわかりずらく変更に弱いコードになるため避ける。
class My {
private final int v = 1;
void exec() {
System.out.println(v);
}
My() {
v = 2;
}
{
exec(); //=> 1を出力
}
}
5-6 staticメンバ
5-6-10 クラスをオブジェクトのように扱わないこと
- 本質的な差は参照を得られるかどうか。
- 参照を得られると、別オブジェクトのフィールド変数から参照したり、メソッドの引数に渡したり、コレクションの要素にしたりできる。
■クラスフィールドとクラスメソッドの用途
- ユーティリティクラスのメソッド
- クラスの役割(型定義と雛型)に関連する状態や操作
5-7 継承
5-7-1 継承とは
- プログラミングの基本的な技法の1つが重複コードの排除。
■複数のクラスの共通処理
- 手続きとして共通部をくくりだす
- ex.ユーティリティクラス
- 共通部をクラスとしてくくりだして、そのオブジェクト参照をフィールドに持ち、処理を受け渡す(移譲する)
- 共通部を基底クラスとしてくくりだして、クラスを階層管理する(継承)
■拡張継承と委譲の使い分け
- 本質的な差は多態(ポリモフィズム)
- 継承は単なる共通処理のくくりだしではなく、型定義の共通部のくくりだしとしてとらえる必要がある。
- 単に共通処理のくくりだすだけが目的であれば委譲を使う!
■コードの再利用と拡張継承
- 拡張継承は、そのクラスが拡張継承されることを明確に意図して作られたクラス(抽象クラス)に限る!
5-7-4 メソッドのオーバーライド
-
@Overfideというアノテーションを使う。
- オーバーライドしたつもりでできていない場合はコンパイルエラーで気づけない。
5-7-7 finalクラス
- finalクラスを継承しようとするとコンパイルエラーとなる。不変クラスやユーティリティクラスなど、拡張継承を明示的に禁止したい場合にfinalを指定する。
5-8 抽象クラスと抽象メソッド
5-8-1 テンプレートメソッドパターン
- 型階層をトップダウン的に見る典型的な手法。
- 変わらない部分と変わりやすい部分を分離する。
abstract class Base {
// 継承クラスがオーバーライドすべき抽象メソッド(protectedにするのが定石)
protected abstract void doTask();
void exec() { // 骨格実装
共通処理
doTask(); // 継承クラス固有の処理を呼び出す
共通処理
}
}
// それぞれの継承クラスはdoTaskメソッドをオーバーライドして固有処理をする
class My1 extends Base {
@Override
protected void doTask() {
継承クラスに固有の処理
}
}
class My2 extends Base {
@Override
protected void doTask() {
継承クラスに固有の処理
}
}
5-8-2 抽象クラスの意義と拡張継承の現実解
- 拡張継承する場合、継承元クラスは必ず抽象クラスにする
- 具象クラスからの拡張継承はしない
5-9 アクセス制御とカプセル化
- クラスのアクセス制御で可視性が変わるのはクラス名
5-9-1 アクセス制御の使い分け
- アクセス制御の原理原則は「可能な限り狭い可視性にする」
- フィールドは基本的にprivateにする。
- 特定のクラス内でしか使わない下請け処理メソッドはprivateにする
- 継承先のクラスでも使う下請け処理メソッドはprotectedにする
- テンプレートメソッドパターンで継承クラスにオーバーライドさせたいメソッドはprotectedにする
- パッケージ内で他から呼ぶメソッドはパッケージ可視にする
- パッケージ外から呼ぶ必要があるメソッドのみpublicにする
5-11 クラスの設計
- 同じコードを繰り返さない。共通部をまとめる
- 変わりやすい部分と変わりにくい部分を分ける(テンプレートメソッドパターン、ストラテジパターン、パラメータ化)
- クラス間の依存を減らす。依存関係を単純化する。
- 呼びやすいコードにする(外部から呼ばれることを意識する)