第5章 繼承
1.然而, 超類中的有些方法對子類 Manager 並不一定適用 。 具體來說 , Manager 類中的getSalary 方法應該返回薪水和獎金的總和。 為此 , 需要提供一個新的方法來覆蓋 ( override )超類中的這個方法 :
public double getSalaryO
{
return salary + bonus ; / / won ' t work
}
然而, 這個方法並不能執行 。 這是因為 Manager 類的 getSalary 方法不能夠直接地訪問超類的私有域。 也就是說 , 儘管每個Manager 物件都擁有一個名為 salary 的域 , 但在 Manager 類的getSalary 方法中並不能夠直接地訪問 salary 域 。 只有 Employee 類的方法才能夠訪問私有部分。 如果Manager 類的方法一定要訪問私有域 , 就必須藉助於公有的介面 , Employee 類中的公有方法 getSalary 正是這樣一個介面 。
現在, 再試一下 。 將對 salary域的訪問替換成呼叫 getSalary 方法。
public double getSalaryO
{
double baseSalary = getSalaryO ; / / still won ' t work
return baseSalary + bonus ;
}
上面這段程式碼仍然不能執行。 問題出現在呼叫getSalary 的語句上, 這是因為 Manager 類
也有一個 getSalary 方法 ( 就是正在實現的這個方法 ), 所以這條語句將會導致無限次地呼叫自己, 直到整個程式崩潰為止 。這裡需要指出 : 我們希望呼叫超類 Employee 中的 getSalary 方法 , 而不是當前類的這個方法。 為此 ,可以使用特定的關鍵字 super 解決這個問題 :
super . getSalaryO
上述語句呼叫的是 Employee 類中的 getSalary 方法 。 下面是 Manager 類中 getSalary 方法的正確書寫格式 :
public double getSalaryO
{
double baseSalary = super . getSalaryO *
return baseSalary + bonus ;
}
2、 註釋 : 有些人認為 super 與 this 引用是類似的概念 , 實際上 , 這樣比較並不太恰當 。 這是因為 super 不是一個物件的引用 , 不能將 super 賦給另一個物件變數,它只是一個指示編譯器呼叫超類方法的特殊關鍵字。
3、正像前面所看到的那樣 , 在子類中可以增加域 、 增加方法或覆蓋超類的方法, 然而絕對不能刪除繼承的任何域和方法。
4、 使用super 呼叫構造器的語句必須是子類構造器的第一條語句 。
5、在 Java 程式設計語言中, 物件變數是多型的 。 一個Employee 變數既可以引用一個
Employee 類物件 , 也可以引用一個 Employee 類的任何一個子類的物件。
Manager boss = new Manager ( . . . ) ;
Employee [ ] staff = new Employee [ 3 ] ;
staff [ 0 ] = boss ;
在這個例子中, 變數 staff [ 0 ] 與 boss 引用同一個物件。 但編譯器將staff [ 0 ] 看成 Employee 物件 。這意味著 , 可以這樣呼叫
boss . setBonus ( 5000 ) ; // OK
但不能這樣呼叫
staff [ 0 ] . setBonus ( 5000 ) ; / / Error
這是因為 staff [ 0 ] 宣告的型別是 Employee , 而 seffionus 不是 Employee 類的方法。
6、然而, 不能將一個超類的引用賦給子類變數 。這條要重點理解,子類is a超類,但是超類is not a子類。
7、 應該養成這樣一個良好的程式設計習慣 : 在進行型別轉換之前, 先檢視一下是否能夠成功地轉換 。 這個過程簡單地使用instanceof 操作符就可以實現 。 例如 :
if ( staff [ 1 ] instanceof Manager )
{
boss = ( Manager ) staff [ 1 ] :
}
綜上所述 :
• 只能在繼承層次內進行型別轉換 。
• 在將超類轉換成子類之前 , 應該使用instanceof 進行檢查。
8、為了提高程式的清晰度 , 包含一個或多個抽象方法的類本身必須被宣告為抽象的。
9、類即使不含抽象方法, 也可以將類宣告為抽象類 。
10、可以定義一個抽象類的物件變數 , 但是它只能引用非抽象子類的物件 。
11、在 Java 中, 只有基本型別 ( primitive types )不是物件。所有的陣列型別, 不管是物件陣列還是基本型別的陣列都擴充套件了 Object 類 。
12、警告 : 只有 i 小於或等於陣列列表的大小時 , 才能夠呼叫 list . set ( i , x ) 。 例如 , 下面這段程式碼是錯誤的 :
ArrayList < Employee > list = new ArrayListo ( 100 ) ; // capacity 100, size0
list . set ( 0 , x ) ; // no element 0 yet
使用 add 方法為陣列新增新元素 , 而不要使用 set 方法 , 它只能替換陣列中已經存在
的元素內容。
13、C + + 註釋 : 事實上, Java 中的受保護部分對所有子類及同一個包中的所有其他類都可見 。這與 c + + 中的保護機制稍有不同 , Java 中的 protected 概念要比 C + + 中的安全性差 。下面歸納一下 Java 用於控制可見性的 4 個訪問修飾符 :
1 ) 僅對本類可見 private。
2 ) 對所有類可見 public :
3 ) 對本包和所有子類可見 protected。
4 ) 對本包可見 — 預設 ( 很遺憾 ) ,不需要修飾符。
關於第三點的理解重中之重:
然而, 在有些時候 , 人們希望超類中的某些方法允許被子類訪問 ,或允許子類的方法訪問超類的某個域。 為此 , 需要將這些方法或域宣告為 protected 。 例如 , 如果將超類Employee中的 hireDay 宣告為 proteced , 而不是私有的 , Manager 中的方法就可以直接地訪問它 。
不過, Manager 類中的方法只能夠訪問Manager 物件中的 hireDay 域 , 而不能訪問其他Employee 物件中的這個域 。 這種限制有助於避免濫用受保護機制 , 使得子類只能獲得訪問受保護。
14、將方法或類宣告為 final 主要目的是 : 確保它們不會在子類中改變語義。 例如 , Calendar類中的 getTime 和 setTime 方法都宣告為 final。 這表明 Calendar 類的設計者負責實現 Date 類與日曆狀態之間的轉換 , 而不允許子類處理這些問題。 同樣地 , String 類也是 final 類 , 這意味著不允許任何人定義 String 的子類。 換言之 , 如果有一個 String的引用, 它引用的一定是一個 String 物件, 而不可能是其他類的物件 。
15、對於 final 域來說 , 構造物件之後就不允許改變它們的值了 。 不過, 如果將一個類宣告為final , 只有其中的方法自動地成為 final ,而不包括域。
16、Object 類中的 equals 方法必須重點掌握:
利用下面這個示例演示equals 方法的實現機制 :
public class Employee
public boolean equals ( Object otherObject ){
/ / a quick test to see if the objects are identical
if ( this = = otherObject ) return true ;
/ / must return false if the explicit parameter is null
if ( otherObject = = null ) return false ;
// if the classes don ' t match , they can ' t be equal
if ( getClassO ! = otherObject . getClass ( ))
return false ;
// now we know otherObject is a non - null Employee
Employee other = ( Employee ) otherObject ;
// test whether the fields have identical values
return name . equals ( other . name ) & & salary = other , sal ary
& & hi reDay . equals ( other , hi reDay ) :
}
}
這段例程有考慮不周全之處:
為了防備 name 或 hireDay 可能為 null 的情況 , 需要使用 Objects . equals 方法。如
果兩個引數都為 null , Objects . equals ( a, b )呼叫將返回 true ; 如果其中一個引數為 null ,則返回 false ; 否則 , 如果兩個引數都不為 null , 則呼叫 a . equals ( b )。利用這個方法 ,Employee . equals 方法的最後一條語句要改寫為 :
return Objects . equals ( name , other . name )
& & salary = = other . sal ary
& & Object . equals ( hireDay , other . hireDay ) ;
此段判等程式最重要的要注意一點,判斷兩個物件是否同屬於一個類:
if ( getClassO ! = otherObject . getClass ( ))
return false ;
16、*java物件不同類物件判等重中之重:
在比較一個類是否和另一個類屬於同一個類例項的時候,我們通常可以採用instanceof和getClass兩種方法通過兩者是否相等來判斷,但是兩者在判斷上面是有很大的差別:
instanceof:判斷某物件是否屬於某類或者某類的派生類。繼承超類的子類物件也認為型別相同。
getClass:就是判斷某物件是否屬於同一類,僅對比當前所屬類,不考慮繼承關係。
17、關於java equals判等的四大性質要求:
自反性, 對稱性, 傳遞性, 一致性本書5.2.2有極其詳細的講解。之後有時間重點深入研究。
18、編寫完美equals方法的規則:
1 ) 顯式引數命名為 otherObject , 稍後需要將它轉換成另一個叫做 other 的變數 。
2 ) 檢測 this 與 otherObject 是否引用同一個物件 :
if ( this = otherObject ) return true ;
這條語句只是一個優化。 實際上 , 這是一種經常採用的形式 。 因為計算這個等式要比一個一個地比較類中的域所付出的代價小得多 。
3 ) 檢測 otherObject 是否為 null , 如 果 為 null , 返 回 false。 這項檢測是很必要的 。
if ( otherObject = null ) return false ;
4 ) 比較 this 與 otherObject 是否屬於同一個類。 如果 equals 的語義在每個子類中有所改
變, 就使用 getClass 檢測 :
if ( getClass ( ) ! = otherObject . getCIassO ) return false ;
如果所有的子類都擁有統一的語義, 就使用instanceof 檢測 :
if ( ! ( otherObject instanceof ClassName ) ) return false ;
5 ) 將 otherObject 轉換為相應的類型別變數 :
ClassName other = ( ClassName ) otherObject
6 ) 現在開始對所有需要比較的域進行比較了 。 使用 = 比較基本型別域 , 使用 equals 比
較物件域。 如果所有的域都匹配 , 就返回true ; 否 則 返 回 false。
return fieldl = = other . field
& & Objects . equals ( fie 1 d 2 , other . field 2 )
如果在子類中重新定義 equals , 就要在其中包含呼叫 super . equals ( other ) 。
19、關於equals必錯點:
警告 : 下面是實現 equals 方法的一種常見的錯誤 。 可以找到其中的問題嗎 ?
public class Employee
{
public boolean equals ( Employee other )
{
return other ! = null
& & getClassO == other . getClass 0
&& Objects . equals ( name , other . name )
&& salary == other.salary
& & Objects.equals ( hireDay , other . hireDay ) ;
這個方法宣告的顯式引數型別是 Employee。 其結果並沒有覆蓋Object 類的 equals 方
法,而是定義了一個完全無關的方法。
為了避免發生型別錯誤 , 可以使用 @ Override 對覆蓋超類的方法進行標記 :
© Override public boolean equals ( Object other )
如果出現了錯誤 , 並且正在定義一個新方法 , 編譯器就會給出錯誤報告
。 例如 ,假設將下面的宣告新增到 Employee 類中 :
Override public boolean equals ( Employee other )
就會看到一個錯誤報告,這是因為這個方法並沒有覆蓋超類 Object 中的任何方法。
20、關於Object.equals相關API:
java . util . Arrays 1.2:
•static boolean equals ( type [ ] a , type [ ] b ) 5.0如果兩個陣列長度相同 , 並且在對應的位置上資料元素也均相同 , 將返回 true。 陣列的兀素型別可以是 Object 、 int 、 long
、 short 、 char 、 byte 、boolean 、 float 或 double。
java . util . Objects 7:
• static boolean equals ( Object a , Object b )
如果 a 和 b 都為 null , 返回 true ; 如果只有其中之一為 null , 則返回 false ; 否則返回
a . equals ( b )
21、String 類使用下列演算法計算雜湊碼 :
int hash = 0 ;
for ( int i = 0 ; i < length 0 ; i + + )
hash = 31 * hash + charAt ( i ) ;
22、由於 hashCode 方法定義在 Object 類中, 因此每個物件都有一個預設的雜湊碼 , 其值為物件的儲存地址。
23、來看下面這個例子 。
String s = " Ok " ;
StringBuilder sb = new StringBuilder ( s ) ;
System . out . println ( s . hashCode ( ) + " " + sb . hashCode ( ) ) ;
String t = new String ( " Ok " ) ;
StringBuilder tb = new StringBuilder ⑴ ;
System . out . println ( t . hashCode ( ) + " ’’ + tb . hashCode ( ) ) ;
請注意 , 字串 s 與 t 擁有相同的雜湊碼, 這是因為字串的雜湊碼是由內容匯出
的。 而字串緩衝 sb 與tb 卻有著不同的雜湊碼,這是因為在 StringBuffer 類中沒有定義
hashCode 方法, 它的雜湊碼是由Object 類的預設 hashCode 方法匯出的物件儲存地址。
24、需要組合多個雜湊值時 , 可以呼叫 ObjeCtS . hash 並提供多個引數 。 這個方法會對各個引數呼叫 Objects . hashCode, 並組合這些雜湊值 。 這樣 Employee . hashCode 方
法可以簡單地寫為 :
public int hashCodeO
{
return Objects , hash ( name , salary , hireDay ) ;
}
Equals 與 hashCode 的定義必須一致 :
如果 x . equals ( y ) 返回 true , 那麼 x . hashCode ( ) 就必須與 y . hashCode ( ) 具有相同的值。
例如 ,
如果用定義的 Employee . equals 比較僱員的 ID, 那麼 hashCode 方法就需要雜湊 ID
, 而不是僱員的姓名或儲存地址 。
25、Object.hashCode 相關API:
java . util . Object 1.0
• int hashCode ( )
返回物件的雜湊碼。 雜湊碼可以是任意的整數 , 包括正數或負數 。 兩個相等的物件要
求返回相等的雜湊碼。
java . util . Objects 7
• static int hash ( Object . … objects )
返回一個雜湊碼, 由提供的所有物件的雜湊碼組合而得到 。
• static int hashCode ( Object a )
如果 a 為 null 返回 0, 否則返回a . hashCode ( ) 。
java . lang . ( lnteger | Long | Short | Byte | Double | Float | Character | Boolean ) 1.0
• staticint hashCode ( ( int 11 ong | short | byte | double | f 1 oat | char | boolean ) value ) 8
返回給定值的雜湊碼。
java . utii . Arrays 1.2
• static int hashCode ( type [ ] a ) 5.0
計算陣列 a 的雜湊碼。 組成這個陣列的元素型別可以是 object , int , long ,short , char ,
byte , boolean , float 或 double。
26、關於toString之後再研究。
27、
java . lang . Object 1.0
•Class getClass ( )
返回包含物件資訊的類物件 。
•boolean equals ( Object otherObject )//預設的Object類中的equals方法
比較兩個物件是否相等 , 如果兩個物件指向同一塊儲存區域 , 方法返回 true ; 否 則 方
法返回 false。 在自定義的類中 ,應該覆蓋這個方法。
•String toString ( )
返冋描述該物件值的字串。 在自定義的類中 , 應該覆蓋這個方法 。
java . lang . Class 1.0
•String getName ( )
返回這個類的名字。
•Class getSuperclass ( )
以 Class 物件的形式返回這個類的超類資訊。
28、java高階版動態陣列ArrayList建立相關API:
java . util . ArrayList < E > 1.2
• ArrayList < E > ()
構造一個空陣列列表 。
• ArrayList < E > ( i nt initialCapacity )
用指定容量構造一個空陣列列表 。引數 : initalCapacity 陣列列表的最初容量
• boolean add ( E obj )
在陣列列表的尾端新增一個元素 。 永遠返回 true。引數 : obj 新增的元素
• int size ( )
返回儲存在陣列列表中的當前元素數量。( 這個值將小於或等於陣列列表的容量。)
• void ensureCapacity ( int capacity )
確保陣列列表在不重新分配儲存空間的情況下就能夠儲存給定數量的元素。
引數 : capacity 需要的儲存容量
• void trimToSize ( )
將陣列列表的儲存容量削減到當前尺寸。多餘的空間將由java虛擬機器自動垃圾回收。
29、警告 : 只有 i 小於或等於陣列列表的實際已用大小時(不是分配大小) , 才能夠呼叫 list . set ( i , x ) 。 例如 , 下面這段程式碼是錯誤的 :
ArrayList < Employee > list = new ArrayListo ( 100 ) ; // capacity 100, size 0
list . set ( 0 , x ) ; // no element 0 yet
使用 add 方法為陣列新增新元素 , 而不要使用 set 方法 , 它只能替換陣列中已經存在
的元素內容。
30、java高階版動態陣列ArrayList使用相關API:
java . util . ArrayList < T > 1.2
•void set ( int index E obj )
設定陣列列表指定位置的元素值 , 這個操作將覆蓋這個位置的原有內容。
引數 : index 位置 ( 必須介於 0 ~ size ( ) - 1 之間 ) obj 新的值
•E get ( int index )
獲得指定位置的元素值。
引數 : index 獲得的元素位置 ( 必須介於 0 ~ size ( ) - 1 之間 )
•void add ( int index , E obj )
向後移動元素, 以便插入元素 。
引數 : index 插入位置 ( 必須介於 0 〜 size ( ) - 1 之間 )obj 新元素
•E removed int index )
刪除一個元素 , 並將後面的元素向前移動。 被刪除的元素由返回值返回 。
引數 : index 被刪除的元素位置 ( 必須介於 0 〜 size ( ) - 1 之間 )
31、關於型別化陣列和原始陣列列表的相容性問題以後遇到再深入研究。
32、假設想定義一個整型陣列列表。 而尖括號中的型別引數不允許是基本型別 , 也就是說 ,不允許寫成 ArrayList < int >。 這裡就用到了 Integer 物件包裝器類 。 我們可以宣告一個Integer物件的陣列列表。
ArrayList < Integer > list = new ArrayList <> ( ) ;
33、關於java 物件包裝器與自動裝箱自動拆箱重中之重:
(1)、物件包裝器類是不可變的 , 即一旦構造了包裝器 , 就不允許更改包裝在其中的值。對這句話的理解重中之重,一開始沒太理解好,結合下面的例子重點分析。
(2)、在算術表示式中也能夠自動地裝箱和拆箱。 例如 , 可以將自增操作符應用於一個包裝器引用 :
Integer n = 3 ;
n + + ;
編譯器將自動地插人一條物件拆箱的指令 , 然後進行自增計算 , 最後再將結果裝箱。
表面上看似改變了integer n包裝器內部的數值,其實忽略了n本身不是一個包裝器物件,而是一個包裝器物件的引用,因此一切疑惑都解開了。在執行後自加運算的時候,編譯器先是自動拆箱,將包裝器內部的數值3提取到int變數中,然後執行自加運算,之後,再次新建一個integer包裝器物件,並且將陣列4自動裝箱,然後將包裝器引用n指向新建的包裝器物件,從而實現表面上的n++操作。
(3)、非常重要的一些數值包裝器類的常用API:
java . lang . Integer 1.0
• int intValue ()
以 int 的形式返回 Integer 物件的值 ( 在 Number 類中覆蓋了 intValue 方法 )。
• static String toString ( int i )
以一個新 String 物件的形式返回給定數值 i 的十進位制表示。
• static String toString ( int i , int radix )
返回數值 i 的基於給定 radix 引數進位制的表示。
• static int parselnt ( String s )
• static int parseInt ( String s , int radix )
返回字串 s 表示的整型數值, 給定字串表示的是十進位制的整數 ( 第一種方法 ) ,或者是 radix 引數進位制的整數 ( 第二種方法 )。
• static Integer valueOf ( String s )
• Static Integer value Of ( String s , int radix )
返回用 s 表示的整型數值進行初始化後的一個新 Integer 物件, 給定字串表示的是十
進位制的整數 ( 第一種方法 ), 或者是 radix 引數進位制的整數 ( 第二種方法 ) 。
java . text . NumberFormat 1.1
•Number parse ( String s )
返回數字值, 假設給定的 String 表示了一個數值 。
34、引數可變的方法,列舉類,反射,以後深入研究。