MogLog

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

『パーフェクトJava』学習ノート:クラス

パーフェクトJavaの第5章「クラス」について、重要と思った箇所を箇条書きベースでまとめた。

Javaにおけるオブジェクトとは「振る舞いと状態を持つ実体(インスタンス)」である。

■ クラスは雛形であり、クラスをもとにインスタンスを生成する。生成されたものはオブジェクトと呼ばれる。

■ クラスは状態と振舞いの共通点のために、フィールドとメソッドという機能を持つ

■ オブジェクトには振る舞いと状態という概念がある。これらは区別できるように

■ 「フィールド」は状態やデータを表現するために使う。
Book book = new Book();
book.title = "hoge";
→ titleはフィールド。変数にドットとフィールド名を記述することで変数のフィールドにアクセスすることができる

■ 「メソッド」にはオブジェクトに対する操作の手続きを記述する。
オブジェクトを使う側から見ると、オブジェクトに処理を依頼したりオブジェクトの状態を知るために呼び出すのがメソッドである
インスタンス化したオブジェクトに対して、ドットとメソッド名をつなげて記述する、オブジェクトのメソッド呼び出しを意味する。また、専門的にはメソッド呼び出しの対象オブジェクトをレシーバと呼ぶ。

■ フィールドとメソッドは結局、クラスにあるのか、それともオブジェクトにあるのかどちらなのか。概念的な回答はフィールドとメソッドの定義がクラスにあり、各オブジェクトはクラスに定義されたフィールドとメソッドの実体を持つと答えることができる。

型とは振る舞いの共通性を規定する概念

Javaではオブジェクトの型と変数の型は必ずしも一致しない。一致させないことで多態性を実現することができる

雛型とは、何かを実体化する時にその鋳型としての役割を指す。Javaには、「雛型」としての役割を担う言語機能として「クラス」と「ジェネリック型」の2つが存在する。(現実的には雛型の役割を担うと、そのまま型定義の役割を担う傾向にある)

Javaの世界に限定して「型」を考えると、型定義を行う言語機能として「クラス」と「インターフェース」の2つがあり、オブジェクトと変数が型を持つ。

オブジェクトという用語はプログラミング一般に広げて考えると多義的で抽象的であるが、Javaに限るとその意味は限定的になる。Javaの世界でオブジェクトと呼べる対象は「クラスのインスタンス」しかない。また、「インスタンス」という用語の意味もJavaでは限定でき、慣習的に「クラスを実体化した実体のみ」をインスタンスと呼ぶ。
(例)Stringクラスのインスタンス → Stringオブジェクト

■ オブジェクトの生成手段5つ
1.new式による明示的な生成(基本的な生成手段)
2.Stringリテラル表記および結合演算式による暗黙の生成(文字列固有の生成手段)
3.オートボクシングによる暗黙の生成(数値クラス固有の生成手段)
4.リフレクションによる生成(フレームワークなどが下位に隠蔽すべき生成手段)
5.Objectクラスのcloneメソッドによる複製

■ 生成したオブジェクトでできること
1.インスタンスメソッドの呼び出し
2.インスタンスフィールドの参照(カプセル化の思想的には余り推奨できないが、一般に行なわれること)
3.クラスメソッドの呼び出し(文法的には許されているが、行うべきではない)
4.クラスフィールドの参照(文法的には許されているが、行うべきではない)

■ クラスには2つの使い方がある。1つはクラスからオブジェクトを生成して使う方法、もう1つはクラスそのものを使う方法。

■ ユーティリティクラスとは、クラスを直接使うことだけを想定しているクラスのことを指す。そのため、ユーティリティクラスはインスタンス化できない。java.lang.Mathクラスはユーティリティクラスである。
(使い方の例)Integer.MIN_VALUE

■ フィールド変数のスコープは、メソッド内とコンストラクタ内を除くと、フィールド変数宣言以降になる。メソッド内とコンストラクタ内に限っては、フィールド変数の宣言が後ろになっても問題ない。

■ thisは該当クラスのオブジェクトを参照する参照型変数である。クラス内の変数名の検索では、フィールド変数よりもローカル変数とパラメータ変数を優先する。以下の動作を理解できればひとまずOKな気がする。

