地球上の人間の多くは、名前に姓と名を持っています。どちらも文字列ですね。では火星人の名前は、整数値二つで表されるとしましょう。同様に土星人は、実数値二つの名前とします。ここでこの二つの名前を構成する要素name1とname2を持つ、地球人も火星人も土星人も、どのオブジェクトも作れる、Personというクラスを考えましょう。name1とname2はどんな型で宣言すればいいでしょうか?



 文字列も整数値も実数値も表せる型とはなんでしょう。文字列はString、これがクラス型なので、整数値も基本データ型のintではなく、ラッパーのIntegerで考えましょう。同様に実数値はDouble型。String、Integer、Doubleのどのオブジェクトでも入れられる変数の型は、そう、一番の親方Objectしかありませんね。コンストラクタとgetter、setterのみだとこんな形になります。(Integer、Doubleについては、ライブラリ編2章をご覧ください。)

public class Person {
   private Object name1;
   private Object name2;

   public Person(Object name1, Object name2) {
      this.name1 = name1;
      this.name2 = name2;
   }

   public Object getName1() {
      return name1;
   }

   public void setName1(Object name1) {
      this.name1 = name1;
   }

   public Object getName2() {
      return name2;
   }

   public void setName2(Object name2) {
     this.name2 = name2;
   }
}

上の3人のオブジェクトを作って、名前を出力してみましょう。

public class Main1 {
   public static void main(String[] args) {
      Person x;
      x = new Person("堅田", "安雄");
      String xname1 = (String)x.getName1();
      String xname2 = (String)x.getName2();
      System.out.println(xname1 + " " + xname2);

      Person y;
      y = new Person(123, 78);
      Integer yname1 = (Integer)y.getName1();
      Integer yname2 = (Integer)y.getName2();
      System.out.println(yname1 + " " + yname2);

      Person z;
      z = new Person(5.1, 0.3);
      Double zname1 = (Double)z.getName1();
      Double zname2 = (Double)z.getName2();
      System.out.println(zname1 + " " + zname2);
   }
}

(注)コンストラクタの実引数に指定したint型の値は、Integerに自動的に変換され、Object型の仮引数に代入されます。double型についてもDouble型に変換され、仮引数に代入されます。また出力の内容は" "という文字列を含むので、+による文字列の連結になり、+の前後はStringに変換されて連結されます。

 出力は次のようになります。

堅田 安雄
123 78
5.1 0.3

 例えば変数yのオブジェクトのname1を取り出すy.getName1()の値は、Objectになります。(クラスPersonのgetName1の戻り値の型を見ましょう。)火星人のname1はIntegerなので、Integer型のyname1に代入しようとしましたが、このときObjectからIntegerへの型変換が必要になります。(Integer)の部分です。(String)や(Double)も同様です。めんどうですね。

 では火星人を作ろうとしてまちがってコンストラクタに文字列”太郎”を指定してしまった次の場合はどうなるでしょうか?

Person w;
w = new Person(33, "太郎");
Integer wname1 = (Integer)w.getName1();
Integer wname2 = (Integer)w.getName2(); // ここで実行時にエラー
System.out.println(wname1 + " " + wname2);

 name2の取り出しで、結果がStringになり、Integerへの型変換ができなくて実行時エラーになります。



 名前を構成する要素は、Stringのペア、Integerのペア、Doubleのペアのどれかのはずです。ところがクラスPersonの中で、Objectとして宣言されているため、片方がInteger、片方がStringのオブジェクトが作れてしまうのです。火星人「33 77」を作るつもりでうっかり「33 太郎」を作ってしまったのかもしれません。この段階でエラーメッセージは出ません。迷子の「33 太郎」ができてしまいました。あとでこれを火星人のつもりで扱うと実行時エラーになってしまうのです。こんなうっかりミスを防ぐ方法があります。ここからがJava5.0の新機能Genericsです。


<>って慣れないと不気味

 オブジェクト生成時、クラスPersonに対し型の指定ができるようにするのです。StringとかIntegerなどを指定してオブジェクトを生成できるようになったのです。クラスPersonは次のようになります。

public class Person<T> {
   private T name1;
   private T name2;

   public Person(T name1, T name2) {
      this.name1 = name1;
      this.name2 = name2;
   }

   public T getName1() {
      return name1;
   }

   public void setName1(T name1) {
      this.name1 = name1;
   }

   public T getName2() {
      return name2;
   }

   public void setName2(T name2) {
      this.name2 = name2;
   }
}

 クラス名の後ろの<T>のTに、型の名前を受け取ります。クラスの中に現れるTはすべて、指定された型の名前が当てはめられます。この「T」は、全体で統一されていれば別の名前でも構いません。
 ではオブジェクトの生成をしてみましょう。なお変数wの処理については、コンパイルエラーになるのでコメントにしてあります。

public class Main1 {
   public static void main(String[] args) {
      Person<String> x;
      x = new Person<String>("堅田", "安雄");
      String xname1 = x.getName1();
      String xname2 = x.getName2();
      System.out.println(xname1 + " " + xname2);

      Person<Integer> y;
      y = new Person<Integer>(123, 78);
      Integer yname1 = y.getName1();
      Integer yname2 = y.getName2();
      System.out.println(yname1 + " " + yname2);

      Person<Double> z;
      z = new Person<Double>(5.1, 0.3);
      Double zname1 = z.getName1();
       Double zname2 = z.getName2();
      System.out.println(zname1 + " " + zname2);

      //Person<Integer> w;
      //w = new Person<Integer>(33, "太郎"); ここでコンパイルエラー
      //Integer wname1 = w.getName1();
      //Integer wname2 = w.getName2();
      //System.out.println(wname1 + " " + wname2);
   }
}


