1. 程式人生 > >關於JAVA你必須知道的那些事(三):繼承和訪問修飾符

關於JAVA你必須知道的那些事(三):繼承和訪問修飾符

今天乘著還有一些時間,把上次拖欠的面向物件程式設計三大特性中遺留的繼承和多型給簡單說明一下。這一部分還是非常重要的,需要仔細思考。

繼承

繼承:它是一種類與類之間的關係,通過使用已存在的類作為基礎來建立新類。其中已存在的類稱為父類(或基類); 建立的新類稱為子類(或派生類)。簡單的就是子類繼承父類的非私有屬性和方法。

需要注意的是,新定義的類可以選擇繼續使用父類的功能或者自己增加新的資料或新的功能,但不能選擇性地繼承父類。(要麼繼承所有(前提是非私有),要麼就不繼承)

只要能滿足 "A is a B"的關係就可以形成繼承關係,程式碼中是通過 extends 關鍵字來實現繼承的。

特別注意:在java中只能繼承一個父類(也就是單繼承),而且子類可以訪問父類的非私有成員。這個和Python不一樣,Python的繼承可就靈活了。

我們知道子類繼承了父類之後,可以訪問父類的非私有成員;但是父類的私有成員,子類還是無法直接訪問。如果我們想訪問呢?可以通過父類暴露的公有方法來實現間接訪問。

父類物件不可以訪問到子類特有的方法或屬性,同時父類不可以訪問子類特有成員(那怕是公有的成員)

過載

方法過載必須同時滿足以下條件:

  1. 同一個類中;;
  2. 方法名相同,引數列表不同(引數順序、個數、型別);
  3. 方法返回值、訪問修飾符任意;
  4. 與方法的引數名無關。
public void printinfo() {
	System.out.println("方法過載1");
};

public void printinfo(String name) {

	System.out.println("方法過載2");
};

public String printinfo(String name, int age) {
	return "方法過載3";
};

public String printinfo(String age, String name) {
	return "方法過載4";
};

public String printinfo(int age, String name) {
	return "方法過載5";
};

 // 與方法的引數名無關,加上下面的程式碼會和上面的 printinfo(int age, String name)造成重複而報錯:
public String printinfo(int size, String name) {
	return "方法過載5";
};

重寫

方法重寫也必須同時滿足以下條件: 1、在滿足繼承關係的子類中; 2、方法名相同,引數列表相同(引數順序、個數、型別); 3、方法返回值相同或者是子類型別(但不允許是Object型別,可以向下相容,向上是不可以的); 4、訪問修飾符的限定範圍大於等於父類方法。

注意:在子類中是可以定義與父類重名的屬性的,但這並不說明屬性是可以重寫的。

訪問修飾符

在Java裡面一共包含4種訪問修飾符,分別是: 1、private:私有的; 2、預設; 3、protected:受保護的; 4、public:公共的。

其中,private:只允許在本類範圍中進行訪問,離開了當前類就不允許訪問;

預設: 允許在當前類,同包子類/非子類都可呼叫,跨包子類/非子類都不允許;

protected:允許在當前類,同包中的子類/非子類都可以以及跨包子類呼叫。跨包的非子類不允許呼叫。

public:允許在任意位置訪問。

按照前面的順序,自上而下,訪問範圍越來越大;自下而上,限制能力越來越強:

(同包包括同包子類與非子類;子類包括同包子類和跨包子類)

訪問修飾符對方法重寫的影響

子類重寫父類方法時,訪問修飾符是允許改變的,要求是: 子類的訪問範圍必須大於等於父類的訪問範圍。也就是說如果父類訪問修飾符是public,那麼子類的訪問修飾符也必須是public,其他的類似。

繼承的初始化順序

繼承後的初始化順序如下:

父類靜態成員 -> 子類靜態成員 -> 父類物件的構造 -> 子類物件的構造

一個問題: 訪問修飾符影響成員載入順序?靜態成員優先於靜態程式碼塊執行?

訪問修飾符不影響成員載入順序,跟書寫位置有關。如果把靜態程式碼塊寫在靜態變數的前面,那麼先執行靜態程式碼塊。

