Java繼承

1基礎

  1. 定義子類
    Java用extends表示繼承,與C++的:相同。
public class Manager extends Employee
{
}

Java中,所有的繼承都是公有繼承,沒有C++中的私有繼承和保護繼承。

  1. 覆蓋方法
    直接使用同名方法來覆蓋父類的方法。
    使用super.可以指定調用父類的方法,相當於C++中使用 父類名::。
//java
public double getSalary()
{
	double baseSalary=super.getSalary();
	return baseSalary+bonus;
}
//c++
public:
	double getSalary()
	{
		double baseSalary=Employee::getSalary();
		return baseSalary+bonus;
	}

在Java中,方法默認是動態綁定的,不需要聲明爲virtual。如果不希望方法有虛擬特性,可以將其標記爲final的。

  1. 構造函數的調用
    子類構造函數可以使用super來調用父類構造函數,而在C++中,在子類的初始化列表中調用父類構造函數。

使用super調用父類構造函數的語句必須出現在子類構造函數的第一句。

//java
public Manager(String name,double salary,int year, int month,int day)
{
	super(name,salary,year,month,day);
	bonus=0;
}
//c++
Manager(String name,double salary,int year,int month,int day)
	:Employee(name,salary,year,month,day),bonus(0){
	}

如果子類沒有顯示地調用父類的構造函數,將會調用父類的默認構造函數,如果父類沒有默認構造函數,編譯器將報錯。

在構造函數中,可以使用this.調用其它構造函數,該語句也必須出現在第一句,並且不能和super語句同時出現,否則編譯器將報錯

  1. 多態
    在java中,子類數組的引用可以轉換成父類數組的引用, 而不需要採用強制類型轉換。例如如下代碼
Manager[] managers=new Manager[10];
Employee[] staff=managers;
staff[0]=new Employee();

該語句編譯可以通過,但是我們將一個父類對象賦給了一個子類的引用,當使用managers[0].setBonus(1000)的時候,用到了一個不存在的成員變量。

  1. 方法的調用
    對於語句
x.f(param);

Java調用方法的過程如下:
首先,如果x的類型爲C,編譯器將列舉出C類型所有名爲f的方法,以及其父類中訪問屬性爲public名爲f的方法。

第二步,根據參數列表的類型,從所有名爲f的方法中篩選出參數列表符合的方法,(過程中可能涉及到類型轉換)。如果編譯器沒有找到與參數類型匹配的方法,或者發現經過類型轉換後有多個方法與之匹配,就會報告一個錯誤。

第三步,如果是private、static、final、或者構造函數,那麼編譯器可以準確知道該調用哪個方法,這稱爲靜態綁定

第四步,如果是需要動態綁定的方法,虛擬機會調用與x所引用對象的實際類型最合適的那個方法。假設x的實際類型是D,它是C的子類,如果D類中定義了方法f()且參數類型符合,就直接調用它。否則將在D類的父類中尋找合適的f()。虛擬機預先爲每個類創建一個方法表(method table),包括了該類所有的方法和該類的父類所有的方法都在方法表中,在調用該類的方法時直接搜索該類的方法表就知道該調用哪個方法。如果使用了super關鍵字,將會在父類的方法表中搜索方法。

在覆蓋一個方法時,子類方法不能低於超類方法的可見性,如果超類方法是public,子類就一定要是public。

  1. 阻止繼承和覆蓋方法——final關鍵字
final使用位置 作用
成員變量聲明前 該成員變量不可修改(相當於const)
方法聲明前 不可以被覆蓋
類聲明前 不可以被繼承,其中的方法也默認是final的,但變量不是。

Java的內聯是由編譯器自動處理的。

  1. 強制類型轉換
    除了內置類型之間的轉換外,類類型之間也可以進行轉換,其要求如下:
  • 只能在繼承層次內進行類型轉換
  • 在將超類轉換成子類之前,應該使用instanceof進行檢查。
  1. 抽象類
  • 包含一個或多個抽象方法的類本身必須被聲明爲抽象的,使用abstract關鍵字聲明。
  • 抽象類的子類如果實現了它全部的抽象方法,就可以不再是抽象類,否則仍然是抽象類。
  • 類即使不含抽象方法也可以被聲明爲抽象類。
  • 抽象類不能創建實例,但可以定義引用,只能引用到它的具體子類對象上。
  • 在C++中,使用=0標記抽象方法,稱爲純虛函數,含有純虛函數的類就是抽象類。
  1. protected
  • protected標記的方法和成員變量子類可以訪問。
  • 事實上,Java的protected和C++稍有不同,Java中的protected對所有子類及同一個包中的其他類可將。
  • 四種訪問修飾符的可見性
修飾符 可見性
private 僅本類中可見
protected 本包和所有子類中可見
public 所有類可見
默認(不需要修飾符) 本包可見