まず変数xはPerson<String>型になります。

   Person<String> x;

クラスPerson<T>のTにStringを当てはめて考えましょう。name1とname2の型は

   private String name1;
   private String name2;

となります。つまりPerson<String>は地球人用になるわけですね。
コンストラクタもTをStringに置き換えると、

   public Person(String name1, String name2) {
      this.name1 = name1;
      this.name2 = name2;
   }

となり、受け取る引数の値はString型になります。

   x = new Person<String>("堅田", "安雄");

 上で変数xには、地球人堅田安雄のオブジェクトが代入されます。コンストラクタには”堅田”と”安雄”というString型の値が渡されています。

 また名前の取り出しでも型変換が不要になります。

   String xname1 = x.getName1(); 
             ↑
     ここに(String)がいらなくなった

なぜならばgetterのTの部分もStringに置き換わっているので、戻り値がStringだからです。

   public String getName1() {
      return name1;
   }

 変数yやzについても同様です。火星人のオブジェクトを作りたければ、クラスPerson<Integer>のオブジェクトをPerson<Integer>型の変数に代入すればいいです。土星人についても同様です。

 さあ、変数wを見てみましょう。Person<Integer>型として宣言しているので、ここには火星人のオブジェクトを代入しなければなりません。火星人を作るためのコンストラクタは

   public Person(Integer name1, Integer name2) {
      this.name1 = name1;
      this.name2 = name2;
   }

になるはずです。ところがこれを呼び出すのに、33と“太郎”を渡してしまいました。

   w = new Person<Integer>(33, "太郎");

“太郎”はString型なので,Integer型の仮引数name2に代入できずコンパイルエラーになります。かわいそうな迷子を作らなくてすみましたね。Person<String>、Person<Integer>、Person<Double>を使っている限り、地球人、火星人、土星人以外の迷子は作らずにすみます。


火星人の変身

 火星人の名前は整数値二つですが、これを文字列として表すこともできます。そうすれば火星人は地球人に変身できますね。火星人を引数で受け取って、地球人にして返すメソッドを考えて見ましょう。引数の型はPerson <Integer>、戻り値の型はPerson<String>になります。次のようになるでしょう。x.getName1()はInteger型なので、toString()でStringに変換しています。x.getName2()についても同様。

   private static Person<String> getPersonFromInteger(Person<Integer> x) {
      return new Person<String>(x.getName1().toString(), x.getName2().toString());
   }

 では変数yの火星人「123 78」を地球人に変身させ、変数aに代入しましょう。

   Person<String> a = getPersonFromInteger(y);


 最後に全部まとめておきます。クラスMain1では、変数wについての処理はコンパイルエラーになるので、コメントにしてあります。

[クラスPerson ]
public class Person<T> {
   private T name1;
   private T name2;

   public Person(T name1, T name2) {
      this.name1 = name1;
      this.name2 = name2;
   }

   public T getName1() {
      return name1;
   }

   public void setName1(T name1) {
      this.name1 = name1;
   }

   public T getName2() {
      return name2;
   }

   public void setName2(T name2) {
      this.name2 = name2;
   }
}


[クラスMain1]
public class Main1 {
   public static void main(String[] args) {
      Person<String> x;
      x = new Person<String>("堅田", "安雄");
      String xname1 = x.getName1();
      String xname2 = x.getName2();
      System.out.println(xname1 + " " + xname2);

      Person<Integer> y;
      y = new Person<Integer>(123, 78);
      Integer yname1 = y.getName1();
       Integer yname2 = y.getName2();
      System.out.println(yname1 + " " + yname2);

      Person<Double> z;
      z = new Person<Double>(5.1, 0.3);
      Double zname1 = z.getName1();
      Double zname2 = z.getName2();
      System.out.println(zname1 + " " + zname2);

      //Person<Integer> w;
      //w = new Person<Integer>(33, "太郎"); ここでコンパイルエラー
      //Integer wname1 = w.getName1();
      //Integer wname2 = w.getName2();
      //System.out.println(wname1 + " " + wname2);

      Person<String> a = getPersonFromInteger(y);
      System.out.println(a.getName1() + " " + a.getName2());

      Person<String> b = getPersonFromDouble(z);
      System.out.println(b.getName1() + " " + b.getName2());
   }

   private static Person<String> getPersonFromInteger(Person<Integer> x) {
      return new Person<String>(x.getName1().toString(), x.getName2().toString());
   }

   private static Person<String> getPersonFromDouble(Person<Double> x) {
      return new Person<String>(x.getName1().toString(), x.getName2().toString());
   }
}

 
 出力結果は次のようになります。右にどこの星の人かを記しておきます。

堅田 安雄   ←地球人
123 78     ←火星人
5.1 0.3    ←土星人
123 78     ←地球人
5.1 0.3    ←地球人

【注意】この章はJava5.0の新機能についての解説です。

〜Generics〜

<<前ページ目次次ページ>>

 火星や土星には生物はいないのでしょうか?いるとしたら各自名前は持っているのでしょうか?今回はそんな壮大なロマン(???)のお話です。

Copyright (c) 2007 Nagi Imai All Rights Reserved.
<<前ページ目次次ページ>>
地球人と火星人と土星人
君の名は?