重新精讀《Java 程式設計思想》系列之向上轉型與向下轉型
前言
今天重讀了一下向上轉型與向下轉型,有些新的體會,瞭解了向上轉型的好處,及如何向下轉型。在此分享給大家。
向上轉型
向上轉型是用來表現新類和基類之間的關係。在傳統中,由匯出類轉型成基類,在繼承圖中是向上移動的。因此稱作向上轉型。由於向上轉型是從一個較專用型別向較通用型別轉換,所以總是安全的。也就是說,匯出類是基類的一個超集。它可能比基類含有更多的方法。但他必須具備基類中所含有的方法。
我們來看一個例子。
class Car { public void run() { System.out.println("這是父類run()方法"); } } public class Benz extends Car { public void run() { System.out.println("這是Benz的run()方法"); } public void price() { System.out.println("Benz:800000$"); } public static void main(String[] args) { Car car = new Benz(); car.run(); //car.price();程式報錯 } }
執行後輸出。這是Benz的run()方法。
但是當我們用car這個物件去呼叫Benz類中price這個方法時,就會報錯。
這就是因為我們此處進行的向上轉型,car這個物件雖然指向子類,但是子類由於進行了向上轉型,就失去了使用父類中所沒有的方法的“權利”,在此處就是不能呼叫price()這個方法。
那麼向上轉型到底有什麼用呢,到目前為止我們不僅看不到它的好處,反而發現使用了向上轉型後反而不能呼叫子類所特有的方法了。那麼向上轉型的作用到底是什麼呢,我們一起來看下面的程式碼:
class Car { public void run() { System.out.println("這是父類run()方法"); } public void speed() { System.out.println("speed:0"); } } class BMW extends Car { public void run() { System.out.println("這是BMW的run()方法"); } public void speed() { System.out.println("speed:80"); } } public class Benz extends Car { public void run() { System.out.println("這是Benz的run()方法"); } public void speed() { System.out.println("speed:100"); } public void price() { System.out.println("Benz:800000$"); } public static void main(String[] args) { show(new Benz());//向上轉型實現 show(new BMW()); } public static void show(Car car) {//父類例項作為引數 car.run(); car.speed(); } }
上面程式碼中
public static void main(String[] args) {
show(new Benz());
show(new BMW());
}
public static void show(Car car) {
car.run();
car.speed();
}
就體現了向上轉型的優點,這也體現了Java抽象程式設計的思想。如果此處沒有向上轉型,要實現show每個子類的功能,那麼有幾個子類就要寫多少函式。程式碼如下:
public static void main(String[] args) { show(new Benz()); show(new BMW()); } public static void show(Benz benz) { benz.run(); benz.speed(); } public static void show(BMW bmw) { bmw.run(); bmw.speed(); }
試想一下,一旦有很多子類,那麼這個工作量將會比沒有使用向上轉型大很多。這也表明向上轉型還有個優點就是提高了程式碼的簡潔性。
我們再來一種帶著static的特殊呼叫情況。
public class Animal {
String name = "我是動物";
static int age = 20;
public void eat() {
System.out.println("動物可以吃飯");
}
public static void sleep() {
System.out.println("動物可以睡覺");
}
public void run(){
System.out.println("動物可以奔跑");
}
public static void main(String[] args) {
Animal am = new Dog();
am.eat();
am.sleep();
am.run();
//am.watchdog();這裡會報錯
System.out.println(am.name);
System.out.println(am.age);
}
}
class Dog extends Animal {
String name = "小狗";
static int age = 60;
public void eat() {
System.out.println("小狗可以吃飯");
}
public static void sleep() {
System.out.println("小狗可以睡覺");
}
public void watchdog() {
System.out.println("小狗可以看門");
}
}
執行結果:
但是可以看到程式碼塊中,直接呼叫Dog的watchdog()方法會報錯。
這就是因為我們此處進行的向上轉型,am這個物件雖然指向子類,但是子類由於進行了向上轉型,就失去了使用父類中所沒有的方法的“權利”,在此處就是不能呼叫watchdog()這個方法。
而且結果裡也可以看到,睡覺是引用的父類“Animal”的睡覺方法,這是因為Animal的睡覺方法為靜態方法,可以總結如下:
如果是訪問成員變數,編譯的話就是看父類,運行同樣是看父類。
如果訪問的方法,編譯就看父類,執行則看子類。
如果是靜態方法,編譯和執行都是看父類。
向下轉型
先看一個錯誤的例子
public class Animal {
public void eat(){
System.out.println("Animal eat()");
}
}
public class Dog extends Animal {
@Override
public void eat(){
System.out.println("Dog eat");
}
}
public class Test {
public static void main(String[] args) {
//向下轉型
Animal animal = new Animal();
((Dog)animal).eat();
}
}
執行結果:
Exception in thread "main" java.lang.ClassCastException: com.hello.test.Animal cannot be cast to com.hello.test.Dog
at com.hello.test.Test.main(Test.java:7)
從上述例子來看,Java似乎並不支援向下轉型,真是如此嗎?其實不然,Java同樣支援向下轉型,只是向下轉型是有條件的——只有引用子類物件的父類引用才能被向下轉型為子類物件。也就是說,向下轉型之前,必須先向上轉型。
public class Animal {
public void eat(){
System.out.println("Animal eat()");
}
}
public class Dog extends Animal {
@Override
public void eat(){
System.out.println("Dog eat");
}
}
public class Test {
public static void main(String[] args) {
//向上轉型
Animal animal = new Dog();
//向下轉型
((Dog)animal).eat();
}
}
執行結果:
Dog