Javaにおけるオブジェクト型キャスト

Javaでのオブジェクト型のキャスト

1. 概要

Java型システムは、プリミティブと参照という2種類の型で構成されています。

プリミティブ変換についてはthis articleで説明しました。ここでは、Javaが型を処理する方法を十分に理解するために、参照のキャストに焦点を当てます。

参考文献:

Javaジェネリックの基本

Java Genericsの基本の簡単な紹介。

Java instanceofオペレーター

Javaのinstanceof operatorについて学ぶ

2. プリミティブvs. 参照

プリミティブ変換と参照変数のキャストは似ているように見えるかもしれませんが、それらはかなりdifferent conceptsです。

どちらの場合も、あるタイプを別のタイプに「変換」しています。 しかし、単純化された方法では、プリミティブ変数はその値を含み、プリミティブ変数の変換はその値の不可逆的な変化を意味します:

double myDouble = 1.1;
int myInt = (int) myDouble;

assertNotEquals(myDouble, myInt);

上記の例の変換後、myInt変数は1であり、それから以前の値1.1を復元することはできません。

Reference variables are different;参照変数はオブジェクトのみを参照し、オブジェクト自体は含みません。

参照変数をキャストしても、それが参照するオブジェクトには影響しませんが、このオブジェクトに別の方法でラベルを付けるだけで、操作する機会が拡大または縮小されます。 Upcasting narrows the list of methods and properties available to this object, and downcasting can extend it.

参照は、オブジェクトへのリモートコントロールのようなものです。 リモートコントロールには、そのタイプに応じてボタンの数が増減し、オブジェクト自体はヒープに格納されます。 キャストするときは、リモコンの種類を変更しますが、オブジェクト自体は変更しません。

3. アップキャスティング

Casting from a subclass to a superclass is called upcasting。 通常、アップキャストはコンパイラーによって暗黙的に実行されます。

アップキャスティングは継承と密接に関連しています。継承はJavaのもう1つのコアコンセプトです。 参照変数を使用して、より具体的なタイプを参照するのが一般的です。 そして、これを行うたびに、暗黙のアップキャストが行われます。

アップキャストを示すために、Animalクラスを定義しましょう。

public class Animal {

    public void eat() {
        // ...
    }
}

それでは、Animalを拡張しましょう。

public class Cat extends Animal {

    public void eat() {
         // ...
    }

    public void meow() {
         // ...
    }
}

これで、Catクラスのオブジェクトを作成し、それをタイプCatの参照変数に割り当てることができます。

Cat cat = new Cat();

また、タイプAnimalの参照変数に割り当てることもできます。

Animal animal = cat;

上記の割り当てでは、暗黙的なアップキャストが行われます。 明示的に行うことができます:

animal = (Animal) cat;

ただし、継承ツリーを明示的にキャストする必要はありません。 コンパイラは、catAnimalであることを認識しており、エラーを表示しません。

その参照は、宣言された型の任意のサブタイプを参照できることに注意してください。

アップキャストを使用して、Catインスタンスで使用できるメソッドの数を制限しましたが、インスタンス自体は変更していません。 これで、Cat –に固有のことは何もできなくなり、animal変数でmeow()を呼び出すことができなくなります。

CatオブジェクトはCatオブジェクトのままですが、meow()を呼び出すとコンパイラエラーが発生します。

// animal.meow(); The method meow() is undefined for the type Animal

meow()を呼び出すには、animalをダウンキャストする必要があります。これは後で行います。

しかし、今度はアップキャストを提供するものについて説明します。 アップキャストのおかげで、ポリモーフィズムを活用できます。

3.1. 多型

Animalの別のサブクラスであるDogクラスを定義しましょう。

public class Dog extends Animal {

    public void eat() {
         // ...
    }
}

これで、すべての猫と犬をanimalsのように扱うfeed()メソッドを定義できます。

public class AnimalFeeder {

    public void feed(List animals) {
        animals.forEach(animal -> {
            animal.eat();
        });
    }
}

