Java-5 ?>

Java-5

オブジェクト指向

ここではJavaのオブジェクト指向について詳しく解説していきます。プログラミングをする上での醍醐味や楽しさも詰まったJava言語の重要な機能なので、しっかりマスターしていきましょう。

オブジェクト指向は難しい?

ネットでオブジェクト指向を検索してみると様々な解釈とその解釈に基づいた例題が取り上げられています。オブジェクト指向を捉える視点によっては、考え方や見方も若干異なってくるので、少し混乱する部分もあるかも知れません。しかし、それはオブジェクト指向の全体像が見えないままに、個別の解説を理解しようとする過程で起こるものです。まずはオブジェクト指向の全体像を把握して、そこから色々な考え方に触れていくというのがスムーズな学習だと考えています。

オブジェクト指向には、さまざまな「発想」「着眼点」「テクニック」そして「関連する文法」が含まれていて、それらは相互に、密接に関係しています。繰り返し学習するうちに、最初は理解が難しかった箇所も2回、3回と復習を繰り返す過程で、相互に関連している部分の理解が進み、それぞれがどのような繋がりでオブジェクト指向を表現しているのかが理解できるようになってくるかと思います。

オブジェクト指向の概要を確認しよう

オブジェクト思考の全体像は以下のリンク先で纏めています。
http://i-b-c.jp/java/oop-supplement – オブジェクト指向補足

オブジェクト指向の3大要素について

オブジェクト指向では「クラス」「ポリモーフィズム」「継承」という、構造化言語にはなかった仕組みが用意されています。この3つの特徴的な仕組みはオブジェクト指向の3大要素と呼ばれたりします。これらの仕組みは難しい言葉のように思いますが、簡単な言葉で本質を説明すると、「プログラムの無駄を省いて、分かりづらかったコードを整理していく仕組み」です。

オブジェクト指向補足の章でも説明しましたが、オブジェクト指向はプログラミングの歴史の中で必然性を持って生まれてきたもので、前世代プログラミング言語では解決出来なかった課題を解決することが出来るようになりました。その特徴的な仕組みとして、これから学習する3大要素があります。技術的な仕組みだけを理解するのではなく、その技術の背景にあることも理解しながら学習を進めることで、難しい技術の習得も後押しされてくると思います。

クラス

クラスは「サブルーチンをまとめる」「クラスの内部で使う変数やサブルーチンを隠す」「1つのクラスからインスタンスをたくさん作る」という3つの特徴的な仕組みがあります。Java基礎編では何気なく使っていたクラスですが、実はオブジェクト指向の特徴的な仕組みのひとつとなっています。

クラスの特徴1 サブルーチンをまとめる

以下のような四則演算を行うサブルーチンが複数存在した場合、Javaなどのオブジェクト指向言語では、その役割を適切に分けて、ひとつのクラスとします。

クラスの特徴でまず理解することは、このように変数とサブルーチンを纏めることが出来るということです。これによって、規模の大きなシステムの開発では、処理の役割ごとにクラスを定義して、複雑化しやすいロジックをきれいに整理していくことが可能になります。

企業の基幹システムなどでは、アプリケーションの規模も大きくなるので、ソースコードは行数として考えただけで数十万行から数百万行あたりになります。それほどのソースコードを一つのファイルで管理するよりも、目的と役割で分けて、1クラスあたり数千行に分割した単位で管理するようにしたほうがソースコードの可読性もあがり、保守しやすいプログラムとなります。

クラスの特徴2 クラスの内部で使う変数やサブルーチンを隠す

二つ目は隠すという仕組みです。纏める仕組みで、変数とサブルーチンをひとつのクラスにまとめていますが、この状態ではクラスの外側から変数にアクセス出来てしまいます。基本的にクラス内部で使う変数はクラスのメソッドからは呼び出しますが、外側から呼び出す必要性はないため、明示的に変数を隠すことが出来ます。これによって、クラス変数に想定外の値が入り、プログラムの不具合が発生した場合も、使用範囲が限定されていることが明確であるため、修正箇所の特定も容易になります。大規模なアプリケーション開発ではこの仕組みも大きな役割を担っています。
以下に隠す仕組みを使ったコードを示します。

最初に示したサンプルコードとの違いは、インスタンス変数を宣言している部分の先頭に「private」とつけているだけです。これが変数を隠す仕組みです。逆にメソッドの方には、外部から呼び出されることを明示的に示すために「public」をメソッドの宣言部に付加しています。逆に、外部から呼び出したくないメソッドの場合(例えば演算クラス内部のみで使用する共通処理を纏めたメソッドなど)は、インスタンス変数と同様に、メソッド宣言部に「private」をつけるとクラス内部だけで呼び出される「プライベートメソッド」になります。

