読者です 読者をやめる 読者になる 読者になる

MogLog

メモというか日記というか備忘録というか

『パーフェクトJava』学習ノート:変数とオブジェクト

■ オブジェクトへの参照の理解の重要性について
オブジェクトの参照の理解は、Javaプログラミングの理解の基盤になる。
この概念を理解することができれば、Javaの多くのソースコードを読んで理解できるようになる。逆にこの部分を曖昧なままにしてしまうと、どこかで挫折をする可能性が高まる。


Javaの変数の型について
基本型と参照型の2つに分類することができる。
現場のJavaプログラミングでは参照型の変数の利用の方が主である。
本書では、参照型を説明するモデルとして「名なしのオブジェクトと(オブジェクトを)名前で呼ぶための変数」を利用する


☆ オブジェクト・参照・変数の関わりについて
変数は名無しのオブジェクトを名前で呼べるための橋渡しをする。変数とオブジェクトを結びつけて、変数を通じて、オブジェクトにたどり着く、この概念こそが参照型変数を説明するためのモデルである。
これを具体化すると以下の説明になる。オブジェクトは名無しではあるが、どこかに存在する以上、存在する場所がある。専門的に言うと、オブジェクトの場所を示す位置情報(アドレス)がある。「参照」とはこの位置情報を指し示すことだと理解しても構わない。
「オブジェクトの参照を持つ」、「オブジェクトへの参照」 → 変数にオブジェクトの位置情報を結びつけて変数(名)を使ってオブジェクトにたどり着く手段ができること


■ 参照型変数のまとめ
・「参照」とはオブジェクトの位置情報をしめす「何か」である
・参照型変数は値として、「参照」を持つ
・「参照」は、その直接の値を意識することは無い(すべきでない)
・結果として、参照型変数には「オブジェクトを指し示す役割」だけが残る


■ オブジェクトとオブジェクトの参照
本質的に「オブジェクト」と「オブジェクトの参照」は別物である。
オブジェクトは実体であり、オブジェクトの参照は実体を指し示す何かであるから。
しかし、現実にはこの辺りの用語はごっちゃになって使われがちである。その理由は、それが自明であるからだとか。


☆ 参照型変数の型について(暫定的な理解のために)
参照型変数には型が存在する。これはオブジェクト自身の型とは別に存在する。
このことは、Javaの参照型変数を理解する上で重要な概念である。
※詳細には関しては別の章で取り扱う


■ 変数の宣言のための構文
[修飾子] 型名 変数名;

StringBuilder sb; // 参照型変数の宣言
int i; // 基本型変数の宣言


■ 変数の修飾子一覧
・private
・protected
・public
・transient
・final
・static
・volatile


■ 参照型変数への代入の定義
参照型変数への代入とは、変数の値にオブジェクトの参照をセットすることである。


■ 代入が起こりうるパターン
・代入演算子(=)を使った代入式
・メソッドおよびコンストラクタの呼び出しの引数
・例外のcatch節の引数

StringBuilder sb = new StringBuilder(); // 代入演算子の例


■ 参照型変数の代入(サンプルコード)
以下のサンプルコードの動作を理解すること

// 同じオブジェクトを参照する2つの変数への操作
StringBuilder sb = new StringBuilder();
StringBuilder sb2 = sb;
sb.append("012");
sb2.append("345");

System.out.println(sb);  // => "012345"
System.out.println(sb2); // => "012345"

■ 基本型変数の代入
参照型変数がオブジェクトへの参照を持つのとは違い、基本型変数は値そのものを保持する。
以下のサンプルコードの動作を理解すること。

// 基本型変数の代入例
int i = 42;    // => iは42
int j = i + 1; // => jは43
int k = i;     // => kは42
i++;

System.out.println(i); // => 43
System.out.println(j); // => 43
System.out.println(k); // => 42 (43ではない)


■ 変数を介さないオブジェクトの操作は可能である
通常、オブジェクトに作用を及ぼす場合、そのオブジェクトへの参照を持つ変数を通じて、メソッド呼び出しやフィールドアクセスを行う。しかし、オブジェクトを使う際に必ずしも参照を変数に代入しなくてはならないというわけではない。

new StringBuilder("012").length(); // 変数を介さないオブジェクト操作の例


☆ 変数の型とオブジェクトの型の規則
1.変数とオブジェクトの型が一致していれば、そのオブジェクトの参照を変数に代入できる
2.変数が参照するオブジェクトに対して行える操作は、変数の型で決まる
3.階層関係のある型Aと型Bがあり、型Aを上位型、型Bを下位型とした場合、A型の変数にB型のオブジェクト参照を代入できる
4.下位型Bのオブジェクトは、上位型Aの持つメソッドを持つことが保証されている。ただし、その実装は異なっても良い。

StringBuilder sb1 = new StringBuilder(); // 合法
StringBuilder sb2 = new ArrayList();     // コンパイルエラー

sb1.length(); // 合法。lengthメソッドはStringBuilderクラスにあるメソッド。
sb1.size(); // コンパイルエラー。sizeメソッドはStringBuilderクラスには無いメソッド。


■ 「振る舞い」と「実装」について
振舞いとはある型がメソッドfooとbarを持つことを指す。実装とは、それらのメソッドを呼んだ時にオブジェクトの内部で実際に何が起きるかを指す。
メソッドのオーバライドや継承の理解が必須になるところだと考えられる。


■ 変数の種類
1.ローカル変数:メソッド内およびコンストラクタ内で宣言される変数
2.パラメータ変数:メソッド及びコンストラクタに引数として渡る変数
3.インスタンスフィールド変数:クラスの構成要素。インスタンスとともに存在する変数
4.クラスフィールド変数:クラスの構成要素。クラスとともに存在する変数
5.(配列構成要素):詳細は後述


■ シャドーイング
最初に宣言した変数iは後の中括弧内でも有効なスコープを持つ。
しかし、ブロック内で新規に宣言した変数が最初のスコープと重なっている。これをシャドーイングと呼び、Javaはシャドーイングを禁止している(バグのもとであるため)。

public static void main(String[] args) {
  int i = 1;
  {
    int i = 0; // コンパイルエラー(シャドーイング)
    System.out.println(i);
  }
  System.out.println(i);
}


■ final変数について
final修飾子を指定した変数をfinal変数と呼ぶ。
final変数は「変更不可」や「書き込み不可」と呼ばれる。しかし、この呼ばれ方は誤解を招きやすい。final変数を厳密に定義すると「再代入不可の変数」となる。
final変数が基本型変数の場合、「変更不可」や「書き込み不可」という表現は間違っていない。
final変数が参照型変数の場合、final修飾子が禁止するのは再代入であって、参照先オブジェクトの変更ではない。
final修飾子が禁止するのは、変数自体の値の変更であって、変数が参照するオブジェクト自体の変更ではないのである。

// 再代入でコンパイルエラー
final double PI = 3.14;
PI = 3.14159;

// 再代入でコンパイルエラー
final StringBuilder sb = new StringBuilder("foo");
sb = new StringBuilder("bar");

// エラーにはならず、参照先のStringBuilderの文字列は変更される
final StringBuilder sb = new StringBuilder("foo1");
sb.append("bar");


■ 堅牢なプログラムのための工夫
1.変数は宣言と同時に初期化する(無理の無い範囲で)
2.変数を使いまわさない(再代入は避ける)
3.変数のスコープを小さくする
4.オブジェクトの寿命を意識する
5.不変オブジェクトを活用する