AnimalFeederがリストにあるanimalCatまたはDog)を気にしないようにします。 feed()メソッドでは、それらはすべてanimalsです。

特定のタイプのオブジェクトをanimalsリストに追加すると、暗黙的なアップキャストが発生します。

List animals = new ArrayList<>();
animals.add(new Cat());
animals.add(new Dog());
new AnimalFeeder().feed(animals);

猫と犬を追加すると、暗黙的にAnimalタイプにアップキャストされます。 各CatAnimalであり、各DogAnimalです。 それらは多形です。

ちなみに、各オブジェクトは少なくともObjectであるため、すべてのJavaオブジェクトは多形です。 AnimalのインスタンスをObjectタイプの参照変数に割り当てることができ、コンパイラは文句を言いません。

Object object = new Animal();

そのため、作成するすべてのJavaオブジェクトには、toString()などのObject固有のメソッドがすでに含まれています。

インターフェイスへのアップキャストも一般的です。

Mewインターフェースを作成し、Catにそれを実装させることができます。

public interface Mew {
    public void meow();
}

public class Cat extends Animal implements Mew {

    public void eat() {
         // ...
    }

    public void meow() {
         // ...
    }
}

これで、CatオブジェクトをMewにアップキャストすることもできます。

Mew mew = new Cat();

CatMewであり、アップキャストは合法であり、暗黙的に行われます。

したがって、CatMewAnimalObject、およびCatです。 この例では、4つのタイプすべての参照変数に割り当てることができます。

3.2. オーバーライド

上記の例では、eat()メソッドがオーバーライドされています。 つまり、eat()Animalタイプの変数で呼び出されますが、作業は実際のオブジェクト(猫と犬)で呼び出されるメソッドによって実行されます。

public void feed(List animals) {
    animals.forEach(animal -> {
        animal.eat();
    });
}

クラスにロギングを追加すると、CatDogのメソッドが呼び出されることがわかります。

web - 2018-02-15 22:48:49,354 [main] INFO com.example.casting.Cat - cat is eating
web - 2018-02-15 22:48:49,363 [main] INFO com.example.casting.Dog - dog is eating

総括する:

  • オブジェクトが変数と同じ型である場合、またはサブタイプである場合、参照変数はオブジェクトを参照できます

  • アップキャストは暗黙的に行われます

  • すべてのJavaオブジェクトは多態性であり、アップキャストによりスーパータイプのオブジェクトとして扱うことができます

4. ダウンキャスト

タイプAnimalの変数を使用して、Catクラスでのみ使用可能なメソッドを呼び出す場合はどうなりますか? ここにダウンキャストがあります。 It’s the casting from a superclass to a subclass.

例を見てみましょう。

Animal animal = new Cat();

animal変数がCatのインスタンスを参照していることがわかっています。 そして、animalCatmeow()メソッドを呼び出したいと思います。 しかし、コンパイラは、タイプAnimalに対してmeow()メソッドが存在しないと文句を言います。

meow()を呼び出すには、animalCatにダウンキャストする必要があります。

((Cat) animal).meow();

内側の括弧とそれに含まれる型は、キャスト演算子と呼ばれることもあります。 コードをコンパイルするには、外部括弧も必要であることに注意してください。

前のAnimalFeederの例をmeow()メソッドで書き直してみましょう。

public class AnimalFeeder {

    public void feed(List animals) {
        animals.forEach(animal -> {
            animal.eat();
            if (animal instanceof Cat) {
                ((Cat) animal).meow();
            }
        });
    }
}

これで、Catクラスで使用可能なすべてのメソッドにアクセスできるようになりました。 ログを見て、meow()が実際に呼び出されていることを確認します。

web - 2018-02-16 18:13:45,445 [main] INFO com.example.casting.Cat - cat is eating
web - 2018-02-16 18:13:45,454 [main] INFO com.example.casting.Cat - meow
web - 2018-02-16 18:13:45,455 [main] INFO com.example.casting.Dog - dog is eating

