在前面的示例中,我們把 Circle 類聲明為 shapes 包的一部分。假設(shè)我們計(jì)劃實(shí)現(xiàn)多個(gè)表示形狀的類:Rectangle、Square、Ellipse、Triangle 等。我們可以在這些表示形狀的類中定義兩個(gè)基本方法:area() 和 circumference()。那么,為了能方便處理由形狀組成的數(shù)組,這些表示形狀的類最好有個(gè)共同的超類 Shape。這樣組織類層次結(jié)構(gòu)的話,每個(gè)形狀對(duì)象,不管具體表示的是什么形狀,都能賦予類型為 Shape 的變量、字段或數(shù)組元素。我們想在 Shape 類中封裝所有形狀共用的功能(例如,area() 和 circumference() 方法)。但是,通用的 Shape 類不表示任何類型的形狀,所以不能為這些方法定義有用的實(shí)現(xiàn)。Java使用抽象方法解決這種問(wèn)題。
Java 允許使用 abstract 修飾符聲明方法,此時(shí)只定義方法但不實(shí)現(xiàn)方法。abstract 修飾的方法沒(méi)有主體,只有一個(gè)簽名和一個(gè)分號(hào)。以下是 abstract 方法和這些方法所在的abstract 類相關(guān)的規(guī)則:
1? ? 只要類中有一個(gè) abstract 方法,那么這個(gè)類本身就自動(dòng)成為 abstract 類,而且必須聲明為 abstract 類,否則會(huì)導(dǎo)致編譯出錯(cuò)。
2? ? abstract 類無(wú)法實(shí)例化。
3? ? abstract 類的子類必須覆蓋超類的每個(gè) abstract 方法并且把這些方法全部實(shí)現(xiàn)(即提供方法主體),才能實(shí)例化。這種類一般叫作具體子類(concrete subclass),目的是強(qiáng)調(diào)它不是抽象類。
4? ? 如果 abstract 類的子類沒(méi)有實(shí)現(xiàn)繼承的所有 abstract 方法,那么這個(gè)子類還是抽象類,而且必須使用 abstract 聲明。
5? ? 使用 static、private 和 final 聲明的方法不能是抽象方法,因?yàn)檫@三種方法在子類中
6? ? 不能覆蓋。類似地,final 類中不能有任何 abstract 方法。就算類中沒(méi)有 abstract 方法,這個(gè)類也能聲明為 abstract。使用這種方式聲明的abstract 類表明實(shí)現(xiàn)的不完整,要交給子類實(shí)現(xiàn)。這種類不能實(shí)例化。
比如Classloader 類,這個(gè)類就沒(méi)有任何抽象方法。
下面通過(guò)一個(gè)示例說(shuō)明這些規(guī)則的運(yùn)作方式。如果定義 Shape 類時(shí)把 area() 和circumference() 聲明為 abstract 方法,那么 Shape 的子類必須實(shí)現(xiàn)這兩個(gè)方法才能實(shí)例化。也就是說(shuō),每個(gè) Shape 對(duì)象都要確保實(shí)現(xiàn)了這兩個(gè)方法。下圖示例展示了如何編寫代碼。在這段代碼中,定義了一個(gè)抽象的 Shape 類和兩個(gè)具體子類:

Shape 類中每個(gè)抽象方法的括號(hào)后面都是分號(hào),沒(méi)有花括號(hào),也沒(méi)定義方法的主體。使用示例中定義的這幾個(gè)類可以編寫如下的代碼:

有兩點(diǎn)要注意:
1? ? Shape 類的子類對(duì)象可以賦值給 Shape 類型數(shù)組中的元素,無(wú)需校正。這又是一個(gè)放大轉(zhuǎn)換引用類型(第 2 章討論過(guò))的例子。
2? ? 即便 Shape 類沒(méi)有定義 area() 和 circumference() 方法的主體,各個(gè) Shape 對(duì)象還是能調(diào)用這兩個(gè)方法。調(diào)用這兩個(gè)方法時(shí),使用虛擬方法查找技術(shù)找到要調(diào)用的方法。因此,圓的面積使用 Circle 類中定義的方法計(jì)算,矩形的面積使用 Rectangle 類中定義的方法計(jì)算。
轉(zhuǎn)換引用類型
對(duì)象可以在不同的引用類型之間轉(zhuǎn)換。和基本類型一樣,引用類型轉(zhuǎn)換可以是放大轉(zhuǎn)換(編譯器自動(dòng)完成),也可以是需要校正的縮小轉(zhuǎn)換(或許運(yùn)行時(shí)還要檢查)。要想理解引用類型的轉(zhuǎn)換,必須理解引用類型組成的層次結(jié)構(gòu),這個(gè)體系叫作類層次結(jié)構(gòu)。
每個(gè) Java 引用類型都擴(kuò)展其他類型,被擴(kuò)展的類型是這個(gè)類型的超類。類型繼承超類的字段和方法,然后定義屬于自己的一些額外的字段和方法。在 Java 中,類層次結(jié)構(gòu)的根是一個(gè)特殊的類,名為 Object。所有 Java 類都直接或間接地?cái)U(kuò)展 Object 類。Object 類定義了一些特殊的方法,所有對(duì)象都能繼承(或覆蓋)這些方法。
預(yù)定義的 String 類和本章前面定義的 Point 類都擴(kuò)展 Object 類。因此,可以說(shuō),所有String 對(duì)象也都是 Object 對(duì)象。也可以說(shuō),所有 Point 對(duì)象都是 Object 對(duì)象。但是,反過(guò)來(lái)說(shuō)就不對(duì)了。我們不能說(shuō)每個(gè) Object 對(duì)象都是 String 對(duì)象,因?yàn)槿缜八?,有些Object 對(duì)象是 Point 對(duì)象。
簡(jiǎn)單理解類層次結(jié)構(gòu)之后,我們可以定義引用類型的轉(zhuǎn)換規(guī)則了:
? 對(duì)象不能轉(zhuǎn)換成不相關(guān)的類型。例如,就算使用校正運(yùn)算符,Java 編譯器也不允許把String 對(duì)象轉(zhuǎn)換成 Point 對(duì)象。
? 對(duì)象可以轉(zhuǎn)換成超類類型,或者任何祖先類類型。這是放大轉(zhuǎn)換,因此不用校正。例如,String 對(duì)象可以賦值給 Object 類型的變量,或者傳入期待 Object 類型參數(shù)的方法。其實(shí)沒(méi)有執(zhí)行轉(zhuǎn)換操作,而是直接把對(duì)象當(dāng)成超類的實(shí)例。這種行為有時(shí)稱為里氏替換原則(Liskov substitution principle),以第一個(gè)明確表述這種行為的計(jì)算機(jī)科學(xué)家 Barbara Liskov 的名字命名。
? 對(duì)象可以轉(zhuǎn)換成子類類型,但這是縮小轉(zhuǎn)換,需要校正。Java 編譯器臨時(shí)允許執(zhí)行這種轉(zhuǎn)換,但 Java 解釋器在運(yùn)行時(shí)會(huì)做檢查,確保轉(zhuǎn)換有效。根據(jù)程序的邏輯,確認(rèn)對(duì)象的確是子類的實(shí)例后才會(huì)把對(duì)象校正成子類類型。否則,解釋器會(huì)拋出ClassCastException 異常。例如,如果把一個(gè) String 對(duì)象賦值給 Object 類型的變量,那么后面可以校正這個(gè)變量的值,再變回 String 類型:
Object o = "string"; // 把String對(duì)象放大轉(zhuǎn)換成Object類型? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? String s = (String) o; // 程序后面再把這個(gè)Object對(duì)象縮小轉(zhuǎn)換成String類型?
數(shù)組是對(duì)象,而且有自己的一套轉(zhuǎn)換規(guī)則。首先,任何數(shù)組都能放大轉(zhuǎn)換成 Object 對(duì)象。帶校正的縮小轉(zhuǎn)換能把這個(gè)對(duì)象轉(zhuǎn)換回?cái)?shù)組。下面是一個(gè)示例:

除了能把數(shù)組轉(zhuǎn)換成對(duì)象之外,如果兩個(gè)數(shù)組的“基類型”是可以相互轉(zhuǎn)換的引用類型,那么數(shù)組還能轉(zhuǎn)換成另一個(gè)類型的數(shù)組。例如:

注意,這些數(shù)組轉(zhuǎn)換規(guī)則只適用于由對(duì)象或數(shù)組組成的數(shù)組?;绢愋偷臄?shù)組不能轉(zhuǎn)換為任何其他數(shù)組類型,就算基本基類型之間能相互轉(zhuǎn)換也不行:
// 就算int類型能放大轉(zhuǎn)換成double類型
// 也不能把int[]類型轉(zhuǎn)換成double[]類型
// 這行代碼會(huì)導(dǎo)致編譯出錯(cuò)
double[] data = new int[] {1,2,3};
//但是,這行代碼是合法的,因?yàn)閕nt[]類型能轉(zhuǎn)換成
Object類型Object[] objects = new int[][] {{1,2},{3,4}};