課題:クラスの仕組みを使ってみよう

ここまでに、クラスの特徴である「纏める仕組み、隠す仕組み」について説明しました。実際にクラスを作りながら、それぞれの仕組み理解しましょう。
Java5のプロジェクトにRenshu1.javaを作成して、以下のプログラムを実装してください。

次にクラスを利用する側のテスタークラスを作成します。Java5のプロジェクトにRenshu1Tester.javaを作成して、以下のプログラムを実装してください。

作成したクラスを実行して、動作を確認してください。以下のような結果が表示されると思います。

スクリーンショット 2017-03-24 午前11.15.25

このクラスでは簡単な四則演算を実行する処理をそれぞれのメソッドで管理し、ひとつのクラスとして纏めました。また、内部で使用する変数には隠す仕組みを利用して、外部からのアクセスを制限しています。テスタークラスでは内部変数に値をセットする方法として、セッターメソッドを利用しています。クラスの内部変数をprivateで定義して隠し、外部からのアクセスにはgetter(ゲッター)、setter(セッター)を使用することをカプセル化と呼びます。下記の参考サイトなども確認しておきましょう。

オブジェクト指向設計 getter, setterを使うなとはどういうことか
http://qiita.com/Yahagi_pg/items/1bf59fc75d7f17c3b731
課題:出力メッセージを修正してみよう

どの演算結果を出力したのかを表示するように出力メッセージを修正しましょう。それぞれのメソッド内で出力メッセージを修正してもいいのですが、共通処理を纏めたプライベートメソッドを追加する形で修正していきます。
出力メッセージを変更する為に、プライベートメソッドを追加していきます。Renshu1.javaのクラスを以下のように修正してください。

このように「共通部分は纏めて、表現の異なる部分は引数として受け取り処理を行う」というやり方は、プログラミングの基本的な部分になります。同じ処理は出来るだけ纏めておきましょう。
クラスの呼び出し側であるテスタークラスは修正していませんが、実行結果は以下のように変化します。
スクリーンショット 2017-03-24 午前11.52.54

クラスの特徴3 たくさん作る

もう1つの仕組みがたくさん作るです。まずは、クラスの仕組みを利用しないサンプルコードをみてみましょう。二人分の銀行口座データを扱うプログラムです。

このプログラムでは二人分のデータ管理なので、そこまで不便を感じません。しかし、このプログラムで扱うデータの個数が数千、数十万の口座データを扱うようになってきた場合、それぞれに変数名を定義して処理を実装することは現実的ではありません。

Javaではクラスを1つの設計図と見立てて、その設計図を元に沢山のインスタンス(ここでは口座データ)を生成する仕組みがあります。それがたくさん作る仕組みです。

課題:クラスからインスタンスを生成してみよう

Java5のプロジェクトにRenshu2.javaRenshu2Tester.javaを作成して、下記のコードを実装してください。

作成したプログラムを実行して、100人分のデータが出力されることを確認してください。

スクリーンショット 2017-03-24 午後0.51.12

このように、一度クラスで定義しておいた銀行口座の設計情報があれば、呼び出し側ではインスタンスの生成を行うことによって簡単に作ることができます。
new クラス名();
でクラスのインスタンスを作成します。

スクリーンショット 2017-03-24 午後1.04.17

インスタンスを生成する仕組みとは、具体的に表現すると、クラスで定義したインスタンス変数が確保されるメモリ領域です。
またメソッドを呼び出すには、クラスの実態を格納したインスタンス変数にピリオドをつけて、扱うインスタンス変数を特定した上で、メソッド名を指定します。
インスタンスを格納する変数名.メソッド名();

スクリーンショット 2017-03-24 午後1.05.52
クラスを定義してインスタンスを生成するという概念がないこれまでのプログラミング言語では、配列などを使って必要な数だけ変数領域を用意する必要があり、それを処理するサブルーチンのロジックも複雑なものになっていました。一般的にアプリケーションでは同種の情報を複数同時に扱う場合がよくあるため、この仕組は非常に強力です。纏める仕組みを利用して、いったんクラスとして定義すると、実行時にそこからいくつでもインスタンスを作ることができます。これによって、ファイル、文字列、顧客情報など、同種の情報を複数同時に扱う処理であっても、そのクラス内部のロジックをシンプルにすることが可能となります。

ポリモーフィズム

ポリモーフィズムを一言で表現すると、「呼び出す側を共通化する仕組み」ということです。ポリモーフィズムを日本語に直訳すると「多態性」「多相性」になり、「いろいろな形に変わる」という意味を持ちます。単純に共通メインルーチンを作るしくみだと認識していれば、理解がし易いかと思います。共通サブルーチンは呼び出される側のロジックを1つにまとめますが、ポリモーフィズムは反対に、呼び出す側のロジックを一本化します。
サンプルコードをみてみましょう。
商品を扱うクラス(ここではインターフェース)を用意し、商品を登録する処理を実装するプログラムを想定します。

