(第1部)第1章
- 外側{}はクラスブロック、内側{}はメソッドブロック
- クラス名(大文字で始める)がファイルの名前になる
- 名は体を表す:やらせたい事を体現するクラス名をつけてからコーディングを始める
- インデントとコメント(2種類ある)で可読性の高いソースコードに
- 変数の実体はメモリ区画。変数に値を割り当てるとメモリに値が書き込まれる
- 変数を宣言する時には、型と変数名の2つを指定。値を加えて「初期化」もできる
- 予約語(50くらいある)は変数名に使えない
- 基本的なデータ型は9つ覚えておけば良い。long, int, short, byte; double, float; boolean; char, String;
- 整数を割り当てるときは基本的に処理が高速になる int型を利用する
- 特別な事情がない限り float型ではなく double型を利用する
- お金の計算など、厳密な計算が必要なときは誤差が生じる double や float を使ってはいけない
- 一文字型 char では単一引用符「'」を利用、文字列型 String では二重引用符は「"」を利用
- 変数宣言の先頭に final を付けると定数化できて誤割り当てを回避できる。定数は慣例的に大文字で表記。税金など、変わることのないものは積極的に定数化させる
第2章
- +演算子は左から評価されていく。=演算子は右から評価されていく
- =(割り当て演算子)は最後に評価される
- x に 3 を加えたい場合「x += 3」と書ける。あるいは「x = x + 3」
- 「a++」は、a を 1 増やす。オペラント(演算子以外)が1つ → 単項演算子
- ++ や -- は単独で使う
- Java は、それが妥当であれば自動的に型変換をしてくれる。妥当ではないとエラー
- 異なる型で演算を行なった場合、意味的に大きな型に統一されてから演算される
- オペラントの片方が String(文字列)の場合は他方も String として連結される
- 「int age = (int) 3.2;」で強制的に型変換(キャスト)できる。データが破損するので極力使わない
- Java は、型をきっちり揃えてから処理する
第3章
- 全てのプログラムは、順次・分岐・繰り返しの3つの組み合わせで作成できる
- これはつまり、日常生活で起こる現象を全てプログラムできることを意味している
- ブロック内で用意した変数は、そのブロックが終わると自動的に消滅する(変数のスコープ)
- ==, !=, <, >, <=, >=。これらは「if, if-else, while」などの条件式で利用する関係演算子
- 文字列比較を行うときは必ず「STR.equals("TEXT")」を利用する。そうしないと不具合が発生
- 「x は 10 より大きく、20 より小さい」は「10 < x < 20」ではなく「10 < x && x < 20」と表記
- switch文:変数を繰り返し比較したい && 一致するかを比較したい && 小数や真偽値を使ってない
- switch文は上から順次、評価していく(一致すると break で抜ける)
- Java は賢いので条件が確定した時点で短絡評価を行う(無駄な評価はしない)
- while は処理前評価、do-while は処理後評価(最低1回は処理される)
- 回数指定で繰り返す場合は for を利用した方がスマート
- for内のループ変数(例えば「i」)は、for文を抜けると自動的に消滅。積極的に使う
- break はループ処理自体を中断。continue は指定の周を飛ばす
- 無限ループの作成:while(true){処理} あるいは for(;;){処理}
第4章
- 「配列」は変数をより便利に使うための仕組み。一度に多くの変数を処理できる
- 私たちの生活においても、複数のデータを1組のものとして扱うことは多い
- 配列以外にも、マップやスタックといったデータ構造もある
- 1つの配列に入れることができるのは同じ型のデータだけ。整数と文字列を混ぜることはできない
- 変数は初期化する必要があるが、配列は自動的に初期化される(整数:0, 文字列:null)
- 配列では int[], double[], String[] といった独自の型が用意されている。[] は箱をイメージ
- String[] fruits = { "apple", "grape", "banana", "melon" }:このように書ける。fruits[3] などでアクセス
- 存在しない値にアクセスしようとすると、コンパイルはできるがエラーメッセージ(例外)が発生
- 効率よく配列内にアクセスするために拡張for が利用できる。for ( String name : fruits )
- 配列をセットすると、その配列には先頭の値のメモリアドレスが「代表として」割り当てられる
- 上記のように具体的なデータではなく参照するメモリ番地が割り当てられる変数は「参照型」と呼ばれる
- int[] hoge = null のようにして配列の「参照を切る」と、参照されなくなったデータはメモリ上のゴミとなる
- 上記のようなメモリ上のゴミを自動的に回収してくれるのがガーベッジ・コレクション機能。Java は最高
- 参照が切れている配列の値にアクセスしようとすると、エラーメッセージ(例外)が発生
- int[][] hoge = { { 1, 2, 3 }, { 6, 7, 8 } }:2次元配列
第5章
- 「メソッド」とは、コードを部品化するための仕組み。部品の最小単位
- 機能単位でメソッドに分割することでプログラム全体の把握が楽になる
- コードを部品化できるおかげで修正が楽、再利用が可能、といった大きなメリット
- プログラムは必ず mainメソッドから処理される
- メソッドに渡す値のことを「実引数」、メソッドから受け取る変数のことを「仮引数」と呼ぶ
- ローカル変数は基本的にそれが利用されるメソッド内だけで有効
- return文は値を返すだけでなく、メソッドの終了も行う
- シグニチャ(メソッド名・引数の個数・型・並び順)が異なっていれば同じメソッド名を複数定義(オーバーロード)できる。メソッドはしっかり分けるべきなので不推奨
- メソッド呼び出し元の変数xと、メソッドに値渡しされる変数xは全く別の存在(基本型の場合)
- ただし、配列などの参照型で渡されるのは配列そのものではなくメモリアドレスなのでメソッド呼び出し先での変更がメソッド呼び出し元にも影響を与える
- 戻り値の場合も、参照型変数ではデータそのものではなく配列の先頭要素のメモリアドレスが返される
- プログラム起動時にクラス名と一緒に指定された引数がある場合、それはmainメソッドに渡される
- public int add(int x, int y) {... は public 出口 add(入り口, 入り口) みたいな感じ:メソッドとは「製造マシン」
第6章
- Java では、プログラムを複数のファイル(クラス)に部品化して開発を進めていく
- Java で開発されたプログラムは基本的に「複数のクラスファイルの集まり」。javaコマンドで起動
- JVM は起動時に指定されたクラスの中にある mainメソッドを起点に実行を始める
- ゆえに、どのクラスの中に mainメソッドがあるのかを知っていないと起動できない
- JAR(Java ARchive):複数のクラスファイルを1つにまとめるファイル形式
- 各クラスをさらにグループ化するための仕組みが「パッケージ」
- 「パッケージ > クラス > メソッド」と、Java には部品化の仕組みがいくつも用意されている
- パッケージ名にはドットで区切った名前もよく利用される。「java.util.Scanner」など
- 最初のクラスファイルは自動的に「デフォルトパッケージ」に属する
- 別パッケージのクラスを利用するためには「完全限定クラス名(FQCN)」で指定する必要がある
- パッケージの仕組みがあるので世界中で作成されたクラスファイルを一意に指定することができる
- 正式なプログラムを世界中に向けて発信するときは、保有ドメインを利用してパッケージ名を決める
- 利用できる名前の総量は「名前空間」と呼ばれる。FQCN があるお陰で自由に名前を付けられる
- import文を利用すれば、長いパッケージ名のパッケージとクラス部分の入力を簡略化することができる
- 例えば、「java.util.Array.sort」は「java.utilパッケージの Arrayクラスにある sortメソッド」を呼び出している
- 他のプログラミング言語と違って、Java では全てのクラスファイルをいつでも自由に使える
- 自分で作成したクラスは1つであっても、そのクラスが実行されるときには数百の標準クラスファイル(API)が連携してくれて実行される
- Java ではプログラマを支えるための API として約200以上のパッケージ、3,500以上のクラスが標準で搭載されている
- これら API は、数学やプログラミングの専門家たちが作ったものなので自作するよりも高速でバグがない
- 開発者はこれら天才たちが作ったレバレッジを上手に利用しながらプログラミング開発していく
- API を沢山覚えれば覚えるほど天才たちのレバレッジを活用できる
- Java の開発では世界中の天才たちが作ったクラスを組み合わせて効率よくプログラムを作っていける
- java.langパッケージは利用頻度がとても多いので import文なしに自動的にインポートされる
- APIリファレンスを把握する必要がある。パッケージ名 → クラス名 → クラスの詳細 という手順で調べる
- ただし、80:20の法則があるので良書やポケットリファレンスを利用して利用頻度の高いものから重点的に暗記した方が良いだろう
- JVM はクラスローダにクラスファイルを指定して必要なクラスファイルを随時読み込んでいく
- クラスローダは部品と部品を結びつけるオブジェクト指向ならではの存在
- クラスパスを適切に指定していないとクラスローダがファイルを見つけられないので実行できない
- 現在のクラスパスを基準として、パッケージ階層に対応したフォルダ階層を作って配置する必要がある
- 実行するには、mainメソッドが含まれるクラスの完全限定ファイル名を指定して javaコマンドを起動する
(第2部)第7章:オブジェクト指向
- 本格的にプログラミングを始めると、いずれコードが長くなりすぎて人間には把握できない瞬間がやってくる
- つまり、大規模なプログラム開発では人間がボトルネックになっていた
- これが1970年代辺りからずっとプログラマたちを悩ませ続けてきた大きな課題だった
- しかし、オブジェクト指向プログラミングが開発されたので大規模な開発もラクにできるようになった
- オブジェクト指向とは、ソフトウェアを開発するときに使う部品化の考え方のこと
- オブジェクト指向のプログラミングによって「人間の頭が追いつかない状況」を避けることができる
- オブジェクト指向は「一度マスターしてしまえば二度と手放せなくなる便利な一生モノの技術」
- オブジェクト指向を使うと、ラクして、楽しく、良いシステムを作れる。まるで絵でも描くかのようにして
- そもそもシステム開発とは「人がやってきたことを機械にやらせて人がラクをするためのもの」
- つまり、現実世界と無関係に単独で存在しているようなコンピュータプログラムはない
- オブジェクト指向での開発とは、現実世界(あるいは自分の分身)を JVM といった仮想世界に忠実に反映させていく作業
- つまり、データベース設計と同じように「パラレルワールドの創造」と言える
- オブジェクト指向開発において、現実世界の的確な把握と仮想世界への再現力が非常に重要になってくる
- オブジェクト指向開発では、設計図の中の登場人物やモノを1つの部品(クラス)と捉え、それを仮想世界で構築していく
- 現実世界に出てくる登場人物の単位で、プログラムをクラスに分割する
- 開発時に作られた「クラス」はプログラムとして実行されると JVM の中にその存在が生み出される
- プログラマのやることは、現実世界をそっくり真似た「Java仮想世界」の創造といえる。新世界の神になる
- 手続き型プログラミングでは、コンピュータが一行一行、何を実行すべきかという手順を考える
- オブジェクト指向では、オブジェクトをどう作るか、どのように連携させるかを第一に開発する
- オブジェクト指向では、私たちが現実世界で普段しているような枠組みや認識を使って開発する
- 現実をお手本に、それをマネして作ればいい。現実を的確に把握して、それを JVM に落とし込めばいい
- 現実世界の登場人物やモノの振る舞いを、Java仮想世界でも再現させる
- 現実世界のオブジェクトにも「行動責任」や「情報保持責任」、「属性」や「操作」がある
- Java仮想世界の中に作られたオブジェクトたちは、それぞれ影響を及ぼしあう
- オブジェクト指向3大機能:開発者がより便利に、より安全に現実世界を模倣できるようにする
- 車輪の再発明を避ける:いかに自分で部品を作るかではなく、天才たちによって既に作られた部品をいかに使いこなせるかがポイント
第8章:クラスとインスタンス
- 各オブジェクトが負うべき仕事を考え「属性(フィールド)」「操作(メソッド)」の種類と内容を考える
- オブジェクト指向では、登場人物の「動き」を定義するためにメソッドを使う
- それから、各オブジェクトを仮想世界に生み出し動かす
- Java では、オブジェクトそのものを直接定義することはできない。代わりに設計図であるクラスを定義する
- 設計図(クラス)さえあれば、好きなだけオブジェクト(インスタンス)を生成できるし、修正や保守も楽になる
- クラスとは、プラモデルの「金型」とも言える。好きなだけ量産できる
- クラス(設計図)とインスタンス(実体)が「オブジェクト」と呼ばれることもある。それだけ重ねて認識されている
- Java仮想世界にインスタンスを創造しても「天の声(mainメソッド)」がなければ被造物は動き出さない
- ゆえに、プログラムを作るときは「神様のクラス」と「登場人物のクラス」の2種類を作る必要がある
- クラス図:クラス名(頭文字が大文字)、フィールド(小文字の名詞)、メソッド(小文字の動詞)
- クラスブロッック内に宣言された属性を「フィールド」と呼ぶ。RDBMS のフィールド(項目)みたいなものか
- フィールド宣言の先頭に final をつけると「定数フィールド(大文字表記)」となり値を上書きできなくなる
- 自分自身のフィールドを使う場合は this. に続けてフィールド名を指定する(this は省略しない)
- クラスを定義する:「属性」「操作」の一覧をクラス図にまとめる → それを「フィールド」と「メソッド」として記述
第9章:さまざまなクラス
- インスタンスとはヒープ領域に確保されたメモリ空間
- クラス型の変数を用意すると、JVM は広いヒープ領域の中から空きメモリ領域を確保してくれる
- Hero h = new Hero(); において、h には勇者情報インスタンスの先頭番地が割り当てられる
- JVM は h に格納されたインスタンスのメモリ住所を参照してから処理に取り掛かる(アドレス解決)
- ゆえに、インスタンスのメモリ住所が格納された変数をコピーすると実体ではなくただ参照先がコピーされる
- つまりクラス型は配列型と同じ「参照型」。実際のデータが全て格納されるわけではない
- フィールドに値を割り当てると、インスタンスのメモリ領域が参照されて、そこに値が書き込まれる
- 同じクラス(設計図)から生まれても、異なるインスタンスであれば互いに影響がない(独立性)
- インスタンスを生み出す方法は new しかない。new の数だけインスタンスが生み出される
- フィールドに別途作ってあるクラス型の変数を宣言すると「has-a の関係」となり「そのクラスが別のクラスを持っている」関係となる
- クラス型は別のクラスファイル内にて、メソッドの引数や返り値として利用できる(魔法使いが勇者を回復させたりできる)
- ちなみに String型も実はクラス型。実際のメモリ内では、実体が格納されているアドレスを参照している
- String を new する必要がないのは二重引用符で文字列を囲めばインスタンスを生成できるという特別ルールがあるから
- 生まれたてのインスタンスに初期値を割り当てたい場合、コンストラクタ(建築者)を記述できる
- コンストラクタが記述されていると、インスタンスが生成された直後にその記述が自動的に実行される
- コンストラクタ:メソッド名がクラス名に完全に等しい && メソッドに返り値の記述がない(void もダメ。public は良い)
- フィールドにて固定の値を初期値としてしまうこともできるが、複雑な初期化をしたいときはコンストラクタが不可欠
- コンストラクタ(メソッド)に引数を渡せば、柔軟な初期化を行うことができる
- コンストラクタを複数作成しておきたい場合は、引数の個数などを変えるなどしてオーバーロード定義できる
- Java では、全てのクラスは必ず何らかのコンストラクタを持っている必要がある
- コンストラクタが1つもない場合は、コンパイル時にデフォルトコンストラクタが自動的に追加される
- コンストラクタ内の値を使いまわしたい場合は、this(引数); で別コンストラクタを呼び出せる
- 同じクラスから生成されたインスタンスたちに特定のフィールドを共有させたい場合、そのフィールドの先頭に static を記述(静的フィールドになる)
- 静的フィールドの箱は、そのクラスに対して1つだけ用意される
- ただ、そのクラスから生成されたインスタンスたちには箱の分身が用意される
- クラスの箱と、生成されたインスタンスたちの箱(分身)は相互に繋がっている
- ゆえに「インスタンス変数名.静的フィールド名」で「クラス名.静的フィールド名」の値にアクセスできる
- 静的フィールドを定義するとインスタンスを1つも生成しなくても、その値を利用することができる
- 静的フィールドは「クラス変数」とも言われる
- 静的フィールド(static)は、変化しない定数をインスタンスたちで共有するために public や final と一緒に利用されることが多い
- static が付いているメソッドは「静的メソッド(クラスメソッド)」と呼ばれ、静的フィールドとあわせて「静的メンバ」と総称される
- 静的メソッド:それ自体がクラスに属する、インスタンスには分身が用意される、インスタンスが1つも生成されなくても利用可
- 静的メソッドは、その実体が各インスタンスではなくクラスに属し「クラス名.メソッド名();」で呼び出せるようになる
- mainメソッドは、インスタンスが1つも生成されていない状態でメソッドを処理する必要があるため static が付いている
- 静的メソッドの中に記述するコードでは、static が付いてないフィールドやメソッドは利用できない。不整合の発生を予防するため
第10章:カプセル化
- 「カプセル化」とは、フィールドへの外部書き込みやメソッドの呼び出しを制限する機能
- カプセル化によって、大切なフィールド情報への誤った書き込みを防いだり、バグの特定が容易になる
- カプセル化によってプログラムに「アクセス制御」を持たすことができる。バグの特定が容易になる
- 人間は必ずミスをする。ゆえに、人間のミスを未然に防ぐ仕組みを事前に用意しておくことが大切
- Java では、メンバ(フィールドおよびメソッド)に対してアクセス制御の設定ができる
- メンバでは、private(自分自身のクラスのみ利用可)、public(全てのクラス利用可)が利用できる
- 自分と同じパッケージのみ利用可能な package private や、子クラスでのみ利用可能な protected がある
- とりあえず上記のようにアクセス制御を行い、状況によって微調整する
- フィールドの値を取得したり書き換えたりする場合は、直接フィールド値を操作しない(バグの温床)
- フィールド値を使いたい場合は、getterメソッドや setterメソッドを用意して使う
- getterメソッド(例):public String getName() { return this.name; }
- getterメソッド(boolean型):public boolean isXxxx() { return this.value; }
- setterメソッド(例):public void setName(String name) { this.name = name; }
- 基本的にフィールドは、メソッド経由でアクセスするもの
- フィールドをカプセル化すると、将来フィールド名を変更したとしても外部への影響はない。外部からの隠蔽
- setterメソッド内で、書き換えられる値の整合性を検証したりエラーで弾いたりできる
- setterメソッドを使って、外部から絶対に不正な値を設定できない安心・安全なクラスを作る
- クラスでは、public(全てのクラスで利用可)、package private(自分と同じパッケージのみ利用可)が使える
- 非publicクラス:クラス名とファイル名を一致させる必要がない。クラスを複数宣言しても良い
- フィールドは getter/setterメソッドによって守られており外部から直接アクセスできない
- カプセル化:外部から直接触られないようにメソッドという殻(カプセル)によってフィールドを保護する
- フィールドの値は連続的に変動していく。不具合の多くはフィールドに異常値が入り込むことが原因だと言える
- ゆえに、プログラムのバグを減らすためにはメソッドよりもフィールドを保護することが重要
- フィールド値にはしっかりと private を設定し、どんどんカプセル化を利用していきましょう
- いつでも外部からフィールド値を参照できるように、getter/setterメソッドも忘れずに
- setterメソッドにて、セットされる値の整合性のチェックもしっかりする
- そもそもバグとは現実世界と仮想世界が食い違ってしまうこと
- カプセル化を使えば「不正な値が入ってしまう事」を未然に防ぎ、現実世界に即したクラスを作っていきやすい
- そのような堅固で安全なクラスを組み合わせていけば、不整合のあり得ない美しいプログラムが完成する
- ゆえに「カプセル化」こそがオブジェクト指向プログラミングの本質を支えていると言える
- カプセル化の定石:クラス = public、フィールド = private、メソッド = public
第11章:継承 - extends
- extends を使うことによって、元となるクラスの「差分」だけを記述して新たなクラスを作れる
- 継承関係:スーパークラス(親クラス:抽象的)← サブクラス(子クラス:具体的)
- Java では、複数のクラスを親として1つの子クラスを作ること(多重継承)はできない
- もちろん、親クラスで既に定義されているメソッド名を新しく定義し直すこともできる(オーバーライド)
- final が付けられれているクラスは継承に使えない
- final が付けられているメソッドは子クラスでオーバーライドできない
- 継承によって作られた子クラスは、内部に親クラスを含む二重構造になっている
- super.フィールド名、super.メソッド名() で親クラスのメンバにアクセスできる
- ちなみに祖父母クラスまで継承することはできるが、メンバへのアクセスは super を利用した親クラスまでしか届かない
- 全てのコンストラクタは、その先頭で必ず内部インスタンス(親クラス)のコンストラクタを呼び出す
- 実は、コンパイル時にコンストラクタの一行目に自動的に super(); が追加され、親クラスのコンストラクタが呼び出されている
- 親クラスと子クラスのコンストラクタの引数に食い違いがあるとインスタンスを作れない状況になることも
- 子クラスのコンストラクタの一行目で親クラスのコンストラクタへ渡す引数を定義できる。super(apple);
- 継承 is-a の原則:継承が正しく使われているかどうかは「子クラス is a 親クラス」の是非で確認できる
- is-a の原則を無視すると、将来的に現実世界との矛盾が生じたり、多態性を利用できなくなったりする
- is-a の関係で結ばれていると、親クラスになればなるほど汎化していき、子クラスになればなるほど特化していく(例:car ← bus)
- 特化していくということは定義するフィールドやメソッドがマニアックに(詳細に)なっていくということ
- 汎化していくということは定義するフィールドやメソッドが汎用的に(大雑把)なっていくということ
- 「継承」はコードの重複記述を減らすだけでなく、2つのクラスの特化・汎化の関係を JVM に伝える目的もある
- とりあえず、extends するときは is-a の原則を絶対に成り立たせる
第12章:共通部品や仕様の作り方 - abstract / interface
- 継承に備わっている機能を駆使すれば長きにわたって利用しやすい(されやすい)親クラスを作成できる
- 継承に備わっている機能とは「抽象クラス」やより上位の抽象クラスである「インターフェース」
- 抽象クラスを利用する上で大切な視点は「将来的に利用されることを想定して使いやすいように」開発しておくこと
- そもそも将来を見越して親クラスの開発をしていると幾つかの決定的な不都合が出てくる
- 詳細を決定できないメソッドが出てくる。かと言ってそのメソッドを省略すると以下2点の心配事が出てくる
- オーバーライドのし忘れに繋がる(第1の心配)
- 「何もしない」ためのメソッドと区別できない(第2の心配)
- そもそも、現実世界との不整合も起きるし、将来の利用者が誤用してしまう危険性がある
- そもそも「クラス」には2つの利用用途がある:1. インスタンス生成用クラス、2. 部品用クラス
- ゆえに、未来の開発者が間違って new してしまう危険性がある(第3の心配事)
- 抽象クラス(親)は「継承の材料」として開発されているにも関わらず、それが new されると不完全インスタンスが生まれてしまう
- 一部でも未完成部分が残っている設計図から、インスタンス(実体)を生み出してはいけない
- Java では、抽象クラスは new によるインスタンス化が禁止されている。不完全インスタンスが誕生することを未然に防止
- 抽象クラス:みんなに継承されて利用される部品専用のクラス。抽象メソッドが含まれると自動的に抽象クラス扱い
- ゆえに、宣言時に abstract を付けて抽象クラスであることをを明示しないとコンパイルエラー
- 抽象メソッド:メソッドに abstract を付けて「;」で閉じる。{} がなくても良い。
- 抽象クラス(インターフェース)を作るということは「これは部品なんだから該当箇所を必ずオーバーライドして使ってね」ということ。オーバーライドの強制
- 抽象クラス(部品)単体ではインスタンス化できない(実体になれない)。必ずオーバーライドによって具現化される必要がある
- 曖昧な設計図からは実体を創造できない
- 抽象クラスを継承した子クラスは、内部に抽象メソッドを含んでいるため、そのメソッドを自分内でオーバーライド(具体化)しないとそもそもインスタンス化できない
- オブジェクト指向における整理された部品作りの良い概念テンプレートは『入門』の p470,471 を参照
- 複数ステップの正しい継承がなされている場合、継承ツリーを上へたどっていくと次のような現象が起こる。『入門』の p474 を参照
- 継承ツリーを上へ辿るということは「どんどん汎化していく」と言うこと
- もはや「仕様書」レベルに抽象度が高くなった抽象クラスは「インターフェース」になる
- インターフェースになる基本条件:全てが抽象メソッド && (フィールドがあるなら定数のみ)
- インターフェース内のフィールドは全て public static final となり、メソッドは全て public abstract となる。これらは省略可。Java8 からはデフォルト指定ができる
- そのままの抽象クラス表記でも良いが interface に置き換えても良い。public interface ClassName { ...
- インターフェースを継承してクラスを作る場合は implements(実装)を付ける
- 抽象クラスを継承している場合は(その内部に抽象メソッドが含まれているので)インスタンス化して使うためには抽象メソッドを全てオーバーライドする必要がある
- インターフェースの使い方や概念は『入門』の p481 を参照
- インターフェースを使うと、implements する複数の子クラスたちに共通のメソッド群の実装を強制できる
- インターフェースを使うと、少なくともそのインターフェースが定めたメソッドは持っていることが保証される
- 継承にインターフェースを複数使ってもメソッドの衝突が起きないのでインターフェースを使った多重継承は認められている。『入門』の p483,484 を参照
- インターフェースを使った多重継承:public class SpecialName implements Hero, Princess, Character { ...
- ちなみに、(継承ではなくて)インターフェースを拡張するときは extends を使う
- 以上のように、抽象クラス(インターフェース)は、プログラムを部品化して使いまわしたり拡張したりするときに不整合や不具合が発生しないようにするための事前の予防策
- 抽象クラスは、複数人で大規模なプログラミングに明け暮れてきたプログラマの天才たちが編み出した苦肉の策
- オブジェクト指向プログラミングでは、実世界の的確な把握かつ、仮想世界への再現力が重要となる
- とりあえず、現実世界を的確に把握して、設計図を書いて、それからそれをインターフェース(仕様書)や抽象クラス(共通部品)に落とし込んで、親クラス → 子クラスへと実体を作り込んでいく
- 最終の子クラス(完成品)には fainal を付けるか?
- どんどん、オーバーライド(概念に肉付け)していく感じ
- とりあえず、自分の作っているクラスが「継承で使う共通部品」であるなら abstract をつける
第13章:多態性、多様性
- 多態性とは、あるものたちをザックリと1つのグループとして捉えて一括で扱うこと(でもそれぞれの動きは独自のものとなる)
- 多態性に専用の文法やキーワードなどはない
- 抽象クラスやインターフェースはインスタンスを生み出すことはできないが、その型を利用することはできる
- 単純に、親クラス型に「=」で子クラス型を割り当てると Java はその子クラスをザックリと親クラスとして扱ってくれる
- ただし、事前に extends や implements を使って継承関係で紐づけてある親クラスと子クラスにのみ、多態性が利用できる
- 当然ながら、ザックリと親クラスとして扱われる子クラスでは、親クラスが持つメソッドだけしか使えない
- しかしながら、その動きは子クラス独自に定義されたものに沿った動きとなる(多態性の真価)
- 親クラス型に割り当てられた子クラス型を、より厳密な型に戻すときはダウンキャストを使う
- しかし万が一、型が間違っていた場合に備えてダウンキャストする際には if文による instanceof のチェックを入れる
- 継承させる時にお互いをしっかりと is-a の関係にするのは、同類のインスタンスたちを十把一絡げで扱えるようにしておくため
- 十把一絡げで扱えるようにしておくと、例えばそれらを配列にまとめてループで一括処理したり、メソッドの引数に入れて一括で処理したりできる
- だから、たとえ楽ができるように思えても is-a の関係にならないのであれば継承(extends や interface)で結びつけてはいけない
- 常々、正しい継承関係を JVM に教えながら部品作りを進めることが、オブジェクト指向の真髄を最大限に利用してとても効率的なプログラミングをするコツ
- とりあえず、十把一絡げで扱いたいグループがある場合そのグループ内のインスタンスたちは共通の親クラスから extends されている必要がある
- オブジェクト指向プログラミングとは、人が世界を認識(カテゴリー/階層に分けて整理、擬人化など)しているその認識を忠実に JVM にアップロードして(パラレルワールドを創造して)マシンに扱わせようという試み
- 整理整頓が大好きで得意な人は、オブジェクト指向プログラミングに向いている
(第3部)第14章:
- ここまで学び終えた私たちは、Java の基本的な文法をほぼマスターしています
- つまり、基本文法やオブジェクト指向を駆使して作成されている JavaAPI についてもとりあえず理解できる状態
- ただ、APIリファレンスの内容や書き方は初学者にとって優しいものではない
- Java で日付を扱うときは基本的に最も利用されている「Date型」を利用する。java.util.Dateクラス
- Calendar を利用して「月」情報を取得・設定する場合は「1~12」ではなく「0~11」で指定する
- 独自に日時の書式を指定したいのであれば java.text.SimpleDateFormatクラスを利用する
- Javaは日付の取り扱いが少し苦手だった。ゆえに、Java8以降からは新しい日付API が追加された
- 全てのクラスの先祖は「Objectクラス」。Objectクラスには「equals()」と「toString()」が定義してある
- Objectクラスは全てのクラスの先祖なので「全てのクラス is-a Objectクラス」と言える。つまり、どのクラスもざっくり「Objectクラス」として扱える
- ゆえに、Object型にはどんなインスタンスも割り当てられるし、引数に Object型を指定すればどんな型でも渡せるメソッドが作れる
- さらに大元の「Objectクラス」で equals と toString が定義されているお陰で、作成したどのクラスでも常に同じ方法で内容を比較したり文字列表示できる
- toString()メソッドをオーバーライドしておくことで、簡単にインスタンス情報を確認できる。『入門』p547
- 等値 → メモリアドレスまで一緒(完全に同一の存在)。等価 → メモリアドレスは違うけど内容が一緒
- 「==」は等値かどうかを評価する。「.equals」は等価かどうかを評価する
- Objectクラスから継承される equals()メソッドはおおむね「等値なら true」になっている。ゆえに、開発者は状況に合わせてオーバーライドする必要がある
- とりあえず、新しくクラス(部品)を作るとき、toString() と equals() をオーバーライドしてカスタマイズする必要性がないか検討する
- ラッパークラス:基本データ型それぞれ(int, double, char, boolean など)に対応させて準備されているクラス
- ラッパークラスでは、その基本データ型に関する便利なメソッドが提供されている
- 例えば、Integerクラスには「parseInt() - 文字列の数字を int型に変換」や「toHexString() - 16進数に変換」などがある
- ラッパークラスはインスタント化しなくても手軽に利用できる
- 古いバージョンの Java では基本型とラッパークラスの相互変換を手動でする必要があったが、Java5 以降からは型変換は「=」で割り当てが発生した時に自動でやってくれる。『入門』p556
- Java における日付情報は基本的に java.util.Date で扱う
- 「年月日時分秒」6つの int値から Dateインスタンスを得るためには Calendarクラスを使う
- Dateインスタンスの内容を好きな書式に整形したい場合は SimpleDateFormatクラスを使う
第15章:例外
- 不具合対策こそがプログラマの腕の見せ所。あらゆる可能性を想定して事前に手を打っておくのが例外処理
- 考えられる不具合は、文法エラー、実行時エラー、論理エラー。「実行時エラー」は例外処理で対処する
- 文法エラーと論理エラーは開発者の過失。開発時にテストをしっかり行ってデバッグすれば良い
- 実行時エラーのことを「例外」と言う:パソコンのメモリ不足、ファイルが見つからない、null を利用しようとしたなど
- 従来の例外処理は、1つの処理に対して1つの例外処理を書くなど記述が冗長になりがちだった。例外処理をサボりたくなる雰囲気があった
- 従来の例外処理は、例外管理とその対応の全責任をプログラマに求めていたと言える
- この点、Java では例外管理は JVM と協働で行う。プログラマは例外が発生しそうな箇所を try-catch文で囲んで例外が発生した時の処理を書いておくことができる
- tryブロックは JVMに「この部分では例外的な状況が発生する可能性があるから検出を試みながら実行しろ」と言っている感じ
- 例外クラス:Java では、発生した例外に応じて適切な処理ができるようにそれぞれの例外的状況に応じたクラスが複数用意されている
- 例外クラスは、Throwable ← Exception ← Exception系例外(catch すべき)、Throwable ← Error ← Error系例外という継承関係になっている
- Error系例外:メモリ不足やファイル自体が壊れている、など。そもそも、このような状況をキャッチしても打つ手がないためキャッチする必要はない
- Exception系例外:ファイルに書き込みできない、ネットワークに接続できない、など。その発生を十分に想定して対処を考える必要がある。例外処理が必須
- RuntimeException系例外:いちいち想定しているとキリがない。対処するかどうかは任意
- Java では、Exception系の例外が発生しそうなコマンドを使う場合、try-catch文を書いて例外処理を書いておかないとコンパイルエラーになる。サボれない
- コンパイル時にその是非がチェックされるために、Exception系のエラーは「チェック例外」と呼ばれる
- JavaAPI のクラスには利用すると何らかなの例外を発生させる可能性があるメソッドが数多くある。利用する時はキャッチすべき例外の有無をリファレンスで確認する必要がある
- Exception系例外が発生する可能性がある場合、引数リストの後に「throw 例外クラス名」と表記される
- 例外的状況の詳細情報が詰め込まれた IOExceptionインスタンスを catch文で指定された変数 e に割り当てたりする
- catchブロック内では、この変数 e に格納された詳細情報を取り出して適切なエラー処理を行うことができる
- 例外インスタンスで共通して利用できるメソッドには「getMessage() - エラーメッセージ表示」や「printStackTrace() - トレース文の表示」などがある
- 例外が発生しても try-catch文でキャッチ並びに適切に処理されない場合、JVM はプログラムを強制終了してスタックトレース内容を画面に表示する
- catchブロックを複数記述できる。処理内容が同じ場合は catch(IOException | NullPointException e) のように記述もできる
- 例外クラスの抽象クラスを利用すれば、ざっくりと捉えた例外処理もできる。親:Exception ← 子:Exception系例外
- fw.close() のような後片付け処理は必ず実行させる必要があるので try-catch-finallyブロック内に必ず記述
- finally を使うべき状況:ファイル・データベース接続・ネットワーク接続など、後片付け処理
- Java7 以降からは、try 直後の丸カッコ内にファイルオープンやデータベース接続の処理を書いておくと、使い終わった時点で JVM が自動的に閉じてくれる
- Exception系例外(チェック例外)は基本的にtry-catch文による対処が必須だが、メソッド宣言時に throw を付けると例外処理を呼び出し元に投げることができる(自分はしなくて済む)
- 例外throw元「私はメソッド内でチェック例外が発生しても処理しませんが、throw先が処理してくれます」
- 例外throw先「このメソッドは例外を throw しているから、例外処理はこちら側でしておかないとな」
- メソッドを作るときは「チェック例外をどう処理するか」について方針を以下の2つから選ぶ必要がある
- 1. チェック例外は呼び出される側で処理し完結させておく。2. 呼び出す側で処理する
- 空の catch文は作らない(例外のもみ消し)。よほどの理由がある時はコメントに理由を残しておく
- 「throw new 例外クラス名("(エラーメッセージ)")」など、例外インスタンスを作成すれば好きな場所で意図的に例外を発生させることもできる
- 好きな場所で意図的に例外を発生させるこの throw と、親に例外処理を丸投げする throw は種類が別なので注意する
- 例外クラスも結局は抽象クラス(共有部品)の1つに変わりないので、親メソッドを extends してオリジナルの例外クラスを作成できる
- 高技術さえあれば、徹夜2週間続けての開発で500万が発想できる業界らしい
第16章・付録
- 他にも Java では、ファイルの読み書き、インターネットアクセス、データベース操作、ウィンドウアプリケーション開発、スマホアプリケーション開発、* Webアプリケーション開発ができる
- 誰もが通る道を可能な限り効率よく通り過ぎ、課題やエラーを素早く解決できるプログラマであれたら理想的
- 技術の上達のためには沢山のエラーたちに出会い、試行錯誤で解決し、必勝セオリーの引き出しを1つでも多く増やしていくことが不可欠
- エラーを素早く解決するコツ1:新しい課題やエラーに直面している時こそ、自分が最も成長している時
- コツ2:エラーメッセージをしっかり読む。意味不明でも飛ばさない
- コツ3:原因を理解した上で修正する。なぜ今回のエラーが発生したのかを消化してから次に進む
- コンパイルエラーは上から1つずつ着実に修正していく。最初のエラーが原因で2つ目のエラーが誘発されている可能性もあるから
- プログラム実行中のエラーはスタックトレースを読みながら修正する。標準API のメソッドにバグがあるとは考えずらい。自分が開発したメソッドからの呼び出しなどにバグがある可能性の方が高い
- 標準API メソッドの直後に表示されている自分で開発したファイルのエラー該当行が最も怪しむべき箇所
- エラーメッセージの原因は『入門』p644 が参考になる