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關鍵字

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

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

盡管父類的構造方法的訪問修飾符是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關鍵字進行修飾,它不能存在於任何方法體內,也不能直接訪問實例變量和實例方法,需要通過類的實例對象來訪問。

通常我們new一個對象,JVM要經過這樣的初始化順序:父類靜態塊>子類靜態塊>父類屬性>父類構造器>子類屬性>子類構造器,這一系列的工作會消耗大量的內存和cpu。

具體的研究可以參看這里:詳解java中的四種代碼塊

java中的注解

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

按照運行機制來分類

注解按照運行機制來進行划分,可以分為3部分:源碼注解,編譯時注解,運行時注解。

源碼注解:只在源碼.java文件中存在,編譯成.class字節碼文件就不存在了;

編譯時注解:在源碼.java文件和字節碼.class文件中都存在;

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

按照來源來分

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

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM