■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
                      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. 不許無断複製