Javaの使い方メモ(Downcast, Generics)

Javaの勉強中です。
Tutorialとか、Examplesにあるコードを見ていると、

String str = (String) list.get(0);
とか、
List<String> list = new ArrayList<String>();

といった記述を目にすることがあって、「この丸カッコ・三角カッコで囲まれているものは何だろう。」と思ったものの、「型を指定しているんだろうなぁ、きっと。」と考えてスルーしてきました。
が、いまさらながら、きちんと調べてみました。

使っている環境
Java JDK1.6.0_35(64bit版)


(1)DowncastとGenerics
Genericsの機能がJavaになかった頃、Listに文字列を追加して、それを取出す場合、このようにしていた。
(参考) Javaジェネリクス再入門 – プログラマーの脳みそ

List list = new ArrayList();
list.add("hello!");
String str = (String) list.get(0);

ArrayListに追加される要素は、Object型となっている。そのため、list.get(0)として、要素を取出したとき中身がStringであったとしても、要素の型はObject型となっている。

このままの状態だと、String型の変数strに値を格納することができない。そのため、
(String) list.get(0)

として、Downcastしている。(上位の型(Object型)を下位の型(String型)に変換している)
Downcastしたときの問題点として、「コンパイル時にエラーが出ないが、アプリ実行時にエラーが発生することがある」という問題がある。
上記の例の場合、listの要素にString以外のものが入っていた場合、「コンパイルは通るが実行時エラー」となる。ClassCastExceptionが発生するらしい。
このClassCastExceptionを解決するにはは、listにどんなデータが格納されていたかを調査する必要があり、デバッグがとても大変らしい。

そこで、現在ではJavaのGenerics機能をつかって、以下のように書いている。

List<String> list = new ArrayList<String>();
list.add("hello!");
String str = list.get(0);

List<String>と書くことで、listにString型がバインドされる。これにより、listに格納される要素はString型のみとなる。また、list.getするとString型が返るようになる。


(2)Genericsとは?
クラス定義でGenericsを使える。
(参考)ジェネリックスとは?なぜジェネリックスなのか – Java 入門

たとえば以下のようなクラスBoxを定義すると、

public class Box<T> {
    T o;

    public Box(T o){
        this.o = o;
    }
    public T get(){
        return o;
    }
}

クラスを使うときに、Box<Integer>、Box<String>といった形で指定することにより、それぞれの型用の Box クラスを用意できるようになる。以下のような感じでBox<Integer>を使うと、getしたときの返り値もInteger型になる。

Box<Integer> b = new Box<Integer>(new Integer(123));
Integer i = b.get();
System.out.println(i);

Genericsを使うことにより、Integer用・String用といったクラスを別々に定義する必要がなくなり、汎用化できる。僕に使いこなせるかどうかは、わからないけど(笑)


(3)もっとGenerics
メソッドにも使える。
(参考)Java Generics Tutorial

public class GenericMethodTest
{
   // generic method printArray                         
   public static < E > void printArray( E[] inputArray )
   {
      // Display array elements              
         for ( E element : inputArray ){        
            System.out.printf( "%s ", element );
         }
         System.out.println();
    }

    public static void main( String args[] )
    {
        // Create arrays of Integer, Double and Character
        Integer[] intArray = { 1, 2, 3, 4, 5 };
        Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
        Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };

        System.out.println( "Array integerArray contains:" );
        printArray( intArray  ); // pass an Integer array

        System.out.println( "\nArray doubleArray contains:" );
        printArray( doubleArray ); // pass a Double array

        System.out.println( "\nArray characterArray contains:" );
        printArray( charArray ); // pass a Character array
    } 
}

printArrayメソッドでは、渡された配列の内容を表示しています。Genericsをつかうことで、int型の配列・double型の配列・String型の配列に対応できるようになっています。
実行すると以下のようになる。

Array integerArray contains:
1 2 3 4 5 6

Array doubleArray contains:
1.1 2.2 3.3 4.4 

Array characterArray contains:
H E L L O

(4)Downcastの使い道
どうしてもDowncastを使う場合、ClassCastExceptionを避けるために、instanceof演算子などを使って型のチェックをしておくべき。
(参考)8章:継承
(参考)強く型付けされているJavaの理解に必修の“型変換” (2/3) – @IT

    if( obj instanceof String )
        str1 = (String)obj;

でも、「そもそもDowncastをどういうときに使うべきか?」っていう素朴な疑問がわいてくる。つかいどころ、ってどこ?
これについては、こちらのサイトに例がありました。
(参考)Downcasting in Java

オブジェクトとオブジェクトの型を比較したいときにDowncastを使うと便利らしい。

public class Person
{
  private String name;
  private int age;

  public boolean equals(Object anObject)
  {
     if (anObject == null)
         return false;

     /* The object being passed in is checked
         to see it's class type which is then compared
         to the class type of the current class.  If they
         are not equal then it returns false
     */

     else if (getClass( ) != anObject.getClass())
         return false;

     else
     {
        /* 
         this is a downcast since the Object class
         is always at the very top of the inheritance tree
         and all classes derive either directly or indirectly 
         from Object:
        */
        Person aPerson = (Person) anObject;
        return ( name.equals(aPerson.name) 
                && (age == aPerson.age));
     }
  }

}

上記の例では、Personクラスのequals()メソッドでDowncastを使っています。異なる2つのオブジェクト(自分自身とanObject)のnameとageを比較しています。

想定される使用イメージはこんな感じ。
・Personクラスを継承したクラスとして、Japanese, American, Chineseといったクラスがあるとします。
・で、それぞれのクラスを比較したい。

Personクラスのequals()メソッドでは、引数として様々なクラスを引き受ける必要があるため、引数としてObject型の変数anObjectを使っています。Personクラスと比較するときに、型を合わせるために、Downcastしています。

今回の例では、ClassCastExceptionを避けるために、instanceof演算子ではなくgetClassを使っています。なのでJapaneseどうし、Americanどうしであった場合に、Downcastして比較することになりますね。

なお、instanceof演算子とgetClassの微妙な違いが気になったので、以下に引用しておきます。

~~~~~~~~~~~~~~~~~~
(参考)4章 継承
instanceof演算子について(以下、引用)
<オブジェクト変数>に代入されているインスタンスのクラスが<クラス名>と一致すればtrueを返します。また、インスタンスが<クラス名>クラスを継承している場合も、trueが返ります。

ObjectクラスのgetClass()を用いてインスタンスobjがクラスAのインスタンスであるか調べる場合、obj.getClass() == A.classと比較することでできます。
もし、インスタンスobjがクラスAを継承したクラスA’のインスタンスである場合、上記の比較構文はfalseを返します。instanceofであればtrueになることに注意してください。
~~~~~~~~~~~~~~~~~~

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s