この頁では、Sun Microsystems社が開発したJavaというプログラミング言語と、その特徴を理解する上で必要となる概念について解説します。
Javaとは、Sun Microsystems社(※ 2010年にOracle社に買収された)が開発したプログラミング言語で、2011年3月時点の最新バージョンは“Version 6 Update 24(内部バージョンは1.6.0.24)”です。
Javaの動作環境(Javaで作られたプログラムを実行するためだけの環境)はJRE)と呼ばれ、java.com: あなたと Javaにて公開されています。 また、Javaの開発環境(JREの他にコンパイラやアーカイバなどを含む)はJDKと呼ばれ、Java SE Downloadsから無料で入手することができます。
Javaは、元々「Greenプロジェクト」という家電機器用のプログラミング言語を開発するためのプロジェクトの中で開発されたOakというプログラミング言語に由来し、WindowsやMacintosh, Unix系などのコンピュータだけでなく、携帯電話や組み込み機器のような小型端末など、様々なプラットフォーム(動作環境)に対応しているという大きな特徴を持っています。
プラットフォームに依存しないというJavaの特徴を実現するために、Javaでは実際のプロセッサを包み込む形で仮想的なプロセッサを構築します。
これをJava仮想マシン(Java Virtual Machine:JVM)と呼びます。
また、一般にプログラムの実行方式には、ハードウェア依存の機械語に変換して実行する「コンパイラ方式」と、ソースコードを一行一行解釈することでプログラムを実行する「インタプリタ方式」がありますが、プラットフォーム毎に仮想マシンのインタプリタさえ用意しておけば、どんなプラットフォーム上でも実行可能となるため、Javaではインタプリタ方式を採用しています。
(※) インタプリタ方式は、一般に実行速度の点でコンパイラ方式より劣るため、初期のJavaは「実行速度が遅い」と言われ、敬遠されることもありましたが、現在ではインタプリタに様々な改良が加えられ、またマシンのCPUも日々向上しているので、実行速度も以前よりは上がってきています。
Javaは、以下のような様々な形態で、広くWeb上で利用されています。
(※) Javaの世界では、複数の動作環境に対応していることをクロスプラットフォームと言い、Sun Microsystems社ではこれを“Write Once, Run Anywhere(一度書けば、どこでも動く)”というフレーズで示しています。
Javaは、開発当初からインターネットでの利用を強く意識した言語設計がなされているのが大きな特徴です。 1995年、Sun Microsystems社は、Javaの公開とともに、Javaだけで実装したWebブラウザであるHotJavaというWebブラウザを発表しました。 HotJavaの最大の特徴は、HTMLに埋め込みWebブラウザ上で動かすことができるプログラムであるJavaアプレットを実行できたことでした。 この「クライアント側にダイナミックな動きを加えることができるようになった」というWebに与えた衝撃は大きく、Hot Java以外にもNetscape Navigator 2.0など、他のWebブラウザも続々とアプレットのサポートを表明しました。 現在では、HotJavaというWebブラウザはほとんど普及していませんが、Javaアプレットという機能は多くのWebブラウザがサポートしています。
ネットワーク対応という意味では、Javaは、標準パッケージであるjava.netの中にソケットなどの豊富なネットワーク機能を含んでおり、さらにHTTPに限っていえば、URL, HttpURLConnectionなどのクラスを利用することにより、ソケットすら意識せずにHTTP通信を行うこともできます。
以下は、コマンドライン引数として与えられたURIへGET, あるいはPOSTリクエストを行い、そのステータスコードとレスポンスボディを出力するサンプルプログラムです。
(HttpClientTester.java)
001: package net.studyinghttp;
002:
003: import java.io.*;
004: import java.net.*;
005:
006: /**
007: * HTTP クライアントの雛形
008: */
009: public class HttpClientTester {
010: /**
011: * HttpURLConnection オブジェクト
012: */
013: HttpURLConnection uCon;
014:
015: /**
016: * オブジェクト作成と同時にソケット作成
017: *
018: * @param url リクエストを行うURL
019: */
020: public HttpClientTester (String url)
021: throws MalformedURLException, ProtocolException, IOException {
022: // URL オブジェクトの生成
023: URL urlObject = new URL(url);
024: // HttpURLConnection オブジェクトの生成
025: uCon = (HttpURLConnection)urlObject.openConnection();
026: }
027:
028: /**
029: * GET リクエストを発行
030: */
031: public void doGet ()
032: throws ProtocolException, IOException {
033: // リクエストメソッド
034: uCon.setRequestMethod("GET");
035: // 接続・リクエスト発行
036: uCon.connect();
037: }
038:
039: /**
040: * POST リクエストを発行
041: *
042: * @param message リクエストメッセージ
043: */
044: public void doPost (String message)
045: throws ProtocolException, IOException {
046: // リクエストメソッド
047: uCon.setRequestMethod("POST");
048: // メッセージがある場合
049: setRequestMessage(message);
050: // 接続・リクエスト発行
051: uCon.connect();
052: }
053:
054: /**
055: * リクエストメッセージを設定
056: *
057: * @param message リクエストメッセージ
058: */
059: void setRequestMessage (String message) throws IOException {
060: // リクエストボディを投げられるようにする
061: uCon.setDoOutput(true);
062: // リクエストボディの MIME は application/x-www-form-urlencoded
063: uCon.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
064: // リクエストボディの長さ
065: uCon.setRequestProperty("Content-Length", Integer.toString(message.length()));
066: // リクエストメッセージを流し込む出力ストリーム
067: BufferedWriter outer = new BufferedWriter(new OutputStreamWriter(uCon.getOutputStream()));
068: outer.write(message);
069: outer.flush();
070: outer.close();
071: }
072:
073: /**
074: * ステータスコードの取得
075: *
076: * @return 3桁のステータスコード
077: */
078: public int getStatusCode () throws IOException {
079: return uCon.getResponseCode();
080: }
081:
082: /**
083: * レスポンスを受け取るためのストリームの取得
084: *
085: * @return レスポンスを受け取るためのストリーム
086: */
087: public InputStream getResponseStream () throws IOException {
088: return uCon.getInputStream();
089: }
090:
091: /**
092: * レスポンスボディを文字列として得る
093: *
094: * @return 文字列としてのレスポンスボディ
095: */
096: public String getResponseToString () throws IOException {
097: // レスポンスボディを受け取るための入力ストリーム
098: BufferedReader inner = new BufferedReader(new InputStreamReader(getResponseStream()));
099: String response = "";
100: String line = "";
101: while ((line = inner.readLine()) != null) {
102: response += line + "\n";
103: }
104: return response;
105: }
106:
107: /**
108: * 接続の切断
109: */
110: public void close () {
111: uCon.disconnect();
112: }
113:
114: public static void main(String[] args) {
115: try {
116: String myUrl = args[0];
117: HttpClientTester myClient = new HttpClientTester(myUrl);
118: String myMessage = "MESSAGE=Hello";
119: myClient.doPost(myMessage);
120: System.out.println(myClient.getStatusCode());
121: System.out.println(myClient.getResponseToString());
122: } catch (Exception e) {
123: e.printStackTrace();
124: }
125: }
126: }
Java言語の大きな特徴として、言語仕様中にマルチスレッドが組み込まれています。
スレッド{thread} とは、本来「糸」という意味ですが、この場合「プログラムの処理単位」を指します。
Javaでは、mainメソッドを実行しているスレッドをメインスレッドと呼び、メインスレッドだけを使ったプログラムを「シングルスレッドのプログラム」と呼びます。
シングルスレッドのプログラムとは、要するに「普通のプログラム」であり、mainメソッドの先頭から順に記述してあるコードを逐次的に処理していくプログラムです。
そのため、シングルスレッドのプログラムは逐次プログラムと言うこともあります。
これに対し、あるスレッド(mainスレッドを含む)から別のスレッドを作成し、それら複数のスレッドを“同時に”利用するプログラムを「マルチスレッドのプログラム」と呼びます。
マルチスレッドのプログラムは並行プログラムとも呼ばれ、“同時に”複数の処理を実行することができるので、たとえば「数値計算の処理」、「GUIの処理」、「通信の処理」……のように、機能ごとにスレッドを作成することができます。
これにより、プログラムの見通しが良くなったり、新たな機能を追加しやすいという意味で拡張性が高いという利点があります。
一方で、複数のスレッドから同時に参照されるリソース(プログラム上の変数だけでなく、ファイルや通信ソケットなどのI/Oなども含む)に関しては、プログラム上の矛盾が起きないように同期をとるなどの、並行作業を行うための処理が必要になります。
(※) 厳密に言うと、CPUが一つしかないマシンの場合、“同時に”処理を行うことはできません。 実際には、CPUの処理時間を非常に短い単位に分割し、複数のスレッドを順番に切り替えることによって、複数の処理を“同時に”行っているように見せています。
Javaでは、「Threadクラスを継承する方法」と「Runnableインターフェースを実装する方法」のどちらかでマルチスレッドプログラムを記述することができ、どちらでも同じ結果となります。
以下のプログラムは、上記の2種類の方法を用いて2種類のスレッドを作成し、それらを“同時に”処理させるプログラムです。
(ThreadTester.java)
01: package net.studyinghttp;
02:
03: public class ThreadTester {
04: // メイン関数
05: public static void main(String[] args) {
06: // インスタンスを生成
07: MyThread1 th1 = new MyThread1();
08: MyThread2 th2 = new MyThread2();
09:
10: // スレッドを開始
11: th1.startThread();
12: th2.startThread();
13: }
14:
15: }
16:
17: // Threadクラスを継承してスレッドを実現するクラス
18: class MyThread1 extends Thread {
19: // カウンタ
20: int cnt = 0;
21:
22: /**
23: * スレッド内の処理を記述
24: * @param なし
25: */
26: public void run() {
27: for(int i=0;i<10;i++) {
28: System.out.print (++cnt);
29: System.out.println(": This is MyThread1");
30:
31: // プリエンプト
32: try {
33: sleep(500);
34: } catch (Exception ex) {
35:
36: }
37: }
38: }
39: /**
40: * スレッドを開始するためのメソッド
41: * @param なし
42: */
43: public void startThread() {
44: this.start();
45: }
46:
47: }
48:
49: // Runnableインタフェースを実装してスレッドを実現するクラス
50: class MyThread2 implements Runnable {
51: // カウンタ
52: int cnt = 0;
53: // 継承しない場合はインスタンスを作っておく
54: Thread thread = new Thread(this);
55:
56: /**
57: * スレッド内の処理を記述
58: * @param なし
59: */
60: public void run() {
61: for(int i=0;i<10;i++) {
62: System.out.print (++cnt);
63: System.out.println(": This is MyThread2");
64:
65: // プリエンプト
66: try {
67: thread.sleep(500);
68: } catch (Exception ex) {
69:
70: }
71: }
72: }
73: /**
74: * スレッドを開始するためのメソッド
75: * @param なし
76: */
77: public void startThread() {
78: thread.start();
79: }
80: }
(※) スレッドの実装方法が2種類ある理由は、Javaでは多重継承ができない(既にあるクラスのサブクラスである場合は、他のクラスを継承できない)からです。
複数のスレッドを並行処理する場合の注意点は、「複数のスレッドがある共有データを別々に操作することで、その共有データが破壊されてしまう」、あるいは「ある順序に沿って呼び出すべきメソッドを複数のスレッドが別々のタイミングで呼び出すことで、プログラムが機能どおりに動作しない」といったなどがあります。
たとえば、以下のプログラムでは、2つのスレッドが共有データであるgCounterを操作しようとして、プログラムが破壊される例です。
(ThreadTester2.java)
01: package net.studyinghttp;
02:
03: public class ThreadTester2 {
04: // メイン関数
05: public static void main(String[] args) {
06: // インスタンスを生成
07: MyThread1b th1 = new MyThread1b();
08: MyThread2b th2 = new MyThread2b();
09:
10: // スレッドを開始
11: th1.startThread();
12: th2.startThread();
13: }
14:
15: // グローバルカウンタ
16: private static int gCounter = 0;
17:
18: /**
19: * 数字と文字を出力する
20: * @param thName スレッド名
21: */
22: // synchronized // このメソッドにロックをかける
23: static void printOut (String thName, int lCnt) {
24: ++gCounter;
25:
26: for (int i=0;i<5000;i++) {
27: if (i%100 ==0) {
28: System.out.print ("-");
29: }
30: }
31: System.out.println("#");
32:
33: System.out.print ("ThreadName: "+ thName);
34: System.out.print (" ("+ lCnt +"times)");
35: System.out.println(", Total :"+ gCounter +" times");
36: }
37: }
38:
39: // Threadクラスを継承してスレッドを実現するクラス
40: class MyThread1b extends Thread {
41: // カウンタ
42: int lCnt = 0;
43:
44: /**
45: * スレッド内の処理を記述
46: * @param なし
47: */
48: public void run() {
49: for(lCnt=1; lCnt<=10; lCnt++) {
50: // プリントアウト
51: ThreadTester2.printOut("MyThread1", lCnt);
52:
53: // プリエンプト
54: yield();
55: }
56: }
57: /**
58: * スレッドを開始するためのメソッド
59: * @param なし
60: */
61: public void startThread() {
62: this.start();
63: }
64:
65: }
66:
67: // Runnableインタフェースを実装してスレッドを実現するクラス
68: class MyThread2b implements Runnable {
69: // カウンタ
70: int lCnt = 0;
71: // 継承しない場合はインスタンスを作っておく
72: Thread thread = new Thread(this);
73:
74: /**
75: * スレッド内の処理を記述
76: * @param なし
77: */
78: public void run() {
79: for(lCnt=1; lCnt<=10; lCnt++) {
80: // プリントアウト
81: ThreadTester2.printOut("MyThread2", lCnt);
82:
83: // プリエンプト
84: Thread.yield();
85:
86: }
87: }
88:
89: /**
90: * スレッドを開始するためのメソッド
91: * @param なし
92: */
93: public void startThread() {
94: thread.start();
95: }
96: }
このプログラムの実行例は以下のようになります。(実行結果は毎回変わります)
-------------------------------------------------------------------------------- -------------------# ThreadName: MyThread1 (1times), Total :2 times -# ThreadName: MyThread2 (1times), Total :2 times -------------------------------------------------------------------------------- ----# ----------------# ThreadName: MyThread1 (2times), Total :4 times ThreadName: MyThread2 (2times), Total :4 times -------------------------------------------------------------------------------- ----# ----------------# ThreadName: MyThread1 (3times), Total :6 times ThreadName: MyThread2 (3times), Total :6 times -------------------------------------------------------------------------------- -------------------# -# ThreadName: MyThread2 (4times), Total :8 times ThreadName: MyThread1 (4times), Total :8 times -------------------------------------------------------------------------------- -----# ---------------# ThreadName: MyThread1 (5times), Total :10 times ThreadName: MyThread2 (5times), Total :10 times ------------------------------------------------------------------# ThreadName: MyThread1----------------- (6times), Total :12 times -----------------# --------------ThreadName: MyThread2 (6times), Total :13 times ----------------------------------------------------------------------# ThreadName: MyThread1 (7times), Total :14 times ----------------# -----------------ThreadName: MyThread2 (7times), Total :15 times --------------------------------------------------# ThreadName: MyThread1 (8times), Total :16 times --------------------------------------------------# -----------------ThreadName: MyThread2 (8times), Total :17 times ----------------# -----------------ThreadName: MyThread1 (9times), Total :18 times --------------------------------------------------# -----------------ThreadName: MyThread2 (9times), Total :19 times ----------------# -----------------ThreadName: MyThread1 (10times), Total :20 times ---------------------------------# ThreadName: MyThread2 (10times), Total :20 times
複数スレッド実行時に、単一スレッドと同じ動きを保証するプログラムをスレッドセーフなプログラムと言います。 今回の場合、一番時間のかかる(=“重い”動作である)「標準出力への文字の書き出し」が間に入っているため、スレッド間の同期がうまく取れず、プログラムが意図どおりに動作しないという結果になってしまいました。
スレッドセーフを実現するためには、アトミック性を保証する必要があります。
アトミック性とは「不可分操作」とも言い、要するに1つのスレッドが特定の処理を実行中に、他のスレッドがその処理を実行することができないという意味です。
今回のプログラムでは printOut() というメソッドは1まとまりで実行されないと表示が乱れてしまうので、この処理のアトミック性が確保されていなければなりません。
アトミック性を確保するための仕組みのには色々なものがありますが、「共有資源へのアクセスを一つのスレッドのみに限定する」という方法があり、これを相互排除(MUTEX)と言います。 相互排除は、しばしばロックをかけるとも呼ばれます。
Javaでは、メソッドかブロックに対してsynchronizedキーワードを利用することでMUTEXを実現できます。
上記のプログラムでは、22行目のコメントアウトを外すことで、以下のように意図どおりの動作をさせることができます。
--------------------------------------------------# ThreadName: MyThread1 (1times), Total :1 times --------------------------------------------------# ThreadName: MyThread2 (1times), Total :2 times --------------------------------------------------# ThreadName: MyThread1 (2times), Total :3 times --------------------------------------------------# ThreadName: MyThread2 (2times), Total :4 times --------------------------------------------------# ThreadName: MyThread1 (3times), Total :5 times --------------------------------------------------# ThreadName: MyThread2 (3times), Total :6 times --------------------------------------------------# ThreadName: MyThread1 (4times), Total :7 times --------------------------------------------------# ThreadName: MyThread2 (4times), Total :8 times --------------------------------------------------# ThreadName: MyThread1 (5times), Total :9 times --------------------------------------------------# ThreadName: MyThread2 (5times), Total :10 times --------------------------------------------------# ThreadName: MyThread1 (6times), Total :11 times --------------------------------------------------# ThreadName: MyThread2 (6times), Total :12 times --------------------------------------------------# ThreadName: MyThread1 (7times), Total :13 times --------------------------------------------------# ThreadName: MyThread2 (7times), Total :14 times --------------------------------------------------# ThreadName: MyThread1 (8times), Total :15 times --------------------------------------------------# ThreadName: MyThread2 (8times), Total :16 times --------------------------------------------------# ThreadName: MyThread1 (9times), Total :17 times --------------------------------------------------# ThreadName: MyThread2 (9times), Total :18 times --------------------------------------------------# ThreadName: MyThread1 (10times), Total :19 times --------------------------------------------------# ThreadName: MyThread2 (10times), Total :20 times
近頃では「オブジェクト指向プログラミング」という言葉がすでに広く知られていますが、これは一体どのようなプログラミングのことを指すのでしょうか? 実は、この「オブジェクト指向プログラミング(“Object Oriented Programming”を略してOOPとも呼ばれる)」というのは、具体的なコーディングテクニックを指すものではありません。 コーディングより前の、ソフトウェアの設計の段階で利用される「設計手法」です。 この考え方を利用することによって、プログラムの内容が大規模になればなるほど、設計が整理され、結果的にコード化の作業を有益にすると言われています。
元々、コンピュータは非常に高価で、またその用途もエンジニアによってのみ扱われるような非常に限定的なものばかりでした。 しかし近年は、インターネットが一般へ開放され、また様々なソフトウェアが開発されたことで、パーソナルコンピュータ(パソコン)も広く普及しました。 それに伴い、コンピュータを利用したシステムもどんどん利用されるようになって来ましたが、その反面、そのようなシステムはどんどん複雑化の一途を辿っています。 そのようなシステムを開発する際、作業は当然分担して行われることになりますが、その場合、そのシステム内の全てのデータ状態を把握することは不可能ですし、またそもそも意味がありません。 元来、プログラムとは逐次処理をするものだったのですが、現在利用されているシステムではそれよりもプログラムの体系化という要素の方がより重要になってきたのです。
つまり、「オブジェクト指向」とは人間がごくごく普通に認識している考え方、あるいは当たり前すぎて普段は認識すらしないであろう考え方を抽象化し、概念に名前をつけることで、体系化したものであり、言い換えると設計論をプログラミングに適用できるようにしたものが「オブジェクト指向」であるということができます。
オブジェクト指向技術は、Simulaと呼ばれるシミュレーション言語を起源とし、その後発表されたSmalltalk-80が、最初のオブジェクト指向プログラミング言語と言われています。 その後、この考え方が様々な言語に取り入れられることになっていきますが、Javaもオブジェクト指向プログラミング言語に対応した言語仕様になっています。
オブジェクト指向の発想の原点は、現実の「物」および「物同士の関係」に注目するところにあります。 「物」は、それぞれ独特な「性質」を持っており、決まった「振る舞い」を行うようにできています。 すなわち、オブジェクト指向とは、システムの構造をこのように現実に近い形でモデル化することにより、その構造がより直感的でわかりやすくなるという考え方なのです。 オブジェクト指向では、「物」にあたるものをオブジェクトと呼んでいます。
この「オブジェクト指向」という抽象的な概念を、プログラムという具体的な形で表現するためにはどうすればよいのでしょうか? とりあえず、次のコードをご覧下さい。 (HelloTester.java)
01: package net.studyinghttp;
02:
03: class MyClass {
04: String callMe () { return "Hello!"; }
05: int randInt = (int)(Math.random() * 10);
06: }
07: class HelloTester {
08: public static void main(String args[]) {
09: MyClass obj = new MyClass();
10: System.out.println(obj.callMe());
11: System.out.println(obj.randInt);
12: }
13: }
まず、3 〜 6 行目で MyClass という名前のクラスを作成しています。
オブジェクト指向では、「ある概念をプログラム化したもの」をクラスと呼びます。
そして、クラス内の「ある振る舞い」をメソッド、「ある性質」をフィールドと言い、メソッドとフィールドを合わせて、そのクラスのメンバと言います。
つまり、この場合 MyClass というクラスには callMe というメソッドと、randInt というフィールドが存在していることになります。
次に、7 〜 13 行目では HelloTester という別の名前のクラスを作成しています。
この中には、main というメソッドがあり、全ての Java プログラムはこの main メソッドを実行することで処理を実行します。
このあたりは、main()関数から処理が始まるC言語と同じ形式です。
9 行目では、obj という変数に HelloTester クラスを指し示すための「物」を格納しました。
この「クラスを指し示すための識別子」をオブジェクトと言います。
そして 10, 11 行目で、その obj というオブジェクトを通して、HelloTester 内に定義されたメソッド(4行目)や、フィールド(5行目)を呼び出しています。
(※) 9行目で作られたオブジェクト obj を特にインスタンスと呼ぶことがあります。
このように、obj という「オブジェクト」を中心にプログラムを設計する手法が、オブジェクト指向プログラミング(OOP)なのです。
オブジェクト指向プログラミングでは、あるクラスのメソッドやフィールドは、そのインスタンスを介してのみ扱うことができ、他のオブジェクトから直接利用できないように隔離することができます。 これをカプセル化と言います。
カプセル化を行うことによって、外部からデータを書き換えるようなことができなくなります。 外部からデータを書き換えられるようになっていた場合、例えば同じ変数名を作ってしまうと、他人がそのデータを上書きしてしまい、結果的にクラスが想定した動作を行わなくなるかもしれません。 カプセル化は、そのような余計な心配から、プログラマを解放します。
また、データの構造を隠蔽することにより、内部的なデータ構造を独自に変更したとしても、外部とのインタフェースであるメソッドさえ変更しなければ、そのクラスを使うユーザはその変更をまったく考慮する必要がありません。 この独立性により、クラスのメンテナンス性を向上させ、またそのクラスの再利用性を高めます。
オブジェクト指向プログラミングでは、あるクラスを定義する時に、他のクラスのメンバを受け継ぐことができます。 これを継承と言います。 この時、元になるクラスをスーパークラス(基底クラス)、受け継ぐ方のクラスをサブクラス(派生クラス)と言います。 スーパークラスの性質は全てサブクラスに受け継がれますので、サブクラスではスーパークラスとの違いを定義するだけでよく、振る舞いは似ているがわずかに違うようなソースコードを繰り返し記述することを避けることができ、プログラムの開発効率が高まります。
また、継承されることが前提となるようなクラスを作ることができます。
そのようなクラスを抽象クラスと言います。
抽象クラスでは、abstract というキーワードを用いることで、クラスに属するメソッドの一部(または全部)の名前のみを宣言しておくことができます。
それらメソッドは、抽象クラスをスーパークラスとするサブクラスで個別に定義することで、使用できるようになります。
例として、以下のコードをご覧下さい。
(InheritanceTester.java)
01: package net.studyinghttp;
02:
03: class InheritanceTester {
04: public static void main(String args[]) {
05: Fruit apple = new Apple();
06: System.out.println(apple.color());
07: System.out.println(apple.taste());
08:
09: Fruit banana = new Banana();
10: System.out.println(banana.color());
11: System.out.println(banana.taste());
12:
13: Fruit melon = new Melon();
14: System.out.println(melon.color());
15: System.out.println(melon.taste());
16: }
17: }
18:
19: abstract class Fruit {
20: abstract String color();
21: String taste () {
22: return "Sweet";
23: }
24: }
25:
26: class Apple extends Fruit {
27: String color() {
28: return "Red";
29: }
30: }
31: class Banana extends Fruit {
32: String color() {
33: return "Yellow";
34: }
35: }
36: class Melon extends Fruit {
37: String color() {
38: return "Green";
39: }
40: }
このプログラムで言えば、スーパークラスは Fruit、サブクラスは Apple, Banana, Melon となります。
ここで注目すべき点は、Fruit というクラス名と、color() というメソッド名に、それぞれ abstract というキーワードが使用されていること (19, 20 行目) です。
これによって、color() というメソッドは、これを継承するサブクラスで個別に定義されることになります。
また、これ以外に taste() というメソッド (21 〜 23 行目) を持っており、これは各サブクラスで共通するメソッドとして使用することができます。
オブジェクト指向プログラミングでは、ある名前のメソッドやフィールドに複数の機能を持たせることができます。 これを多態性(ポリモーフィズム) と言います。 多態性によって、プログラマはより理解しやすい(人間にとって読みやすい)プログラムを書くことができるようになります。 例えば、次のコードをご覧下さい。 (InheritanceTester2.java)
01: package net.studyinghttp;
02:
03: class InheritanceTester2 {
04: public static void main(String args[]) {
05: Fruit apple = new Apple();
06: System.out.println(apple.color());
07: System.out.println(apple.color("codling"));
08: System.out.println(apple.taste());
09: }
10: }
11:
12: class Apple extends Fruit {
13: String color (String type) {
14: if (type.equals("codling")) {
15: return "Green";
16: } else {
17: return "Red";
18: }
19: }
20: String color () {
21: return color("normal");
22: }
23:
24: String taste () {
25: return "Sour";
26: }
27: }
28:
29: abstract class Fruit {
30: abstract String color();
31: abstract String color(String type);
32: String taste () {
33: return "Sweet";
34: }
35: }
Javaでは、多態性(ポリモーフィズム)を実現するために、二種類の方法が提供されています。
一つはオーバーロード {overload}で、これは「一つのクラスでメソッドを多重定義すること」を意味します。
複数定義されたメソッドのうち、実際にどれを実行するかは、引数の数や種類で区別されます。
上の例で言うと、color() がこれにあたり、「引数なし」と「引数が String 型」のものとか区別されています。
しかし実際には、21 行目より、「引数なし」の color() は「引数が String 型」のものを呼び出しています。
このように、ほとんど同じような処理を行うメソッドを、よりシンプルに提供できる仕組みとして、オーバーロードは利用されています。
もう一つはオーバーライド {override}で、これは「スーパークラスで定義されたメソッドをサブクラスで再定義すること」を言います。
上の例で言うと、taste() がこれにあたり、8 行目の結果は、スーパークラスで定義された値 (33 行目) ではなく、サブクラスで再定義された値 (25 行目) のものになります。
この仕組みにより、いちいち全てのクラスで値を定義する必要がなくなります。
従って、例えば「ほとんどのクラス(BananaやMelon)では“Sweet”なのだが、Appleでのみ“Sour”を使いたい」という場合に便利です。
プログラマにとって、メソッドや変数の型についての考慮を減らせるということは大変有用です。 この点からも、オブジェクト指向プログラミングが、複数人による、大規模なプログラミングに有用であるということが言えるでしょう。
(※) ちなみに、このような仕組みは、たとえばC言語では提供されていないので、関数名(Javaにおける“メソッド”)は異なるものにしなければなりません。