一、對象轉型介紹
對象轉型分為兩種:一種叫向上轉型(父類對象的引用或者叫基類對象的引用指向子類對象,這就是向上轉型),另一種叫向下轉型。轉型的意思是:如把float類型轉成int類型,把double類型轉成float類型,把long類型轉成int類型,這些都叫轉型。把一種形式轉成另外一種形式就叫轉型。除了基礎數據類型的轉型之外(基礎數據類型的轉型:大的可以轉成小的,小的也可以轉成大的。),對象領域里面也有對象之間的轉型。
1.1.對象轉型實例一
1 package javastudy.summary; 2 3 /** 4 * 父類Animal 5 * @author gacl 6 * 7 */ 8 class Animal { 9 10 public String name; 11 12 public Animal(String name) { 13 this.name = name; 14 } 15 } 16 17 /** 18 * 子類Cat繼承Animal 19 * @author gacl 20 * 21 */ 22 class Cat extends Animal { 23 24 /** 25 * Cat添加自己獨有的屬性 26 */ 27 public String eyeColor; 28 29 public Cat(String n, String c) { 30 super(n);//調用父類Animal的構造方法 31 this.eyeColor = c; 32 } 33 } 34 35 /** 36 * 子類Dog繼承Animal 37 * @author gacl 38 * 39 */ 40 class Dog extends Animal { 41 /** 42 * Dog類添加自己特有的屬性 43 */ 44 public String furColor; 45 46 public Dog(String n, String c) { 47 super(n);//調用父類Animal的構造方法 48 this.furColor = c; 49 } 50 51 } 52 53 /** 54 * 下面是這三個類的測試程序 55 * @author gacl 56 * 57 */ 58 public class TestClassCast { 59 60 /** 61 * @param args 62 */ 63 public static void main(String[] args) { 64 65 Animal a = new Animal("name"); 66 Cat c = new Cat("catname","blue"); 67 Dog d = new Dog("dogname", "black"); 68 /** 69 * a instanceof Animal這句話的意思是a是一只動物嗎? 70 * a是Animal這個類里面的是一個實例對象,所以a當然是一只動物,其結果為true。 71 */ 72 System.out.println(String.format("a instanceof Animal的結果是%s",a instanceof Animal));//true 73 /** 74 * c是Cat類的實例對象的引用,即c代表的就是這個實例對象, 75 * 所以“c是一只動物”打印出來的結果也是true。 76 * d也一樣,所以“d是一只動物”打印出來的結果也是true。 77 */ 78 System.out.println(String.format("c instanceof Animal的結果是%s",c instanceof Animal));//true 79 System.out.println(String.format("d instanceof Animal的結果是%s",d instanceof Animal));//true 80 /** 81 * 這里判斷說“動物是一只貓”,不符合邏輯,所以打印出來的結果是false。 82 */ 83 System.out.println(String.format("a instanceof Cat的結果是%s",a instanceof Cat)); 84 /** 85 * 這句話比較有意思了,a本身是Animal類的實例對象的引用, 86 * 但現在這個引用不指向Animal類的實例對象了,而是指向了Dog這個類的一個實例對象了, 87 * 這里也就是父類對象的引用指向了子類的一個實例對象。 88 */ 89 a = new Dog("bigyellow", "yellow"); 90 System.out.println(a.name);//bigyellow 91 /** 92 * 這里的furColor屬性是子類在繼承父類的基礎上新增加的一個屬性,是父類沒有的。 93 * 因此這里使用父類的引用對象a去訪問子類對象里面新增加的成員變量是不允許的, 94 * 因為在編譯器眼里,你a就是Animal類對象的一個引用對象,你只能去訪問Animal類對象里面所具有的name屬性, 95 * 除了Animal類里面的屬性可以訪問以外,其它類里面的成員變量a都沒辦法訪問。 96 * 這里furColor屬性是Dog類里面的屬性,因此你一個Animal類的引用是無法去訪問Dog類里面的成員變量的, 97 * 盡管你a指向的是子類Dog的一個實例對象,但因為子類Dog從父類Animal繼承下來, 98 * 所以new出一個子類對象的時候,這個子類對象里面會包含有一個父類對象, 99 * 因此這個a指向的正是這個子類對象里面的父類對象,因此盡管a是指向Dog類對象的一個引用, 100 * 但是在編譯器眼里你a就是只是一個Animal類的引用對象,你a就是只能訪問Animal類里面所具有的成員變量, 101 * 別的你都訪問不了。 102 * 因此一個父類(基類)對象的引用是不可以訪問其子類對象新增加的成員(屬性和方法)的。 103 */ 104 //System.out.println(a.furColor); 105 System.out.println(String.format("a指向了Dog,a instanceof Animal的結果是%s",a instanceof Animal));//true 106 /** 107 * 這里判斷說“a是一只Dog”是true。 108 * 因為instanceof探索的是實際當中你整個對象到底是什么東西, 109 * 並不是根據你的引用把對象看出什么樣來判斷的。 110 */ 111 System.out.println(String.format("a instanceof Dog的結果是%s",a instanceof Dog));//true 112 /** 113 * 這里使用強制轉換,把指向Animal類的引用對象a轉型成指向Dog類對象的引用, 114 * 這樣轉型后的引用對象d1就可以直接訪問Dog類對象里面的新增的成員了。 115 */ 116 Dog d1 = (Dog)a; 117 System.out.println(d1.furColor);//yellow 118 } 119 120 }
運行結果:
內存分析
在內存中可以看到,指向Dog類實例對象的引用對象a是一個Animal類型的引用類型,這就比較有意思了,Animal類型指向了Dog這個對象,那么,在程序的眼睛里會把這只Dog當成一只普通的Animal,既然是把Dog當成一只普通的Animal,那么Dog類里面聲明的成員變量furColor就不能訪問了,因為Animal類里面沒有這個成員變量。因此,從嚴格意義上來講,這個a眼里只看到了這個子類對象里面的父類對象Animal,因此能訪問得到的也只是這個Animal對象里面的name屬性,而這個Animal對象外面的furColor屬性是訪問不到的,雖然Dog對象確實有這個屬性存在,但a就是看不到,a門縫里看Dog——把Dog看扁了,不知道Dog還有furColor這個屬性存在,因此a訪問不了furColor屬性,因此從嚴格意義上來講,a指向的只是這個Dog對象里面的Animal對象,也就是黃色箭頭指向的那部分,a就只看到了Dog里面這部分,而Dog外面的部分都看不到了。這就是父類引用指向子類對象,父類引用指向子類對象的時候,它看到的只是作為父類的那部分所擁有的屬性和方法,至於作為子類的那部分它沒有看到。
如果真的想訪問Dog對象的furColor屬性,那就采用對象轉型的辦法,把父類對象的引用轉型成子類對象的引用。Dog d1 = (Dog)a;這里采用的就是對象轉型的辦法,把a強制轉換成一只Dog對象的引用,然后將這個引用賦值給Dog型的引用變量d1,這樣d1和a都是指向堆內存里面的Dog對象了,而且d1指向的就是這只Dog所有的部分了,通過這個d1就可以訪問Dog對象里面所有的成員了。
1.2.對象轉型實例二
1 public class TestClassCast { 2 3 public void f(Animal a) { 4 System.out.println(a.name); 5 if (a instanceof Cat) { 6 Cat cat = (Cat)a; 7 System.out.println(cat.eyeColor+" eye"); 8 }else if (a instanceof Dog) { 9 Dog dog = (Dog)a; 10 System.out.println(dog.furColor+" fur"); 11 } 12 } 13 14 /** 15 * @param args 16 */ 17 public static void main(String[] args) { 18 Animal a = new Animal("name"); 19 Cat c = new Cat("catname","blue"); 20 Dog d = new Dog("dogname", "black"); 21 TestClassCast testClassCast = new TestClassCast(); 22 testClassCast.f(a); 23 testClassCast.f(c); 24 testClassCast.f(d); 25 } 26 }
這里的這些代碼是對前面聲明的三個類Animal,Dog,Cat測試的延續,這里我們在TestClassCast這里類里面測試這個三個類,這里我們在TestClassCast類里面new了一個testClassCast對象,為的是調用TestClassCast類里面聲明的f(Animal a)這個方法,這個f()方法里面的參數類型是Animal類型,如果是Animal類型的參數,那么我們可以把這個Animal類型的子類對象作為參數傳進去,這是可以的。如把一只Dog或者是一只Cat丟進f()方法里面這都是可以的,因為Dog和Cat也是Animal。因此當程序執行到testClassCast.f(a);,testClassCast.f(c);,testClassCast.f(d);的時候,因為f()方法里面的參數是Animal類型的,所以我們可以把一個Animal對象傳進去,除此之外,我們還可以直接把從Animal類繼承下來的Dog類和Cat類里面的Dog對象和Cat對象作為實參傳遞過去,即是把Animal類型的子類對象作為參數傳進去。這里就體現出了繼承和父類對象的引用可以指向子類對象的好處了,如果說沒有繼承關系的存在,如果說父類的引用不可以指向子類對象,那么我們就得要在Test類里面定義三個f()方法了,即要定義這樣的f()方法:
f(Animal a)、f(Dog d)、f(Cat c)分別用來處理Animal、Dog和Cat,使用三個方法來處理,將來程序的擴展起來就不是很容易了,因為面向對象可以幫助我們這些年來編程苦苦追求的一個境界是可擴展性比較好。可擴展性比較好的一個典型例子就是說當你建好一個建築之后或者是你寫好這個程序之后,把這個主建築給建好了,將來你要加一些其他的功能的時候,盡量不要去修改主結構,這叫可擴展性好,你蓋了一座大樓,你現在要在大樓的旁邊添加一個廚房,那你在它旁邊一蓋就行了,如果有人告訴你,我添加一個廚房我需要把你整個大樓的主要柱子都給拆了然后再蓋一遍,這你干嗎,肯定不干。如果結構設計成這樣,那就是設計得不好,可擴展性不好。所以這里如果要把f()方法寫成三個重載的f()方法,那么將來我輸出一只鳥的時候又得要添加一個f(Bird b)方法來處理鳥。這樣擴展起來就太麻煩了,因為每處理一只動物都要添加一個新的方法,但是如果存在繼承關系,如果父類對象的引用可以指向子類對象,那擴展起來就簡單了,你可以把處理動物的方法寫在一個方法f(Animal a)里面就夠了,因為所有動物的種類都是從Animal類繼承下來,因此給f()方法傳遞Animal類型的參數的時候可以直接把這個Animal類的子類對象傳進去,這樣不管是要增加什么動物的輸出,我都可以調用f(Animal a)方法去處理,這種擴展性比每次都要增加一個新的處理方法的擴展性要好得多,這就是繼承的一個好處,這就是對象轉型對於可擴展性來的好處:“對象的引用可以指向子類對象”,是因為面向對象的編程里面存在這樣的繼承關系,使得程序的可擴展性比較好。
對象轉型可以使父類對象的引用可以指向子類對象,給程序帶來了比較好的可擴展性:我們可以在一個方法的參數里面定義父類的引用,然后實際當中傳的時候傳的是子類的對象,然后我們再在方法里面判斷這個傳過來的子類對象到底屬於哪個子類,然后再去執行這個子類里面的方法或者調用這個子類里面的成員變量,因此程序的可擴展性比單獨定義好多個方法要好一些。不過這個可擴展性還沒有達到最好,使用多態就可以讓程序的擴展性達到極致。