class My {
     String s = "foo";
     void method(String s) {
          System.out.println(s); // パラメータ変数
          System.out.println(this.s); // フィールド変数
     }
     void method2() {
          String s = "foo";
          System.out.println(s); // ローカル変数
          System.out.println(this.s); // フィールド変数
     }
}


■ メソッド宣言の構文
1.メソッド本体がある場合の文法
[修飾子] 返り値型 メソッド名([引数型 引数名, ...]) [throws節] {
メソッド本体
}

2.メソッド本体がない場合の文法(abstractメソッドまたはnativeメソッド)
[修飾子] 返り値型 メソッド名([引数型 引数名, ...]) [throws節];

■ メソッドに返り値が存在しない場合は、メソッドの返り値型の記述箇所に「void」と記述する。

■ メソッド宣言の修飾子
public:アクセス制御
protected:アクセス制御
private:アクセス制御
abstract:抽象メソッド
final:オーバーライド不可
static:クラスメソッド
synchronized:同期のためのロック構造
native:ネイティブメソッド
strictfp:浮動小数点演算を厳密に評価

■ 返り値型がvoidのメソッドに対してreturn文を書くとコンパイルエラーになる

■ オーバーライドとオーバーロードは違う。オーバーライドはスーパークラスで定義されたメソッドをサブクラスで再定義することを指す。一方オーバーロードは同一クラス内で、メソッド名が同一で引数の型、数、並び順が異なるメソッドを複数定義することを指す。

■ メソッドのシグネチャとは、あるクラスの中でメソッドを一意に、区別するために必要な情報のことを指す。実際には、「メソッド名」と「引数の型の並び」になる。

再帰呼び出しとは、メソッドが自分自身のメソッドを呼ぶことを言う。

コンストラクタはオブジェクト生成時に呼ばれる処理のこと。Javaではコンストラクタとクラス名は一致する。
[アクセス制御の修飾子] コンストラクタ名([引数型 引数名, ...]) [throws節] {
}

コンストラクタはオーバーロード可能である

■ オブジェクト初期化処理時に走る処理には、「フィールド変数宣言時の初期化」「コンストラクタ」「初期化ブロック」の3つがある。以下の順序で実行される。
1.フィールド変数にデフォルト値代入
2.フィールド変数宣言時の初期化及び初期化ブロックをコード上で上から順に実行
3.コンストラクタ呼び出し
以下のサンプルコードは数値の順に実行される。

class My {
     int i  = 1;
     {
          System.out.println("2");
     }
     int j = 3;
     My() {
          System.out.println("6");
     }
     int k = 4;
     {
          System.out.println("5");
     }
}


■ static修飾子のついたフィールド変数、メソッド、初期化ブロックはクラスに属するメンバである。これらをクラスメンバと呼ぶ。クラスメンバの対比としてstaticのつかないフィールド変数やメソッドをインスタンスメンバと呼ぶ。

■ クラスをいくつも書いていると、クラスの間に同じコードが繰り返されることがある。このような共通部をまとめる手法として主に3つの方法がある。
1.手続きとして共通部をくくりだす
2.共通部をクラスとしてくくりだして、そのオブジェクト参照をフィールドに持ち、処理を受け渡す(委譲)
3.共通部を基底クラスとしてくくりだして、クラスを階層管理する(継承)

■ クラスからオブジェクトをいくつ生成しようと、クラスフィールド変数の実体は1つしかない(インスタンスフィールドはインスタンスごとにある)。

■ クラスメソッドとは、外見的にはstatic修飾子のついたメソッドのこと。

■ クラスメソッドはクラス内で以下のように呼び出すことができる

class My {
  // インスタンスフィールド変数の初期化子
  int i = doit();

  // クラスフィールド変数の初期化子
  static int i = doit();

  // インスタンスメソッド
  void caller() {
    doit();
  }

  // 別のクラスメソッド
  static void caller2() {
    doit();
  }

  // コンストラクタ
  My() {
    doit();
  }

  // 初期化ブロック
  {
    doit();
  }

  // static初期化ブロック
  statis {
    doit();
  }

