http://www.javabeginner.com/learn-Java/java-object-typecasting でこの例を見つけましたが、明示的な型キャストについて説明している部分に混乱を招く例があります私。
例:
class Vehicle {
String name;
Vehicle() {
name = "Vehicle";
}
}
class HeavyVehicle extends Vehicle {
HeavyVehicle() {
name = "HeavyVehicle";
}
}
class Truck extends HeavyVehicle {
Truck() {
name = "Truck";
}
}
class LightVehicle extends Vehicle {
LightVehicle() {
name = "LightVehicle";
}
}
public class InstanceOfExample {
static boolean result;
static HeavyVehicle hV = new HeavyVehicle();
static Truck T = new Truck();
static HeavyVehicle hv2 = null;
public static void main(String[] args) {
result = hV instanceof HeavyVehicle;
System.out.print("hV is an HeavyVehicle: " + result + "\n");
result = T instanceof HeavyVehicle;
System.out.print("T is an HeavyVehicle: " + result + "\n");
result = hV instanceof Truck;
System.out.print("hV is a Truck: " + result + "\n");
result = hv2 instanceof HeavyVehicle;
System.out.print("hv2 is an HeavyVehicle: " + result + "\n");
hV = T; //Sucessful Cast form child to parent
T = (Truck) hV; //Sucessful Explicit Cast form parent to child
}
}
Tに参照hVが割り当てられ、(キャスト)として型キャストされる最後の行では、コメントで、これが親から子への明示的な明示的なキャストであると言うのはなぜですか?私が理解しているように、(暗黙的または明示的)キャストは、実際の型ではなく、宣言されたオブジェクトの型のみを変更します(実際にそのオブジェクトのフィールド参照に新しいクラスインスタンスを割り当てない限り、変更されることはありません)。 hvにTruckクラスのスーパークラスであるHeavyVehicleクラスのインスタンスが既に割り当てられている場合、HeavyVehicleクラスから拡張されるTruckというより具体的なサブクラスにこのフィールドを型キャストするにはどうすればよいですか?
私がそれを理解する方法は、キャストがオブジェクト(クラスインスタンス)の特定のメソッドへのアクセスを制限する目的を果たすことです。したがって、オブジェクトを、オブジェクトの実際に割り当てられたクラスよりも多くのメソッドを持つより具体的なクラスとしてキャストすることはできません。つまり、オブジェクトは、スーパークラスまたは実際にインスタンス化されたクラスと同じクラスとしてのみキャストできます。これは正しいですか、ここで間違っていますか?私はまだ学んでいるので、これが物事を見る正しい方法であるかどうかはわかりません。
また、これはダウンキャストの例であることも理解していますが、実際の型にこのオブジェクトがダウンキャストされているクラスのメソッドがない場合、これが実際にどのように機能するのかわかりません。明示的なキャストにより、オブジェクトの実際の型(宣言された型だけでなく)が何らかの形で変更されるため、このオブジェクトはもはやHeavyVehicleクラスのインスタンスではなく、Truckクラスのインスタンスになりますか?
参照vsオブジェクトvs型
私にとって重要なのは、オブジェクトとその参照の違いを理解すること、つまりオブジェクトとそのタイプの違いを理解することです。
Javaでオブジェクトを作成するとき、その本質を宣言します。しかし、Javaの任意のオブジェクトは複数のタイプ。を持っている可能性が高い。 、配列)。
特に参照型の場合、クラス階層によりサブタイプ規則が決まります。たとえば、あなたの例ではすべてのトラックは大型車両です、およびすべての大型車両は車両です sです。したがって、このis-a関係の階層は、トラックに複数の互換性のあるタイプがあることを示しています。
Truck
を作成するとき、それにアクセスするための「参照」を定義します。この参照には、これらの互換性のあるタイプのいずれかが必要です。
Truck t = new Truck(); //or
HeavyVehicle hv = new Truck(); //or
Vehicle h = new Truck() //or
Object o = new Truck();
したがって、ここで重要なのは、オブジェクトへの参照はオブジェクトそのものではないという認識です。作成されるオブジェクトの性質は決して変わりません。しかし、さまざまな種類の互換性のある参照を使用して、オブジェクトにアクセスできます。これは、ここでのポリモーフィズムの機能の1つです。異なる「互換性のある」型の参照を介して、同じオブジェクトにアクセスできます。
あらゆる種類のキャストを行うとき、異なる種類の参照間のこの互換性の性質を単純に想定しています。
参照変換または拡張参照変換
タイプTruck
の参照を持つことで、すべてのトラックは車両であるため、タイプVehicle
の参照と常に互換性があると簡単に結論付けることができます。したがって、明示的なキャストを使用せずに、参照をアップキャストできます。
Truck t = new Truck();
Vehicle v = t;
これは、 拡大参照変換 とも呼ばれます。これは、基本的に型階層を上に行くと、型がより一般的になるためです。
必要に応じて、ここで明示的なキャストを使用できますが、不要です。 t
とv
によって参照されている実際のオブジェクトは同じであることがわかります。これは、常にTruck
です。
ダウンキャストまたはナローイング参照変換
現在、タイプVechicle
の参照を持っているため、実際にTruck
を参照していると「安全に」結論付けることはできません。結局、他の形式の車両も参照する可能性があります。例えば
Vehicle v = new Sedan(); //a light vehicle
参照している特定のオブジェクトを知らずにコードのどこかにv
参照が見つかった場合、それがTruck
を指しているかSedan
を指しているか、または他の種類の車両。
コンパイラーは、参照されているオブジェクトの真の性質については保証できないことをよく知っています。しかし、プログラマーは、コードを読むことで、自分が何をしているかを確信しているかもしれません。上記の場合のように、Vehicle v
がSedan
を参照していることが明確にわかります。
そのような場合、ダウンキャストを行うことができます。型階層を下っていくので、このように呼び出します。また、これを 縮小参照変換 と呼びます。私たちは言うことができます
Sedan s = (Sedan) v;
これは常に明示的なキャストを必要とします。これは、コンパイラがこれが安全であると確信できないためです。そのため、これはプログラマに「自分が何をしているのか確信がありますか?」コンパイラーに嘘をつくと、実行時にこのコードが実行されたときにClassCastException
を受け取ります。
その他の種類のサブタイプ規則
Javaにはサブタイプのルールが他にもあります。たとえば、式の数値を自動的に強制する数値プロモーションと呼ばれる概念もあります。のように
double d = 5 + 6.0;
この場合、整数と倍精度の2つの異なるタイプで構成される式は、式を評価する前に整数を倍精度にアップキャスト/強制し、倍精度値を生成します。
プリミティブなアップキャストとダウンキャストを行うこともできます。のように
int a = 10;
double b = a; //upcasting
int c = (int) b; //downcasting
これらの場合、情報が失われる可能性がある場合、明示的なキャストが必要です。
配列の場合のように、一部のサブタイプ規則はそれほど明白ではない場合があります。たとえば、すべての参照配列はObject[]
のサブタイプですが、プリミティブ配列はそうではありません。
そして、ジェネリックの場合、特にsuper
やextends
などのワイルドカードを使用すると、事態はさらに複雑になります。のように
List<Integer> a = new ArrayList<>();
List<? extends Number> b = a;
List<Object> c = new ArrayList<>();
List<? super Number> d = c;
b
のタイプは、a
のタイプのサブタイプです。また、d
のタイプは、c
のタイプのサブタイプです。
また、ボクシングとボックス解除には、いくつかのキャスティングルールが適用されます(これもまた、私の意見では強制の一種です)。
正解です。オブジェクトは、そのクラス、その親クラスの一部、またはそのオブジェクトまたはその親が実装するインターフェイスにのみ正常にキャストできます。親クラスまたはインターフェイスの一部にキャストした場合、元の型にキャストできます。
それ以外の場合(ソースで使用できる場合)、ランタイムClassCastExceptionが発生します。
キャストは通常、同じフィールドまたは同じタイプのコレクション(たとえば車両)に(同じインターフェースまたは親クラスのすべての車など)異なるものを格納できるようにするために使用されます。同じように。
その後、完全なアクセス権を取得する場合は、それらを元に戻すことができます(例:Vehicle to Truck)
この例では、最後のステートメントが無効であり、コメントが単に間違っていると確信しています。
コードの最後の行は、例外なく正常にコンパイルおよび実行されます。それが行うことは完全に合法です。
hVは最初、タイプHeavyVehicleのオブジェクトを参照します(このオブジェクトをh1と呼びましょう):
static HeavyVehicle hV = new HeavyVehicle(); // hV now refers to h1.
後で、hVがTruck型の別のオブジェクトを参照するようにします(このオブジェクトをt1と呼びます)。
hV = T; // hV now refers to t1.
最後に、Tがt1を参照するようにします。
T = (Truck) hV; // T now refers to t1.
Tはすでにt1を参照しているため、このステートメントは何も変更しませんでした。
HvにTruckクラスのスーパークラスであるHeavyVehicleクラスのインスタンスが既に割り当てられている場合、HeavyVehicleクラスから拡張されるTruckというより具体的なサブクラスにこのフィールドを型キャストするにはどうすればよいですか?
最後の行に到達するまでに、hVはHeavyVehicleのインスタンスを参照しなくなりました。 Truckのインスタンスを指します。 TruckのインスタンスをCasting型にキャストしても問題はありません。
つまり、オブジェクトは、スーパークラスまたは実際にインスタンス化されたクラスと同じクラスとしてのみキャストできます。これは正しいですか、ここで間違っていますか?
基本的に、はい。ただし、オブジェクト自体をオブジェクトを参照する変数と混同しないでください。下記参照。
明示的なキャストにより、オブジェクトの実際の型(宣言された型だけでなく)が何らかの形で変更されるため、このオブジェクトはもはやHeavyVehicleクラスのインスタンスではなく、Truckクラスのインスタンスになりますか?
いいえ。作成されたオブジェクトは、そのタイプを変更できません。別のクラスのインスタンスになることはできません。
繰り返しになりますが、最後の行では何も変わりませんでした。 Tはその行の前のt1を参照し、その後のt1を参照します。
では、なぜ最後の行で明示的なキャスト(トラック)が必要なのでしょうか?私たちは基本的に、コンパイラを支援するだけです。
Weその時点で、hVはTruck型のオブジェクトを参照しているため、Truck型のオブジェクトを変数Tに割り当てることは問題ありません。 compilerは、それを知るほど賢くありません。コンパイラーは、その行に到達して割り当てを行おうとすると、それを待っているトラックのインスタンスを見つけるという保証を望んでいます。
そのようなTruckオブジェクトからHeavyVehicleへのキャストを行う場合:
Truck truck = new Truck()
HeavyVehicle hv = truck;
オブジェクトはまだトラックですが、HeavyVehicle参照を使用してheavyVehicleのメソッドとフィールドにのみアクセスできます。再びトラックにダウンキャストすると、すべてのトラックのメソッドとフィールドを再び使用できます。
Truck truck = new Truck()
HeavyVehicle hv = truck;
Truck anotherTruckReference = (Truck) hv; // Explicit Cast is needed here
ダウンキャストする実際のオブジェクトがトラックではない場合、次の例のようにClassCastExceptionがスローされます。
HeavyVehicle hv = new HeavyVehicle();
Truck tr = (Truck) hv; // This code compiles but will throw a ClasscastException
実際のオブジェクトは正しいクラスではなく、スーパークラスのオブジェクト(HeavyVehicle)であるため、例外がスローされます
上記のコードはコンパイルして正常に実行されます。上記のコードを変更し、次の行を追加しますSystem.out.println(T.name);
これにより、hVオブジェクトをTruckとしてダウンキャストした後、オブジェクトTを使用していないことが確認されます。
現在、コードではダウンキャスト後にTを使用していないため、すべてが正常に機能しています。
これは、hVをトラックとして明示的にキャストすることにより、プログラマーがオブジェクトをキャストしたとして、どのオブジェクトが何にキャストされているかを認識していることを考慮すると、コンパイラーが文句を言うからです。
ただし、実行時にJVMはキャストを正当化できず、ClassCastException "HeavyVehicleをトラックにキャストできません"をスローします。
上記のいくつかのポイントをわかりやすく説明するために、問題のコードを変更し、次のようにインラインコメント(実際の出力を含む)でコードを追加します。
class Vehicle {
String name;
Vehicle() {
name = "Vehicle";
}
}
class HeavyVehicle extends Vehicle {
HeavyVehicle() {
name = "HeavyVehicle";
}
}
class Truck extends HeavyVehicle {
Truck() {
name = "Truck";
}
}
class LightVehicle extends Vehicle {
LightVehicle() {
name = "LightVehicle";
}
}
public class InstanceOfExample {
static boolean result;
static HeavyVehicle hV = new HeavyVehicle();
static Truck T = new Truck();
static HeavyVehicle hv2 = null;
public static void main(String[] args) {
result = hV instanceof HeavyVehicle;
System.out.print("hV is a HeavyVehicle: " + result + "\n"); // true
result = T instanceof HeavyVehicle;
System.out.print("T is a HeavyVehicle: " + result + "\n"); // true
// But the following is in error.
// T = hV; // error - HeavyVehicle cannot be converted to Truck because all hV's are not trucks.
result = hV instanceof Truck;
System.out.print("hV is a Truck: " + result + "\n"); // false
hV = T; // Sucessful Cast form child to parent.
result = hV instanceof Truck; // This only means that hV now points to a Truck object.
System.out.print("hV is a Truck: " + result + "\n"); // true
T = (Truck) hV; // Sucessful Explicit Cast form parent to child. Now T points to both HeavyVehicle and Truck.
// And also hV points to both Truck and HeavyVehicle. Check the following codes and results.
result = hV instanceof Truck;
System.out.print("hV is a Truck: " + result + "\n"); // true
result = hV instanceof HeavyVehicle;
System.out.print("hV is a HeavyVehicle: " + result + "\n"); // true
result = hV instanceof HeavyVehicle;
System.out.print("hV is a HeavyVehicle: " + result + "\n"); // true
result = hv2 instanceof HeavyVehicle;
System.out.print("hv2 is a HeavyVehicle: " + result + "\n"); // false
}
}