2 Object——所有類的父類

  1. equals方法
  • Object類中的equals方法判斷兩個對象是否有相同的引用。
  • 可以重寫子類的equals方法使其具有合適的意義。
  1. 相等測試與繼承
  • Java語言規範要求方法具有下面的特性
特性 意義
自反性 x.equals(x)應返回true
對稱性 x.equals(y)和y.equals(x)應返回同樣的結果
傳遞性 如果x.equals(y)&&y.equals(z),則x.equals(z)
一致性 如果x、y引用的對象不變,則x.equals(y)不變
非空性 如果x非空,則x.equals(null)返回false
  • 一個編寫出完美的equals方法的建議
//顯式參數命名爲otherObject
public boolean equals(Object otherObject)
{
	//檢測this與otherObject是否引用到同一個對象
	if(this==otherObject) return true;
	//檢測otherObject是否爲null
	if(otherObject==null) return false;
	//比較this與otherObject是否屬於同一個類。如果equals的語義在每個子類中有所改變,就使用getClass檢測
	if(getClass()!=otherObject.getClass()) return false;
	//如果所有的子類都擁有統一的語義,就使用instanceof檢測
	if(!(otherObject instanceof ClassName)) return false;
	//將otherObject轉換爲相應的類類型變量
	ClassName other=(ClassName)otherObject;
	//比較所有需要比較的域
	return field1==other.field1&&.......;
	//如果在子類中重新定義了equals,就首先調用super.equals(other);
}
  • 在覆蓋equals方法時,參數一定是Object類型的,否則,實際上沒有覆蓋Object的equals方法。爲了避免發生此類錯誤,可以用@Override對覆蓋超類的方法進行標記,告訴編譯器此處覆蓋超類的方法,如果沒有成功覆蓋,編譯器就會給出錯誤報告。
  1. hashCode方法
  • Object類的默認hashCode方法導出對象的存儲地址。
  • 如果重新定義equals方法,就必須重新定義hashCode方法,也就是說如果x.equals(y)返回true,那麼它們的hashCode也應該相等。
  • 可以使用Objects.hash()傳入多個參數,快速返回它們組合的hashCode。
public int hashCode()
{
	//將會返回三者的hashCode組合後的結果
	return Objects.hash(name,salary,hireDay);
}
  • 對於數組,可以使用靜態的Arrays.hashCode方法計算一個hashCode,這個hashCode由數組元素的hashCode組成。
  1. toString方法
  • 絕大多數的toString方法都遵循這樣的格式:類的名字+方括號括起來的域值。
  • 一個建議的toString實現如下:
public String toString()
{
	return getClass().getName()
	+"[name="+name
	+",salary"+salary
	+",hireDay"+hireDay
	+"]";
}
  • 只要對象魚一個字符串通過操作符“+“連接,編譯器就會自動調用其toString方法。""+x相當於x.toString()。
  • Object類的toString方法打印對象所屬類名和散列碼。
  • 數組繼承了Object類的toString方法,可以使用Arrays.toString打印數組,對於多維數組可以使用Arrays.deepToString方法。
  • 強烈建議爲自定義的每一個類增加toString方法。

3 泛型數組列表

ArrayList<T> 與C++的vector<T>類似,不同之處在於不支持下標操作[],而要使用get、set方法來訪問和修改元素。

4 對象包裝器與自動裝箱

有時,需要將int這樣的基本類型轉換爲對象,例如:聲明ArrayList<int>就是不允許的,要使用ArrayList<Integer>(這是什麼鬼特性,懷念STL),這時候就需要把int打包爲Integer類型。各種基本類型對應的包裝器類型如下

基本類型 包裝器類型
int Integer
long Long
floar Float
double Double
short Short
byte Byte
char Character
void Void
boolean Boolean
  • 大部分情況下,編譯器會爲我們自動裝箱和拆箱,我們可以像使用基本類型一樣使用它們。
  • ==運算符有所不同,它比較的是兩個裝箱對象的引用地址是否相同,應使用equals方法。
  • 裝箱對象是不可變的。
  • 裝箱類型是final的。
  • 裝箱類型可以自動類型升級,例如在一個表達式中同時含有Integer和Double時,編譯器會自動將Integer升級爲Double。

5 參數數量可變的方法

意味着方法可接受的參數數量可變,printf就是一個可變參數方法,其定義如下。

public class PrintStream
{
	public PrintStream printf(String fmt,Object...args){return format(fmt,args);
}

其中Object…是java語法的一部分,表示這個方法可以接受任意數量的對象。

下面一段代碼用於計算若干個double數據的最大值:

public static double max(double...values)
{
	double result=Double.NEGATIVE_INFINITY;
	for(double x:values) result=result>x?result:x;
	return result;
}

6 枚舉類

  • 一個示例如下
public enum Size{SMALL,MEDIUM,LARGE,EXTRA_LARGE};
  • 所有枚舉類型都是Enum的子類,繼承了它許多方法。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章