インターフェースの定義には interface キーワードを指定します。

次に、商品インターフェースを実装したBookクラスを実装していきます。

Bookクラスでは、「商品名」「価格」「ISBN」のフィールドがあり、登録メソッドではこれらのフィールド値を登録します。実際のデータ登録処理ではDB接続など高度な処理を実装しますが、概念の説明のため、単純にコンソールへの文字出力をしています。

次にClothesクラスを作成します。ここではBookクラス同様な「商品名」「価格」と「サイズ」のフィールドがあり、登録メソッドではこれらのフィールド値を登録します。

※ポリモーフィズムでは呼び出されるメソッドの引数や戻り値の形式を統一する必要があります。

課題:ポリモーフィズムの仕組みを利用してみよう

Java5のプロジェクトにSaveProduct.javaを作成して、下記のコードを実装してください。

以下のような出力結果が得られます。

スクリーンショット 2017-03-26 午後3.20.23

このSaveDataクラスはポリモーフィズムの仕組みを利用しています。まず、6行目のリストの型にはインターフェースとして定義したProductがクラスの型として使用されています。このようにインターフェースはクラスと同様に型として扱う事ができます。

7行目と8行目ではProductインターフェースの型で定義したリスト変数に、Bookクラスのインスタンス、Clothesクラスのインスタンスを追加しています。通常、クラスは同じクラスの型で定義された変数にしか値の代入は出来ません。しかし、ポリモーフィズムではこのように、インターフェースを実装したクラス、もしくはスーパークラスを継承したサブクラスであれば、それぞれのクラスを「ざっくりと捉えて」ひとつの同じ「規格」だと認識することが出来ます。
このポリモーフィズムの仕組みがあるため、Productインターフェースを実装したクラスであるBookクラスとClothesクラスのインスタンスは同じ規格として認識され、Productインターフェースの型で定義したリスト変数へ値を追加することが出来ています。

ややこしく考えずに、とりあえず、ポリモーフィズムとはこのように「ざっくりと捉えて同じように扱うことが出来る仕組み」だと覚えておきましょう。

このようにすることでクラスごとに処理を分けずに同じ繰り返し処理の中にひとつの型として書くことが出来ます。そしてProductインターフェースに定義されている共通のメソッド「データ登録」処理を呼び出しています。ざっくりと捉えて、あたかも同じような処理を命令していますが、実際のメソッドの振る舞いは格納されているインスタンスが属するクラスで実装されたメソッドの内容が実行されます。
このサンプルコードではBookクラスとClothesクラスのインスタンスをリスト変数に追加しているので、それぞれのクラスで定義したメソッドが実行されます。実際に動作を確かめてみて下さい。

Javaのコレクションクラスについて
http://qiita.com/NoriakiOshita/items/49294a0a8b13ef86c94e

継承

クラス定義の重複を排除するのが継承です。別の表現をすると、「クラスの共通部分を別クラスにまとめる仕組み」です。この継承の仕組みがあるので、変数とメソッドをまとめた共通クラスを作り、別のクラスからその定義をまるごと受け継ぐことが可能になります。

継承を使う場合、共通に使いたいメソッドとインスタンス変数は共通クラスに定義し、利用したいクラスはその共通クラスを「継承すること」を宣言します。これにより共通クラスの定義内容がそのまま使えるようになります。オブジェクト指向プログラミングでは、この共通クラスのことをスーパークラスと呼び、それを利用するクラスのことをサブクラスと呼びます。

サンプルコードで継承の動きを確かめてみましょう。
ここでは仮に、日本人クラスとアメリカ人クラスを用意して、それらは人間クラスを継承していると考えていきます。

このスーパークラスには abstrat キーワードがクラス宣言の前についています。このように明示的にスーパークラスを定義する際はabstrat キーワードを付加します。abstrat キーワードを付加することによって、このスーパークラスはnew 演算子からインスタンスを生成できないようになります。

Humanクラスでは人間の共通動作である「歩く」という振る舞いを具体的な内容として walk(); メソッド内に記述していますが、「話す」という国ごとに言語が異なり、違う振る舞いをさせたい talk(); メソッドは抽象的に定義しています。抽象メソッドであることを宣言するabstrat キーワードを付与することによって、通常のメソッドとは違い、{ } (中括弧)ではなく、;(セミコロン)で文を終了することが可能となります。

では次に、このHumanクラスを継承した日本人クラスとアメリカ人クラスをみていきましょう。

