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

1概要

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

プリミティブ変換については、リンク:/java-primitive-conversions[この記事]で説明しました。ここでは、Javaがどのように型を処理するのかを理解するために、参照のキャストに焦点を当てます。

2プリミティブと参照

基本的な変換と参照変数のキャストは似ているように見えるかもしれませんが、それらはかなりhttps://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.1[異なる概念]です。

どちらの場合も、あるタイプを別のタイプに「変えて」います。しかし、簡単に言うと、プリミティブ変数はその値を含み、プリミティブ変数の変換はその値の不可逆的な変更を意味します。

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

assertNotEquals(myDouble, myInt);

上記の例の変換後、 myInt 変数は 1 になっています。以前の値である 1.1 を元に戻すことはできません。

  • 参照変数は異なります。参照変数はオブジェクトのみを参照しますが、オブジェクト自体は含まれません。

また、参照変数をキャストしても、それが参照するオブジェクトには影響しませんが、このオブジェクトに別の方法でラベルを付けるだけで、それを使用する機会が拡大または縮小されます。アップキャストはこのオブジェクトで利用可能なメソッドとプロパティのリストを絞り込み、ダウンキャストはそれを拡張することができます。

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

3アップキャスティング

サブクラスからスーパークラスへのキャストはアップキャストと呼ばれます。

通常、アップキャストはコンパイラによって暗黙的に実行されます。

アップキャスティングは継承と密接に関係しています。これは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;

しかし、継承ツリーを明示的にキャストする必要はありません。 cat Animal であり、エラーを表示しません。

この参照は、宣言された型の任意のサブタイプを参照できます。

アップキャストを使用して、 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<Animal> animals) {
        animals.forEach(animal -> {
            animal.eat();
        });
    }
}

AnimalFeeder がリストに含まれている animal Cat または Dog )を気にしたくない場合 feed() メソッドでは、それらはすべて animals です。

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

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

私たちは猫と犬を追加し、それらは暗黙のうちに Animal タイプにキャストされています。

Cat Animal で、各 Dog Animal です。それらは多相です。

ところで、各オブジェクトは少なくとも Object であるため、すべてのJavaオブジェクトは多態的です。 Animal のインスタンスを Object typeの参照変数に代入すると、コンパイラは文句を言いません。

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();

Cat Mew です。アップキャストは正当で暗黙のうちに行われます。

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

3.2. オーバーライド

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

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

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

web - 2018-02-15 22:48:49,354[main]INFO com.baeldung.casting.Cat - cat is eating
web - 2018-02-15 22:48:49,363[main]INFO com.baeldung.casting.Dog - dog is eating
  • 総括する:**

  • 参照変数は、そのオブジェクトが

変数と同じ型、またはサブタイプの場合 ** アップキャストは暗黙のうちに行われます

  • すべてのJavaオブジェクトは多相であり、のオブジェクトとして扱うことができます。

アップキャストによるスーパータイプ

4ダウンキャスト

Cat クラスでしか使用できないメソッドを呼び出すために Animal 型の変数を使用したい場合はどうなりますか?ここに曇りが来ます。スーパークラスからサブクラスへのキャストです。

例を見てみましょう:

Animal animal = new Cat();

animal 変数は Cat のインスタンスを参照することを私たちは知っています。そして animal の上で Cat meow() メソッドを呼び出したいです。しかし、コンパイラは meow() メソッドが Animal 型には存在しないと訴えています。

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

((Cat) animal).meow();

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

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

public class AnimalFeeder {

    public void feed(List<Animal> 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.baeldung.casting.Cat - cat is eating
web - 2018-02-16 18:13:45,454[main]INFO com.baeldung.casting.Cat - meow
web - 2018-02-16 18:13:45,455[main]INFO com.baeldung.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<Animal> animals) {
    animals.forEach(animal -> {
        animal.eat();
        ((Cat) animal).meow();
    });
}

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

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

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

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

無関係な型にダウンキャストしようとすると、コンパイラはこれを許可しません。

Animal animal;
String s = (String) animal;

コンパイラは「AnimalからStringにキャストできません」と言います。

コードをコンパイルするには、両方の型が同じ継承ツリーに含まれている必要があります。

まとめると:

  • ダウンキャストは、特定のメンバーにアクセスするために必要です。

サブクラス ** キャスト演算子を使用してダウンキャストが行われます

  • オブジェクトを安全にダウンキャストするには 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<T> {
    private Class<T> type;

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

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

}

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

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

T Cat と等しくし、メソッドがcatだけを返すようにしましょう:

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

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

6. 結論

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

いつものように、この記事のコードはhttps://github.com/eugenp/tutorials/tree/master/core-java-lang-oop[over on GitHub]から入手できます。