super關鍵字

如果子類繼承並重寫了父類的方法,那麼我們通常呼叫的就是重寫後的子類方法。如果需要呼叫父類的方法,我們可以使用this.方法來達到這個目的。

當然也可以使用this.屬性來達到訪問父類的非私有屬性的目的。

儘管父類的構造方法的訪問修飾符是public,但是它卻不可以被子類繼承和重寫的。

雖然它兩個不可以,但是它的存在卻是非常必要的,因為子類物件的例項化要依賴於父類物件的構造方法(預設,無參或有參的構造方法)。

如果子類呼叫了自己有參的構造方法,而父類定義了有參和無參的構造方法,程式依然是呼叫父類無參的構造方法。也就是說,我們在子類的構造方法中沒有顯式標註的情況下,預設呼叫父類的無參構造方法,因此父類的無參構造方法很重要,一定要寫,否則會影響子類的物件例項化。

如果子類構造方法中既沒有顯式標註,且父類中沒有無參的構造方法,則引發編譯錯誤。

我們可以使用super(引數)這種形式來呼叫父類允許被訪問的其他構造方法,但是此時super()必須放在子類構造方法有效程式碼的第一行(必須是子類的構造方法(其他方法不行)的第一行(其他行不行))。

也就是說父類在例項化的時候會預設呼叫無參的構造方法(此時你不定義無參的構造方法是可以的),但是如果子類在例項化物件的時候沒有顯示標誌(也就是會預設呼叫父類無參的構造方法),而此時父類其實是不存在無參的構造方法,所以會引發編譯錯誤。

this和super的對比

**this:**當前類物件的引用: 1、訪問當前類的成員方法; 2、訪問當前類的成員屬性; 3、訪問當前類的構造方法; 4、不能在靜態方法中使用;


**super:**父類物件的引用: 1、訪問父類的成員方法; 2、訪問父類的成員屬性; 3、訪問父類的構造方法; 4、不能在靜態方法中使用;


注意:在呼叫構造方法時,this和super不能同時存在(前面說過兩者都要求在第一行)。

Object類

Object類是所有類的父類,這個其實和Python中差不多,在Python裡面也是所有的類都繼承於object這個基類。點這直接檢視api:javase8api

一個類沒有使用extends關鍵字明確標識繼承關係,則預設繼承Object類(包括陣列)。

Class Object is the root of the class hierarchy.
Every class has Object as a superclass. 
All objects, including arrays, implement the methods of this class.

Object類存放於java.lang包中,這個包系統預設會為我們直接載入。

equals用法

如果子類沒有重寫Object類的equals方法,那麼比較的是兩個引用是否指向同一個地址;而String類則重寫了Object類的equals方法,所以比較的是字串的值是否相等。(言外之意,子類可以通過重寫equals方法的形式,改變比較的內容)

因此我們不能這樣說equals比較的兩個物件的值,或者引用地址,但是我們卻可以說"=="比較的卻一定是兩個物件的引用地址。

toString用法

api告訴我們,toString最後返回的是下面這種形式:(包名.類名@記憶體中的雜湊碼)

 getClass().getName() + '@' + Integer.toHexString(hashCode())

同樣的,子類如果沒有重寫Object類的toString方法,那麼則會列印輸出其在記憶體中的雜湊碼;而String類則重寫了Object類的toString方法,所以列印輸出其真實值。(言外之意,子類可以通過重寫toString方法的形式,改變輸出的內容)

還要說明的一點就是輸出物件物件.toString的效果是一樣的,因為直接輸出物件的時候其實是呼叫了物件.toString方法。

Final關鍵字

當我們不希望某些類被繼承,某些方法被重寫或者某些資料被修改時,可以使用final關鍵字來實現這個目的。

如果某個類被final修飾,則表明該類不可以被繼承,該類沒有子類,public final class/final public class都可以,只要是放在class的前面就可以;

如果某個方法被final修飾,則表明該方法不可以被重寫,但是並不影響子類去繼承呼叫它(final不可以修飾構造方法)。

