[英]Explicit type casting example in Java
我在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 並且類型轉換為 (Truck) 的最后一行中,為什么在評論中說這是從父級到子級的成功顯式轉換? 據我所知,轉換(隱式或顯式)只會更改對象的聲明類型,而不是實際類型(它不應該更改,除非您實際為該對象的字段引用分配了一個新類實例)。 如果已經為 hv 分配了一個 HeavyVehicle 類的實例,該類是 Truck 類的超類,那么如何將這個字段類型轉換為一個更具體的子類,稱為 Truck,它從 HeavyVehicle 類擴展而來?
我的理解是,強制轉換的目的是限制對對象(類實例)的某些方法的訪問。 因此,您不能將對象轉換為更具體的類,該類具有比對象實際分配的類更多的方法。 這意味着該對象只能被轉換為超類或與實際實例化它的類相同的類。 這是正確的還是我錯了? 我仍在學習,所以我不確定這是否是正確的看待事物的方式。
我也明白這應該是向下轉換的一個例子,但是如果實際類型沒有這個對象被向下轉換到的類的方法,我不確定這實際上是如何工作的。 顯式轉換是否以某種方式改變了對象的實際類型(不僅僅是聲明的類型),以便該對象不再是 HeavyVehicle 類的實例,而是現在成為 Truck 類的實例?
引用 vs 對象 vs 類型
對我來說,關鍵是理解對象與其引用之間的區別,或者換句話說,就是理解對象與其類型之間的區別。
當我們在 Java 中創建一個對象時,我們聲明了它的真實性質,它永遠不會改變(例如new Truck()
)。 但是 Java 中的任何給定對象都可能有多種類型。 其中一些類型顯然是由類層次結構給出的,另一些則不那么明顯(即泛型、數組)。
特別是對於引用類型,類層次結構規定了子類型規則。 例如,在您的示例中,所有卡車都是重型車輛,所有重型車輛都是車輛。 因此,這種 is-a 關系的層次結構決定了卡車具有多種兼容類型。
當我們創建Truck
,我們定義了一個“引用”來訪問它。 此引用必須具有這些兼容類型之一。
Truck t = new Truck(); //or
HeavyVehicle hv = new Truck(); //or
Vehicle h = new Truck() //or
Object o = new Truck();
所以這里的關鍵點是認識到對對象的引用不是對象本身。 被創建對象的性質永遠不會改變。 但是我們可以使用不同類型的兼容引用來訪問對象。 這是這里多態的特點之一。 可以通過不同“兼容”類型的引用訪問同一對象。
當我們進行任何類型的轉換時,我們只是假設不同類型引用之間的這種兼容性的性質。
向上轉換或擴大參考轉換
現在,有了Truck
類型的引用,我們可以很容易地得出結論,它始終與Vehicle
類型的引用兼容,因為所有 Trucks 都是 Vehicles 。 因此,我們可以向上轉換引用,而不使用顯式轉換。
Truck t = new Truck();
Vehicle v = t;
它也稱為擴展引用轉換,基本上是因為隨着您在類型層次結構中的上升,類型變得更加通用。
如果需要,您可以在此處使用顯式強制轉換,但這是不必要的。 我們可以看到t
和v
引用的實際對象是相同的。 它是,並且永遠是Truck
。
向下轉換或縮小參考轉換
現在,有了Vechicle
類型的引用,我們不能“安全地”得出它實際上引用了Truck
結論。 畢竟它也可能引用其他形式的 Vehicle。 例如
Vehicle v = new Sedan(); //a light vehicle
如果您在代碼中的某處找到v
引用而不知道它引用的是哪個特定對象,則您無法“安全地”論證它是否指向Truck
、 Sedan
或任何其他類型的車輛。
編譯器很清楚它不能保證所引用對象的真實性質。 但是程序員通過閱讀代碼,可以確定他/她在做什么。 與上面的例子一樣,您可以清楚地看到Vehicle v
引用了Sedan
。
在這些情況下,我們可以做一個低頭。 我們這樣稱呼它是因為我們要沿着類型層次結構向下移動。 我們也稱其為收縮引用轉換。 我們可以說
Sedan s = (Sedan) v;
這總是需要顯式轉換,因為編譯器無法確定這是安全的,這就是為什么這就像問程序員,“你確定你在做什么嗎?”。 如果你對編譯器撒謊,你會在運行時拋出一個ClassCastException
,當這段代碼被執行時。
其他類型的子類型規則
Java 中還有其他子類型規則。 例如,還有一個稱為數字提升的概念,它可以自動強制表達式中的數字。 喜歡在
double d = 5 + 6.0;
在這種情況下,由兩種不同類型(整數和雙精度)組成的表達式在計算表達式之前將整數向上轉換/強制轉換為雙精度,從而產生雙精度值。
你也可以做原始的向上轉換和向下轉換。 如
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。
轉換通常用於將不同的東西(相同接口或父類,例如所有汽車)存儲在同一字段或相同類型的集合(例如車輛)中,以便您可以使用他們以同樣的方式。
如果您想獲得完全訪問權限,您可以將它們退回(例如,車輛到卡車)
在這個例子中,我很確定最后一條語句是無效的,注釋是完全錯誤的。
當您像這樣從 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) 的對象
最后一行代碼編譯並成功運行,沒有異常。 它的所作所為是完全合法的。
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 分配了一個 HeavyVehicle 類的實例,該類是 Truck 類的超類,那么如何將這個字段類型轉換為一個更具體的子類,稱為 Truck,它從 HeavyVehicle 類擴展而來?
當我們到達最后一行時,hV 不再是指 HeavyVehicle 的實例。 它指的是 Truck 的一個實例。 將 Truck 的實例轉換為 Truck 類型沒有問題。
這意味着該對象只能被轉換為超類或與實際實例化它的類相同的類。 這是正確的還是我錯了?
基本上,是的,但不要將對象本身與引用該對象的變量混淆。 見下文。
顯式轉換是否以某種方式改變了對象的實際類型(不僅僅是聲明的類型),以便該對象不再是 HeavyVehicle 類的實例,而是現在成為 Truck 類的實例?
不可以。對象一旦被創建,就永遠不能改變它的類型。 它不能成為另一個類的實例。
重申一下,最后一行沒有任何變化。 T 在該行之前指代 t1,在之后指代 t1。
那么為什么最后一行需要顯式轉換 (Truck) 呢? 我們基本上只是幫助編譯器。
到那時,我們知道 hV 指的是 Truck 類型的對象,因此可以將 Truck 類型的對象分配給變量 T。但是編譯器不夠聰明,無法知道這一點。 編譯器希望我們保證,當它到達該行並嘗試進行分配時,它將找到一個 Truck 的實例等待它。
上面的代碼將編譯並運行良好。 現在更改上面的代碼並添加以下行 System.out.println(T.name);
這將確保您在將 hV 對象向下轉換為 Truck 后不使用對象 T。
目前,在您的代碼中,您在向下轉型后沒有使用 T,因此一切正常且工作正常。
這是因為,通過將 hV 顯式轉換為 Truck,編譯器確實會抱怨認為程序員已轉換對象並且知道將什么對象轉換為什么。
但是在運行時 JVM 無法證明轉換的合理性並拋出 ClassCastException “HeavyVehicle cannot be cast to Truck”。
為了更好地說明上面提出的一些觀點,我修改了有問題的代碼,並使用內聯注釋(包括實際輸出)向其中添加了更多代碼,如下所示:
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
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.