■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 2009年11月29日 Java総合講座 - 初心者から達人へのパスポート vol.181 セルゲイ・ランダウ バックナンバー: http://www.flsi.co.jp/Java_text/ ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ ------------------------------------------------------- ・現在、このメールマガジンは以下の2部構成になっています。 [1] 当初からのコース:毎週日曜の深夜に発行 これは現在、中級レベルになっています。 [2] 2009年11月開講コース:毎週水曜の深夜に発行 これは現在、初心者向けのレベルになっています。 ・このメールマガジンは、画面を最大化して見てください。 小さな画面で見ていると、不適切な位置で行が切れてしまう など、問題を起すことがあります。 ・このメールマガジンに掲載されているソース・コード及び 文章は特に断らない限り、すべて筆者が著作権を所有してい ます。また、これらのソース・コードは学習用のためだけに 提供しているものです。 ------------------------------------------------------- ======================================================== ◆ 01.ログ出力のための技術 ======================================================== 58〜59行目の buffer.append(" " + fieldName + " = "); appendObject(fieldName, result); では、"フィールド名 = フィールドの値"という形式でフィールドの情報をStringBuffer オブジェクトに出力させるようにしていますが、このとき、フィールドの値は appendObject(fieldName, result)というメソッド呼び出しによって出力させて います。そして、そのappendObject()メソッドはその直ぐ下の62行目以降に定義 しています。 ここのresultという変数にはいっている値は50行目 result = method.invoke(object, new Object[0]); の戻り値でしたが、この戻り値(result)はObject型であることに注意して下さい。 では、もし、50行目の実行によって呼び出されるメソッドがプリミティブ型の 戻り値を持っている場合はどうなるのでしょうか?(たとえば、Studentクラスの getAge()メソッドはint型というプリミティブ型の戻り値を持っていますね。) 実は、この場合は、プリミティブ型の戻り値がラッパー・クラスのオブジェクト に変換されてから返されます。 ┌補足─────────────────────────┐ ラッパー・クラスというのは、オブジェクトでないもの(例えば プリミティブ型などのオブジェクトでないものや、他のプログラ ミング言語で書かれたプログラムから取り出したデータなど)等 の何らかのデータをオブジェクトにラップ(wrap = 包み込む) するクラスです。 例えば、Integerというクラスはint型のデータに対するラッパー・ クラスであり、Characterというクラスはchar型のデータに対す るラッパー・クラスであり、Longはlongのラッパー・クラス、 Floatはfloatのラッパー・クラスというように各プリミティブ型 に対応するラッパー・クラスが用意されています。 (なお、ラッパー・クラスには、他のオブジェクトを処理しやす くするために別のオブジェクトに包み込むという種類のものもあ ります。とにかく、何かを包み込むクラスがラッパー・クラス です。) └───────────────────────────┘ 例えば、先ほどのgetAge()メソッドが呼び出された場合は、そのint型の戻り値 はInteger型のオブジェクトに変換されてresult変数に代入されることになります。 ┌補足─────────────────────────┐ 試しに下記のようなソース・コードを実行してみればわかると 思いますが、プリミティブ型のデータをObject型の変数に代入 すると自動的にラッパー・クラスに変換されるようになってい るのです。 int int10 = 10; Object intObj = int10; Class clazz = intObj.getClass(); System.out.println("Class name of intObj = " + clazz.getName()); System.out.println("Value of intObj = " + intObj); 上記のコードを実行すると Class name of intObj = java.lang.Integer Value of intObj = 10 という結果が返ってきますね。 └───────────────────────────┘ ただし、プリミティブ型を要素とする配列が戻り値の場合は、配列(Array)自体は オブジェクト(Objectのサブクラス)であるため、(ラッパー・クラスに変換され ずに)そのまま返されることになります。 このことを踏まえた上で、62行目以降のappendObject()メソッドの定義を見て 下さい。 private void appendObject(String fieldName, Object value) { if (value instanceof Collection) { append(fieldName, (Collection) value); } else if (value instanceof Map) { append(fieldName, (Map) value); } else if (value instanceof long[]) { append(fieldName, (long[]) value); } else if (value instanceof int[]) { append(fieldName, (int[]) value); } else if (value instanceof short[]) { append(fieldName, (short[]) value); } else if (value instanceof byte[]) { append(fieldName, (byte[]) value); } else if (value instanceof char[]) { append(fieldName, (char[]) value); } else if (value instanceof double[]) { append(fieldName, (double[]) value); } else if (value instanceof float[]) { append(fieldName, (float[]) value); } else if (value instanceof boolean[]) { append(fieldName, (boolean[]) value); } else if (value.getClass().isArray()) { append(fieldName, (Object[]) value); } else { append(fieldName, value); } buffer.append("\n"); } このメソッドの大部分はif-else文で構成されていますね。そして、引数のオブジェ クトの型に合わせて、それぞれ別々のメソッド(すべてappend()という名前のメソッ ドであるが、引数の違いによって複数の同名のメソッドにオーバーロード(多重定義) されている)を呼び出すだけの単純なロジックになっていますね。 オブジェクトの型は、まず最初にCollection型とMap型の場合が抽出されていますが、 CollectionやMapは配列のように複数の要素(ただし、要素が0個や1個だけの場合もある) を持つ、入れ物的な型のインターフェースです。 配列以外の複数の要素を持つ型(たとえばVectorなど)はすべてこのCollectionかMap のどちらかのインターフェースを実装しています。 また、その下では、long型やint型などさまざまなプリミティブ型の要素を持つ配列を 抽出していますね。そして、最後にObject型の要素を持つ配列を抽出しています。 以上で、複数の要素を持つ型はすべて抽出され、それらに対応したappend()メソッドが 呼び出されることになります。 最後のelse文では、引数のオブジェクトの型がそれら以外の型の場合、つまり入れ物的 ではない単独のオブジェクトの場合に、それに対応したappend()メソッドを呼び出す ようにしています。 ここでは単独のプリミティブ型についての記述がありませんが、先ほど述べたように、 このappendObject()メソッドを呼び出す前にプリミティブ型のデータはラッパー・クラ スのオブジェクトに変換されているので、これらはすべて最後のelse文で処理される訳 です。 あと、最後のbuffer.append("\n")というコードは、たんに改行をStringBufferに書き 込んでいるだけですね。 では、続いて、91行目以降の各append()メソッドの定義を見ていきましょう。 まず、91〜99行目 private void append(String fieldName, Collection value) { Object[] array = value.toArray(); buffer.append("{\n"); for (Object element : array) { buffer.append(" "); appendObject(fieldName, element); } buffer.append("}"); } は、Collection型のオブジェクトを対象(引数)にしていますが、ここではまず CollectionのtoArray()メソッドを呼び出すことによって、valueをいったん配列に 変換してから、forループで、その配列の中のすべての要素に対して処理を行っています。 こうすれば、Collection型のすべてのクラスに対して、同じように、そのすべての 要素に対する処理が行えます。 その要素の型はどんな型であるか分かりませんので、ここで再帰的にappendObject() メソッドを呼び出すことによって、どんな型に対しても処理を行えるようにしています。 続いて、101〜110行目 private void append(String fieldName, Map value) { Collection col = value.values(); Object[] array = col.toArray(); buffer.append("{\n"); for (Object element : array) { buffer.append(" "); appendObject(fieldName, element); } buffer.append("}"); } は、Map型のオブジェクトを対象(引数)にしていますが、ここではまず最初に Mapのvalues()メソッドを呼び出すことによって、いったんCollection型に変換 しています。こうすれば、あとは上のCollectionに対する処理と同様にすれば 済むわけです。 続いて、112〜118行目 private void append(String fieldName, long[] value) { buffer.append("{\n"); for (long element : value) { buffer.append(" " + element + "\n"); } buffer.append("}"); } では、longの配列を対象として処理を行っています。forループで、その配列の中の すべての要素に対して処理を行っていることは上のCollectionやMapの場合と同様です。 ただし、longはプリミティブ型なので、データとして直接書き出すことができますから、 StringBufferのappend()メソッドを呼び出して直接、bufferのデータの中に値を書き込 んでいます。したがって、ここでは再帰的にappendObject()メソッドを呼び出したり しません。 以下、120〜174行目までは、同様に各プリミティブ型に対する処理を行うメソッドに なっています。 最後の185〜199行目 private void append(String fieldName, Object value) { if ((value instanceof String) || (value instanceof Long) || (value instanceof Integer) || (value instanceof Short) || (value instanceof Byte) || (value instanceof Character) || (value instanceof Double) || (value instanceof Float) || (value instanceof Boolean)) { buffer.append(value); } else { appendFields(value); } } は、Collection型でもMap型でも配列でもない単独のオブジェクトの場合の処理を 行うメソッドですが、if文でString型やプリミティブ型(のラッパー・クラス) の場合にStringBufferのappend()メソッドでbufferに直接その値を書き出し、 String型でもプリミティブ型でもない場合にはappendFields()メソッドを呼び出し ています。 ここでappendFields()メソッドを呼び出すのは、一般のオブジェクトの場合は、 さらにそのオブジェクトの中のフィールドを調べ上げて、その値をリストアップ する必要があるためです。 ところで、このReflectionToStringMakerクラスはEclipseのエディター上で、 たくさん警告のマーク(黄色いアンダーラインが引かれ、左側に小さな黄色い 電球とエクスクラメーション(びっくりマーク)のアイコンが付いている)が 付いていると思いますが、これらは無視してかまいません。 ここで、これについてちょっと解説しておきます。 ClassやCollectionやMapは総称型(generic type)と言って、<>にパラメターを 入れて指定することによって、対象とする型を明示することができます。 (パラメターを指定した型を「パラメータ化型(Parameterized Types)」と呼び、 それに対し、パラメターを指定しない場合はraw型と呼ぶことがあります。) たとえばCollectionインターフェースを実装しているVectorクラスは Collection<Integer> intCollection = new Vector<Integer>(); または、 Vector<Integer> intVector = new Vector<Integer>(); のようにクラスにパラメター(ここではVectorクラスにパラメターとしてIntegerを 指定している)を指定することによって、要素の型(ここでは要素をInteger型に している)を明示することができますね。 (このようなパラメターはクラス、インターフェース、メソッドおよびコンストラ クターに指定することができます。) このように要素の型を明示しておくと、間違った型のオブジェクトを要素として Vectorオブジェクトに追加しようとしたときなどに、コンパイラーが間違いをチェッ クしてエラーを返してくれます。 このように、コンパイルの段階で間違いがチェックされるので、プログラムの実行時 にいきなりエラーが出るということがなく、安全なのです。 したがって、総称型としてパラメーターを指定できるものはパラメーターを指定する ことが推奨されており、パラメーターを指定していない場合はコンパイラーが警告 を発するようになっています。 ReflectionToStringMakerクラスにEclipseのエディター上で、たくさん警告のマーク が付いているのは、そのためですが、ReflectionToStringMakerクラスでは、対象とす る型を明示できないため、パラメーターを指定しないようにしているのです。 これは意図的に行っていることであり、問題はありません。 このように警告のメッセージがあっても、その理由が明白で問題がないことがわかって いる場合は無視してかまいません。 さて、あとは自力で理解できることと思いますが、ただ、個々のメソッドが簡単な わりには、クラス全体を理解するのは難しいと感じられるかも知れません。 プログラミングに慣れている人にとっては、やさしいかもしれませんが、プログラ ミングにまだ十分に慣れていない人にとっては、特にメソッドが再帰的に呼び出さ れているところなど、難しく感じられるところがたくさんあるかも知れません。 そういう人は、ReflectionToStringMakerTestを実行したときに各コードが実行されて いく手順を一つ一つ自分で追ってみて下さい。つまり、自分がコンピューターになった つもりで、ソース・コードの中を順番に自分で実行したらどうなるか、各変数の値が どのように変わっていくか、結果はどのようになるかを(紙にメモ書きしながら)考え ていくのです。(こういう作業を机上デバッグと呼ぶことがあります。つまり、これを やっているうちにプログラムの理解が深まると同時に、机上でプログラムの間違いを 見つけることができます。) この作業には時間がかかりますが、様々なプログラムでこのような作業を繰り返して いるうちに、段々とプログラムを理解する能力が向上してきます。そして、それが 自分の技術力の土台にもなっていきます。 ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ (次回に続く) では、今日はここまでにします。 ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ★ホームページ: http://www.flsi.co.jp/Java_text/ ★このメールマガジンは 「まぐまぐ(http://www.mag2.com)」 を利用して発行しています。 ★バックナンバーは http://www.flsi.co.jp/Java_text/ にあります。 ★このメールマガジンの登録/解除は下記Webページでできます。 http://www.mag2.com/m/0000193915.html ★このメールマガジンへの質問は下記Webページにて受け付けて います。わからない所がありましたら、どしどしと質問をお寄 せください。 http://www.flsi.co.jp/Java_text/ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Copyright (C) 2009 Future Lifestyle Inc. 不許無断複製 |