如果某個區域性變數被final修飾,那麼我們可以不用在宣告的同時立馬進行賦值,但是必須在使用之前進行賦值,一旦賦值就不能被修改;

(方法內的區域性變數的作用範圍,從該行開始到所在大括號結束;而類的成員變數的作用範圍取決於它前面的訪問修飾符);

如果某個成員變數被final修飾,我們同樣不需要宣告的同時進行立馬賦值,但是必須在使用之前進行賦值,而且只能在構造方法或者類程式碼塊(構造程式碼塊)中進行賦值,一旦賦值就不能被修改;也就是說類中成員屬性的賦值可以有三種方式: 1. 定義是直接初始化; 2. 構造方法; 3. 構造程式碼塊(類程式碼塊)。

注意: 當具有多個構造方法時,final關鍵字修飾的成員變數如果選擇了在構造方法裡面進行賦值,那麼就需要在所有的構造方法裡面進行賦值,但是不同構造方法是可以賦不同值的

final對資料型別的影響

我們知道java 資料型別分為基本資料型別(byte,short,int,long,float,double,char,boolean) 和 引用資料型別(array,String,interface,自定義的類...)

基本資料型別是可以直接進行賦值的,而引用型別需要例項化該類的物件,然後才能給其物件進行賦值(String這個比較特殊,兩種形式都是可以的)

我們知道基本資料型別在記憶體中存放的是資料本身,而引用資料型別在記憶體中存放的則是物件的引用地址。

下面的例子告訴我們,被final修飾的物件不可以修改它的引用地址,但是屬性卻是可以的:

final Test test=new Test("hello");	
//		test=new Test ();
		Test.key="world";

總結一下就是:基本資料型別的變數其一旦被賦初值,就無法進行修改;而引用型別的變數只是在初始化之後不能再指向另一個物件,但是物件的內容卻是可變的。

因此final可配合static使用,用來修飾方法和變數。通常是用於修飾配置資訊等(因為這類資訊只需要載入一次,而且後面不需要被修改)。言外之意,使用final修飾可以提高效能,但會降低可擴充套件性。

普通程式碼塊,類程式碼塊,構造程式碼塊,靜態程式碼塊區別

程式碼塊都是一對大括號{}所括起來的內容。

普通程式碼塊就是一對大括號{}所括起來的內容,只存在於類的方法之中;

類程式碼塊和構造程式碼塊是一個東西,就是直接在類中進行定義的,前面沒有static進行修飾。構造程式碼塊在建立物件時被呼叫,每次建立物件都會被呼叫,並且構造程式碼塊的執行次序優先於類的構造方法。

靜態程式碼塊前面有static關鍵字進行修飾,它不能存在於任何方法體內,也不能直接訪問例項變數和例項方法,需要通過類的例項物件來訪問。

具體的研究可以參看這裡:詳解java中的四種程式碼塊

java中的註解

註解是JDK1.5版本引入的一個特性, 它可以宣告在包、類、屬性、方法、區域性變數、方法引數等前面,作用就是對這些元素進行說明、註釋。

按照執行機制來分類

註解按照執行機制來進行劃分,可以分為3部分:原始碼註解,編譯時註解,執行時註解。

原始碼註解:只在原始碼.java檔案中存在,編譯成.class位元組碼檔案就不存在了;

編譯時註解:在原始碼.java檔案和位元組碼.class檔案中都存在;

執行時註解:在執行階段還起作用,甚至會影響執行邏輯的註解。(spring框架中的@Autowired依賴注入的這種註解,它實現的就是在程式執行的過程當中自動的將外部傳入的資訊載入進去,它就是一種可以影響程式執行邏輯的執行時註解。)

按照來源來分

註解按照來源來進行劃分,可以分為3部分:JDK註解,第三方註解,自定義註解。

還有一種元註解,它是對註解進行註解的。

不行了,寫著寫著字數又超了,快4000字了,面向物件最後一個特性:多型,我還沒說呢,下次吧,今天得滾去運動了。。。感謝你的賞閱。