  // クラスメソッド宣言
  static int doit() {
    System.out.println("doit");
    return 1;
  }
}


■ メソッド、コンストラクタ、初期化ブロック内のローカル変数やパラメータ変数はクラスフィールド変数を隠蔽する

■ クラス名にドット演算子で明示的にクラスフィールド変数にアクセスすると隠蔽を回避することができる。アクセス制御が許せば他のクラスからクラスフィールドにアクセスすることはできるが、非推奨。

class You {
     void doit() {
          System.out.println(My.sb); // クラスフィールドにアクセス
          My my = new My();
          System.out.prinltn(my.sb); //クラスフィールドにアクセス(非推奨)
     }
}


■ クラスメソッドはオブジェクト参照を通じた呼び出しも可能であるが、クラスフィールド同様に非推奨。

class You {
     void caller() {
          My.doit(); // クラスメソッド呼び出し
          My my = new My();
          my.doit(); // クラスメソッド呼び出し(非推奨)
     }
}


クラスメソッドはクラスに属するものであり、オブジェクトに属するものでは無い。そのため、クラスメソッドの中からインスタンスフィールドにアクセスすることや、インスタンスメソッドを呼び出すことはできない。クラスメソッド内でthis変数を使うこともできない。

■ クラスはオブジェクトのように扱ってはいけない。様々な観点からクラスをオブジェクトとして扱うのは間違い
・クラスは、メソッドの引数に渡すことも、コレクションの要素にすることもできない。オブジェクトはできる。
・クラス自身はスコープが広いので、コードの構造をわかりづらくする
・クラス自身を使うと継承による恩恵をうけることができなくなる

■ クラスフィールドとクラスメソッドの用途は以下の使い道に限定すべきである
1.ユーティリティクラスのメソッド
2.クラスの役割(型定義と雛型)に関連する状態や動作

//「2」のサンプルコード。オブジェクトの生成数を数える用途
class My() {
     private My() {}
     static int instance_num;
     static My getInstance() {
          instance_num++;
          return new My();
     }
}

■ 抽象クラスとはインスタンス化できないクラスのことである。Javaではabstract修飾子をつけてクラスを宣言すると抽象クラスになる。反意語は具象クラス。

■ 抽象クラスは何らかの具象クラスの基底クラスになる。基底クラスとしてインスタンス化の一端を担うことになる。つまり、直接のインスタンス化はできないが、雛型としての役割は依然持っているということになる

■ 抽象メソッドとは、メソッド本体を持たないメソッドのことである

■ 具象クラスからの拡張継承は推奨できない。安易な拡張継承はクラス間の依存度を高め、変更に弱いコードになるためである。

■ クラス自身およびクラスメンバは、アクセス制御の修飾子でアクセスを制御することができる。トップレベルクラスのアクセス制御は2種類ある。
1.public:グローバルスコープ(どこからでもアクセス可能)
2.なし:パッケージスコープ(パッケージ内からのみアクセス可能)

■ クラスメンバのアクセス制御
1.public:グローバルスコープ(どこからでもアクセス可能)
2.protected:プロテクティッドスコープ(そのクラス、継承クラス、およびパッケージ内の他のクラスからアクセス可能)
3.なし:パッケージスコープ(そのクラスおよびパッケージクラス内の他クラスからアクセス可能)
4.private:プライベートスコープ(そのクラスのみアクセス可能)

クラスの実装詳細を外部から隠すことを「カプセル化」と呼ぶ

■ クラスの中に定義したクラスを「ネストしたクラス」や「メンバクラス」と呼ぶ

// ネストしたクラス
public class MyHost { // エンクロージングクラス
     private static class MyHelper{ // ネストしたクラス
     }
}


■ ネストしたクラスには以下の4種類がある
1.staticなネストしたクラス
2.非staticなネストしたクラス(内部クラス)
3.ローカル内部クラス
4.匿名クラス

■ ネストしたクラスの用途
1.エンクロージングクラス内部だけでオブジェクトを使う場合
2.ネストしたクラスの実装をエンクロージングクラス内に隠蔽したい場合
3.トップレベルクラスの名前を節約する場合(非推奨)