JAVA學習---面向物件之繼承·多型
這週六我們學習了面向物件的一些知識,包含繼承和多型的。
以下是我學過後的心得總結:
封裝:
即將構造方法,成員變數,成員方法封裝在一起,形成一個類class檔案,實現呼叫。
繼承:
-
繼承概述:
繼承即多個類中存在相同的屬性和行為時,我們可以將這些內容抽取到單獨的一個類中,這個類即父類,剩餘的類則無需再定義這些屬性和行為,只需繼承那個類即可,這些類就是父類的子類。 -
繼承語法:
class 子類名 extends 父類名{}
-
繼承特點:
1.JAVA類只支援單繼承,但支援多層繼承。
2.子類只能繼承父類所有非私有成員(成員方法和成員變數)。
3.子類不能繼承父類的構造方法,但是我們可以通過super關鍵字去訪問父類的構造方法。
4.子類在建立物件時,一定會先初始化父類的資料(執行父類的空參構造)。
5.Object類是所有類的頂層父類,所有類都是直接或間接繼承與它。 -
繼承的利弊:
1.優點:提高了程式碼的複用性和維護性,同時讓類魚類之間產生了關係,這是多型的前提。
2.弊端:類的耦合性增強了。而我們開發的原則是高內聚,低耦合。 -
this和super的區別和應用:
this 代表的是本類物件的引用;
super 代表的父類儲存空間的標識,是父類的引用,可以操作父類的成員。
現在我們來看一個程式碼,它結合了繼承的運用,同時體現了super和this的區別。
public class Fu {
int num = 100;
int num2=50;
public void fu(){
System. out.println("我是父類");
}
}
public class Zi extends Fu {
int num = 10;
int c = 666;
public void show(int num) {
num = 123;
System.out.println(num);
System.out.println(this.num);
//當子類的成員變數和父類重名的時候,可以用一個關鍵super來區分
//this 代表本類的引用。
//super 代表父類空間的一個表示,你可以通過super可以訪問父類的成員變數 成員方法,構造方法
System.out.println(super.num);
System.out.println(c);
System.out.println(num2);
}
public void hehe() {
//呼叫父類的方法
this.fu();
//super訪問父類的成員方法
super.fu();
}
}
public class Test {
public static void main(String[] args) {
//變數的尋找 遵循就近訪問原則。先在區域性範圍找,找到就使用,如果沒找到就去本類成員範圍找,如果還沒找到,就去父類的成員找//
Zi zi = new Zi();
zi.show(1);
zi.hehe();
}
}
從上面的結果看出來子類 Zi 繼承了父類 Fu ,test方法最後new了一個子類物件zi用它呼叫了其自身的show方法和hehe方法,在子類中hehe方法通過this和super方法呼叫訪問fu方法,this是在自己類中找不到了,所以去父類中去找fu方法,super則是直接去父類中尋找fu方法
-
方法重寫:
即子類中出現了和父類一模一樣的宣告方法(方法名,引數列表,返回值型別),也被稱為方法覆蓋,方法重寫。
當子類需要父類的功能,而功能主體子類有自己特有的內容時,就用到了方法重寫。用這種特性即沿襲了父類的功能,又定義了子類特有的內容。 -
方法重寫的注意事項:
1.父類中私有方法不能被重寫,應為它根本就不能被繼承,所以談不上重寫。
2.子類重寫父類方法時,訪問許可權不能更低,最好與父類一致。
3.父類的靜態方法,子類也必須通過靜態方法進行重寫。
重寫父類的快捷鍵 CTRL+O。 -
final關鍵字:
由於繼承中有一個方法重寫的現象,而有時候我們不想讓子類去重寫父類的方法,所以我們就用final關鍵字定義父類,這樣子類就沒法繼承了。final修飾特點:
修飾類: 被修飾類不能被繼承
修飾方法: 被修飾的方法不能被重寫
修飾變數: 被修飾的變數不能被重新賦值 -
程式碼塊
在Java中,使用{}括起來的程式碼被稱為程式碼塊。
根據其位置和宣告的不同,可以分為區域性程式碼塊,構造程式碼塊,靜態程式碼塊,同步程式碼塊等等。
a:區域性程式碼塊
在方法中出現;限定變數生命週期,及早釋放,提高記憶體利用率
b:構造程式碼塊
在類中方法外出現;多個構造方法方法中相同的程式碼存放到一起,每次呼叫構造都執行,並且在構造方法前執行
c:靜態程式碼塊
在類中方法外出現,並加上static修飾;用於給類進行初始化,在載入的時候就執行,並且只執行一次。
class Fu {
static {
System.out.println("靜態程式碼塊Fu");
}
{
System.out.println("構造程式碼塊Fu");
}
public Fu() {
System.out.println("構造方法Fu");
}
}
class Zi extends Fu {
static {
System.out.println("靜態程式碼塊Zi"); //2
}
{
System.out.println("構造程式碼塊Zi");
}
public Zi() {
System.out.println("構造方法Zi");
}
}
public class Test8 {
static {
System.out.println("測試類的靜態程式碼塊");
}
static {
System.out.println("測試類的靜態程式碼塊2");
}
public static void main(String[] args) {
Zi z = new Zi();
}
}
實驗結果:
由上面的實驗結果我們可以看出,相同等級下,構造程式碼塊優先於構造方法執行,每建立一次物件,都要執行,而靜態程式碼塊,隨著類的載入而載入,只執行一次。
多型
- 概念
某一個事物,在不同時刻表現出來的不同狀態。
Cat c=new Cat(); //貓可以是貓的型別。
Animal a=new Cat(); //貓也是動物的一種
-
多型前提
1.要有繼承關係,否則談不上多型。
2.要有方法重寫,沒有也可以,但是如果沒有就沒有多型的意義。
3.要有父類引用指向子類物件。 -
多型的訪問成員特點!!!
1.多型訪問成員變數的特點:
編譯看 左邊,執行看 左邊。
2.多型訪問構造方法的特點:
建立子類物件的時候,會先對父類的資料進行初始化並訪問父類。
3.多型訪問成員方法的特點:
編譯看左邊,執行看右邊。
4.多型訪問靜態方法的特點:
編譯看左邊,執行看左邊。
(靜態本來就算不上重寫,所以訪問還是看左邊的) -
多型的利弊:
首先多型提高了程式碼的維護性,這由繼承的特性保證。其次提高了程式碼的拓展性,這是由多型保證。
但是多型不能使用子類的特有功能,我們必須通過向下轉型來解決此問題。
而向下轉型也是有風險的,時常不注意時就會犯型別轉換異常的錯誤,下面我們為您用一個例子來看看空指標異常的錯誤原理。
//父類定義Fu
public class Fu {
public void show(){
System.out.println("fu show");
}
}
//子類定義Zi,同時定義了子類獨有的方法ziMethod()
public class Zi extends Fu { //繼承了父類Fu
@Override
public void show() {
System.out.println("zi show");
}
//提供一個子類獨有的方法
public void ziMethod(){
System.out.println("我是子類一個特有的一個方法");
}
}
//子類定義Son,同時定義了子類獨有的方法sonMethod()
public class Son extends Fu{ //繼承了父類Fu
@Override
public void show() {
System.out.println("son show");
}
**public void sonMethod(){
System.out.println("我是Son 裡面的一個特有的方法");
}**
}
按照多型的特性,父類引用指向子類的方法,父類不能使用子類的特有方法,我們需要向下轉型。
public class Test5 {
public static void main(String[] args) {
Fu fu = new Zi();
//fu.ziMethod(); 這裡就會報錯
//向下轉型
Zi zi = (Zi) fu;
zi.ziMethod();
fu = new Son();
fu.show();
Son son = (Son) fu;
son.sonMethod();
System.out.println("---------------------------");
Fu fu1 = new Zi();
Zi zi1 = (Zi) fu; //ClassCastException 型別轉換異常
zi1.ziMethod();
Son son1 = (Son) fu1;
son1.sonMethod();
}
}
從以上結果我們可以看出,這個測試類就犯了型別轉換異常,而原理就是
fu = new Son();
fu.show();
Son son = (Son) fu;
在這裡我們先把fu物件向下轉型為了Son型別的物件,而在後面我們又
Zi zi1 = (Zi) fu;
zi1.ziMethod();
要把fu物件向下轉型為Zi類物件,而它在上面已經被轉型為Son的物件,Son和Zi都屬於子類,不存在直接的繼承關係,所以無法轉型,而我們新定義的
Fu fu1 = new Zi();
fu1就可以向下轉型為son,從而呼叫son特有的方法sonMethon();