上記の例では、実際にはCatのインスタンスであるオブジェクトのみをダウンキャストしようとしていることに注意してください。 これを行うには、演算子instanceofを使用します。

4.1. instanceof演算子

オブジェクトが特定のタイプに属しているかどうかを確認するために、ダウンキャストする前にinstanceof演算子を使用することがよくあります。

if (animal instanceof Cat) {
    ((Cat) animal).meow();
}

4.2. ClassCastException

instanceof演算子で型をチェックしていなければ、コンパイラは文句を言わなかったでしょう。 ただし、実行時には例外が発生します。

これを示すために、上記のコードからinstanceof演算子を削除しましょう。

public void uncheckedFeed(List animals) {
    animals.forEach(animal -> {
        animal.eat();
        ((Cat) animal).meow();
    });
}

このコードは問題なくコンパイルされます。 ただし、実行しようとすると例外が表示されます。

java.lang.ClassCastException: com.example.casting.Dogcom.example.casting.Catにキャストすることはできません

これは、DogのインスタンスであるオブジェクトをCatインスタンスに変換しようとしていることを意味します。

ダウンキャスト先のタイプが実際のオブジェクトのタイプと一致しない場合、ClassCastException'sは常に実行時にスローされます。

無関係な型にダウンキャストしようとすると、コンパイラはこれを許可しないことに注意してください。

Animal animal;
String s = (String) animal;

コンパイラは、「動物から文字列にキャストできません」と言います。

コードをコンパイルするには、両方のタイプが同じ継承ツリー内にある必要があります。

要約しましょう:

  • サブクラスに固有のメンバーにアクセスするには、ダウンキャストが必要です

  • ダウンキャストはキャスト演算子を使用して行われます

  • オブジェクトを安全にダウンキャストするには、instanceof演算子が必要です

  • 実際のオブジェクトがダウンキャスト先のタイプと一致しない場合、実行時にClassCastExceptionがスローされます

5. Cast()メソッド

Classのメソッドを使用してオブジェクトをキャストする別の方法があります。

public void whenDowncastToCatWithCastMethod_thenMeowIsCalled() {
    Animal animal = new Cat();
    if (Cat.class.isInstance(animal)) {
        Cat cat = Cat.class.cast(animal);
        cat.meow();
    }
}

上記の例では、キャストおよびinstanceof演算子の代わりに、cast()およびisInstance()メソッドがそれぞれ使用されています。

ジェネリック型ではcast()メソッドとisInstance()メソッドを使用するのが一般的です。

typeパラメータの値に応じて、猫または犬の1種類の動物のみに「餌を与える」feed()メソッドを使用してAnimalFeederGeneric<T>クラスを作成しましょう。

public class AnimalFeederGeneric {
    private Class type;

    public AnimalFeederGeneric(Class type) {
        this.type = type;
    }

    public List feed(List animals) {
        List list = new ArrayList();
        animals.forEach(animal -> {
            if (type.isInstance(animal)) {
                T objAsType = type.cast(animal);
                list.add(objAsType);
            }
        });
        return list;
    }

}

feed()メソッドは各動物をチェックし、Tのインスタンスである動物のみを返します。

型パラメーターTからは取得できないため、Classインスタンスもジェネリッククラスに渡す必要があることに注意してください。 この例では、コンストラクタに渡します。

TCatと等しくし、メソッドが猫のみを返すようにします。

@Test
public void whenParameterCat_thenOnlyCatsFed() {
    List animals = new ArrayList<>();
    animals.add(new Cat());
    animals.add(new Dog());
    AnimalFeederGeneric catFeeder
      = new AnimalFeederGeneric(Cat.class);
    List fedAnimals = catFeeder.feed(animals);

    assertTrue(fedAnimals.size() == 1);
    assertTrue(fedAnimals.get(0) instanceof Cat);
}

6. 結論

この基本的なチュートリアルでは、アップキャスト、ダウンキャストとは何か、それらの使用方法、およびこれらの概念がポリモーフィズムの活用にどのように役立つかについて説明しました。

いつものように、この記事のコードはover on GitHubで利用できます。