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]から入手できます。