継承元のクラスにはabstrat キーワードをつけましたが、継承先のクラスにはextends キーワードをつけています。このextends キーワードの後には、継承元となるスーパークラスの名前を続けて記述します。Javaでは多重継承が禁止されているため、このextends キーワードの後には必ず一つのクラス名しか指定することが出来ません。
日本人クラスとアメリカ人クラスでは、スーパークラスで抽象的に定義されていた talk(); メソッドの内容をそれぞれの言語に適した形で実装しています。このように抽象的に捉えていたスーパークラスのメソッド定義は、これを継承したサブクラスで、それぞれのクラスに適した内容を具体的に実装していきます。

課題:継承の仕組みを利用してみよう

Java5のプロジェクトにHumanBehavior.javaを作成して、下記のコードを実装してください。

以下のような出力結果が得られます。

スクリーンショット 2017-03-26 午後3.34.47

このHumanBehaviorクラスでは、日本人クラスのインスタンスとアメリカ人クラスのインスタンスをそれぞれ生成しています。実行するとわかりますが、それぞれのクラスには実装されていないはずの、walk(); メソッドを呼び出すことが出来ています。このように、サブクラスでは、何も実装する必要なく、スーパークラスで定義して実装されているメソッドを使用する事ができます。

さらに、サブクラスでオーバーライドしたtalk(); メソッドに関しては、それぞれのクラスで実装した内容で実行されます。日本人クラスのインスタンスから呼び出した場合は「日本語」で、アメリカ人クラスのインスタンスから呼び出した場合は「英語」で、という振る舞いをしています。

Onepoint

ポリモーフィズムで少し紹介したインターフェースをスーパークラスとして定義した場合は、多重継承が可能です。多重継承が可能になる理由としては、インターフェースはメソッドを実装できないからです。abstratキーワードをつけた抽象クラスであっても、スーパークラスはあくまでもクラスなので、abstratキーワードがないメソッドにはコードを実装する事が可能です。その為、インターフェースではないスーパークラスの多重継承は禁止されています。

演習問題

Enshu.javaクラスを作成して、以下のコードを実装してください。

問題1(クラスの基本:カプセル化)

IbcStudent.javaクラスを作成して、以下のコードを実装してください。

次に実装したIbcStudent.javaクラスのメンバ変数他のクラスからアクセス出来ないように隠して下さい。(カプセル化)

カプセル化出来たら、以下の要件を実現するコードをEnshu.javaのプライベートメソッドkaito1()に実装してください。

  • IbcStudent クラスのインスタンスを作成する
  • setName メソッドで名前を設定する
  • showProfile メソッドで名前を表示する

 

問題2(メンバ変数の追加)

前問のIbcStudentクラスに、次の内容を追加して下さい。

  • 年齢を保持する int 型のメンバー変数
  • 年齢を設定するメンバーメソッド setAge
  • showProfile メソッドで名前、年齢を表示するようにする

メンバ変数の追加が出来たら、以下の要件を実現するコードをEnshu.javaのプライベートメソッドkaito2()に実装してください。

  • IbcStudent クラスのインスタンスを作成する
  • 名前、年齢を設定する
  • プロフィールを表示する

 

問題3(クラスの継承:動物クラスと猫クラス)

Animal.javaクラスを作成して、以下のコードを実装してください。

Animalクラスを継承したサブクラス、Cat.java を作成して以下の要件を満たすメソッドを実装してください。

  • Animal クラスの抽象メソッド cry() を継承して実装する
  • cry() を実行すると“にゃあ~という文字列を表示する

Catクラスの実装が出来たら、以下の要件を実現するコードをEnshu.javaのプライベートメソッドkaito3()に実装してください。

  • Cat クラスのインスタンスを作成する
  • public のメンバー変数、name、age に名前、年齢を設定する
  • showProfile() を実行する
  • cry() を実行する

 

問題4(クラスの継承:動物クラスと犬クラス)

Animalクラスを継承したサブクラス、Dog.java を作成して以下の要件を満たすメソッドを実装してください。

  • Animal クラスの抽象メソッド cry() を継承して実装する
  • cry() を実行すると“ワンワンという文字列を表示する

Dogクラスの実装が出来たら、以下の要件を実現するコードをEnshu.javaのプライベートメソッドkaito4()に実装してください。

  • Dog クラスのインスタンスを作成する
  • public のメンバー変数、name、age に名前、年齢を設定する
  • showProfile() を実行する
  • cry() を実行する

 

問題5

ポリモーフィズムの仕組みを利用して、次のプログラムをEnshu.javaのプライベートメソッドkaito5()に実装してください。

  • Animal クラスのリスト変数を作成する
  • リスト変数に Cat クラスのインスタンスと Dog クラスのインスタンスをそれぞれ追加する
  • リスト変数を拡張for文でループさせ、全ての要素の cry() メソッドを実行する

解答:a-5