目錄
1.繼承
1.1 為什么需要繼承
1.2 繼承概念?
1.3 繼承的語法
1.4 父類成員訪問
1.4.1 子類中訪問父類的成員變量
1.4.2 子類中訪問父類的成員方法
?1.5 super關(guān)鍵字?
1.6 子類構(gòu)造方法
1.7 super和this
1.8 再談初始化
1.9 protected 關(guān)鍵字
1.10 繼承方式
?1.11 final 關(guān)鍵字
1.12 繼承與組合
2.多態(tài)
2.1 多態(tài)的概念?
2.2 多態(tài)實(shí)現(xiàn)條件
2.3 重寫
2.4 向上轉(zhuǎn)移和向下轉(zhuǎn)型
2.4.1 向上轉(zhuǎn)型?
2.4.2 向下轉(zhuǎn)型?
1.繼承
1.1 為什么需要繼承
Java中使用類對(duì)現(xiàn)實(shí)世界中實(shí)體來進(jìn)行描述,類經(jīng)過實(shí)例化之后的產(chǎn)物對(duì)象,則可以用來表示現(xiàn)實(shí)中的實(shí)體,但是現(xiàn)實(shí)世界錯(cuò)綜復(fù)雜,事物之間可能會(huì)存在一些關(guān)聯(lián),那在設(shè)計(jì)程序是就需要考慮。
比如:狗和貓,它們都是一個(gè)動(dòng)物。?
???????
(注:圖片來自網(wǎng)絡(luò),如有侵權(quán),請(qǐng)聯(lián)系刪除)?
使用Java語言來進(jìn)行描述,就會(huì)設(shè)計(jì)出:
// Dog.java
public class Dog{
string name;
int age;
float weight;
public void eat(){
System.out.println(name + "正在吃飯");
}
public void sleep(){
System.out.println(name + "正在睡覺");
}
void Bark(){
System.out.println(name + "汪汪汪~~~");
}
}
// Cat.Java
public class Cat{
string name;
int age;
float weight;
public void eat(){
System.out.println(name + "正在吃飯");
}
public void sleep(){
System.out.println(name + "正在睡覺");
}
void mew(){
System.out.println(name + "喵喵喵~~~");
}
}
通過觀察上述代碼會(huì)發(fā)現(xiàn),貓和狗的類中存在大量重復(fù),
如:name、String、age、int、eat()、sleep().
那能否將這些共性抽取呢?面向?qū)ο笏枷胫刑岢隽死^承的概念,專門用來進(jìn)行共性抽取,實(shí)現(xiàn)代碼復(fù)用
1.2 繼承概念?
繼承(inheritance)機(jī)制:是面向?qū)ο蟪绦蛟O(shè)計(jì)使代碼可以復(fù)用的最重要的手段,它允許程序員在保持原有類特性的基礎(chǔ)上進(jìn)行擴(kuò)展,增加新功能,這樣產(chǎn)生新的類,稱派生類。繼承呈現(xiàn)了面向?qū)ο蟪绦蛟O(shè)計(jì)的層次結(jié)構(gòu), 體現(xiàn)了由簡單到復(fù)雜的認(rèn)知過程。繼承主要解決的問題是:共性的抽取,實(shí)現(xiàn)代碼復(fù)用。
例如:狗和貓都是動(dòng)物,那么我們就可以將共性的內(nèi)容進(jìn)行抽取,然后采用繼承的思想來達(dá)到共用。
?
上述圖示中,Dog和Cat都繼承了Animal類,其中:Animal類稱為父類/基類或超類,Dog和Cat可以稱為Animal的子類/派生類,繼承之后,子類可以復(fù)用父類中成員,子類在實(shí)現(xiàn)時(shí)只需關(guān)心自己新增加的成員即可。
從繼承概念中可以看出繼承最大的作用就是:實(shí)現(xiàn)代碼復(fù)用,還有就是來實(shí)現(xiàn)多態(tài)
1.3 繼承的語法
在Java中如果要表示類之間的繼承關(guān)系,需要借助extends關(guān)鍵字,具體如下
修飾符 class 子類 extends 父類 {
// ...
}
?對(duì)1.2中場(chǎng)景使用繼承方式重新設(shè)計(jì)
// Animal.java
public class Animal{
String name;
int age;
public void eat(){
System.out.println(name + "正在吃飯");
}
public void sleep(){
System.out.println(name + "正在睡覺");
}
}
// Dog.java
public class Dog extends Animal{
void bark(){
System.out.println(name + "汪汪汪~~~");
}
}
// Cat.Java
public class Cat extends Animal{
void mew(){
System.out.println(name + "喵喵喵~~~");
}
}
// TestExtend.java
public class TestExtend {
public static void main(String[] args) {
Dog dog = new Dog();
// dog類中并沒有定義任何成員變量,name和age屬性肯定是從父類Animal中繼承下來的
System.out.println(dog.name);
System.out.println(dog.age);
// dog訪問的eat()和sleep()方法也是從Animal中繼承下來的
dog.eat();
dog.sleep();
dog.bark();
}
}
?注意:
????????1. 子類會(huì)將父類中的成員變量或者成員方法繼承到子類中了
????????2. 子類繼承父類之后,必須要新添加自己特有的成員,體現(xiàn)出與基類的不同,否則就沒有必要繼承了
1.4 父類成員訪問
在繼承體系中,子類將父類中的方法和字段繼承下來了,那在子類中能否直接訪問父類中繼承下來的成員呢??
1.4.1 子類中訪問父類的成員變量
1. 子類和父類不存在同名成員變量
public class Base {
int a;
int b;
}
public class Derived extends Base{
int c;
public void method(){
a = 10; // 訪問從父類中繼承下來的a
b = 20; // 訪問從父類中繼承下來的b
c = 30; // 訪問子類自己的c
}
}
?2. 子類和父類成員變量同名
public class Base {
int a;
int b;
int c;
}
/
public class Derived extends Base{
int a; // 與父類中成員a同名,且類型相同
char b; // 與父類中成員b同名,但類型不同
public void method(){
a = 100; // 訪問父類繼承的a,還是子類自己新增的a?
b = 101; // 訪問父類繼承的b,還是子類自己新增的b?
c = 102; // 子類沒有c,訪問的肯定是從父類繼承下來的c
// d = 103; // 編譯失敗,因?yàn)楦割惡妥宇惗紱]有定義成員變量b
}
}
?在子類方法中 或者 通過子類對(duì)象訪問成員時(shí):
??????如果訪問的成員變量子類中有,優(yōu)先訪問自己的成員變量。
??????如果訪問的成員變量子類中無,則訪問父類繼承下來的,如果父類也沒有定義,則編譯報(bào)錯(cuò)。
??????如果訪問的成員變量與父類中成員變量同名,則優(yōu)先訪問自己的。
???????成員變量訪問遵循就近原則,自己有優(yōu)先自己的,如果沒有則向父類中找。
1.4.2 子類中訪問父類的成員方法
1. 成員方法名字不同
public class Base {
public void methodA(){
System.out.println("Base中的methodA()");
}
}
public class Derived extends Base{
public void methodB(){
System.out.println("Derived中的methodB()方法");
}
public void methodC(){
methodB(); // 訪問子類自己的methodB()
methodA(); // 訪問父類繼承的methodA()
// methodD(); // 編譯失敗,在整個(gè)繼承體系中沒有發(fā)現(xiàn)方法methodD()
}
}
總結(jié):成員方法沒有同名時(shí),在子類方法中或者通過子類對(duì)象訪問方法時(shí),則優(yōu)先訪問自己的,自己沒有時(shí)再到父類中找,如果父類中也沒有則報(bào)錯(cuò)。
2. 成員方法名字相同
public class Base {
public void methodA(){
System.out.println("Base中的methodA()");
}
public void methodB(){
System.out.println("Base中的methodB()");
}
}
public class Derived extends Base{
public void methodA(int a) {
System.out.println("Derived中的method(int)方法");
}
public void methodB(){
System.out.println("Derived中的methodB()方法");
}
public void methodC(){
methodA(); // 沒有傳參,訪問父類中的methodA()
methodA(20); // 傳遞int參數(shù),訪問子類中的methodA(int)
methodB(); // 直接訪問,則永遠(yuǎn)訪問到的都是子類中的methodB(),基類的無法訪問到
}
}
【說明】
通過子類對(duì)象訪問父類與子類中不同名方法時(shí),優(yōu)先在子類中找,找到則訪問,否則在父類中找,找到則訪問,否則編譯報(bào)錯(cuò)。
通過派生類對(duì)象訪問父類與子類同名方法時(shí),如果父類和子類同名方法的參數(shù)列表不同(重載),根據(jù)調(diào)用方法適傳遞的參數(shù)選擇合適的方法訪問,如果沒有則報(bào)錯(cuò);
問題:如果子類中存在與父類中相同的成員時(shí),那如何在子類中訪問父類相同名稱的成員呢?
?1.5 super關(guān)鍵字?
?由于設(shè)計(jì)不好,或者因場(chǎng)景需要,子類和父類中可能會(huì)存在相同名稱的成員,如果要在子類方法中訪問父類同名成員時(shí),該如何操作?直接訪問是無法做到的,Java提供了super關(guān)鍵字,該關(guān)鍵字主要作用:在子類方法中訪問父類的成員。
public class Base {
int a;
int b;
public void methodA(){
System.out.println("Base中的methodA()");
}
public void methodB(){
System.out.println("Base中的methodB()");
}
}
public class Derived extends Base{
int a; // 與父類中成員變量同名且類型相同
char b; // 與父類中成員變量同名但類型不同
// 與父類中methodA()構(gòu)成重載
public void methodA(int a) {
System.out.println("Derived中的method()方法");
}
// 與基類中methodB()構(gòu)成重寫(即原型一致,重寫后序詳細(xì)介紹)
public void methodB(){
System.out.println("Derived中的methodB()方法");
}
public void methodC(){
// 對(duì)于同名的成員變量,直接訪問時(shí),訪問的都是子類的
a = 100; // 等價(jià)于: this.a = 100;
b = 101; // 等價(jià)于: this.b = 101;
// 注意:this是當(dāng)前對(duì)象的引用
// 訪問父類的成員變量時(shí),需要借助super關(guān)鍵字
// super是獲取到子類對(duì)象中從基類繼承下來的部分
super.a = 200;
super.b = 201;
// 父類和子類中構(gòu)成重載的方法,直接可以通過參數(shù)列表區(qū)分清訪問父類還是子類方法
methodA(); // 沒有傳參,訪問父類中的methodA()
methodA(20); // 傳遞int參數(shù),訪問子類中的methodA(int)
// 如果在子類中要訪問重寫的基類方法,則需要借助super關(guān)鍵字
methodB(); // 直接訪問,則永遠(yuǎn)訪問到的都是子類中的methodA(),基類的無法訪問到
super.methodB(); // 訪問基類的methodB()
}
}
?在子類方法中,如果想要明確訪問父類中成員時(shí),借助super關(guān)鍵字即可。
【注意事項(xiàng)】
????????1. 只能在非靜態(tài)方法中使用
????????2. 在子類方法中,訪問父類的成員變量和方法。
????????super的其他用法在后文中介紹。
?
1.6 子類構(gòu)造方法
父子父子,先有父再有子,即:子類對(duì)象構(gòu)造時(shí),需要先調(diào)用基類構(gòu)造方法,然后執(zhí)行子類的構(gòu)造方法。
public class Base {
public Base(){
System.out.println("Base()");
}
}
public class Derived extends Base{
public Derived(){
// super(); // 注意子類構(gòu)造方法中默認(rèn)會(huì)調(diào)用基類的無參構(gòu)造方法:super(),
// 用戶沒有寫時(shí),編譯器會(huì)自動(dòng)添加,而且super()必須是子類構(gòu)造方法中第一條語句,
// 并且只能出現(xiàn)一次
System.out.println("Derived()");
}
}
public class Test {
public static void main(String[] args) {
Derived d = new Derived();
}
}
結(jié)果打?。?Base()
Derived()
?在子類構(gòu)造方法中,并沒有寫任何關(guān)于基類構(gòu)造的代碼,但是在構(gòu)造子類對(duì)象時(shí),先執(zhí)行基類的構(gòu)造方法,然后執(zhí)行子類的構(gòu)造方法,因?yàn)椋?strong>子類對(duì)象中成員是有兩部分組成的,基類繼承下來的以及子類新增加的部分 。父子父子肯定是先有父再有子,所以在構(gòu)造子類對(duì)象時(shí)候 ,先要調(diào)用基類的構(gòu)造方法,將從基類繼承下來的成員構(gòu)造完整,然后再調(diào)用子類自己的構(gòu)造方法,將子類自己新增加的成員初始化完整 。
注意:
? 1. 若父類顯式定義無參或者默認(rèn)的構(gòu)造方法,在子類構(gòu)造方法第一行默認(rèn)有隱含的super()調(diào)用,即調(diào)用基類構(gòu)造方法
? 2. 如果父類構(gòu)造方法是帶有參數(shù)的,此時(shí)需要用戶為子類顯式定義構(gòu)造方法,并在子類構(gòu)造方法中選擇合適的父類構(gòu)造方法調(diào)用,否則編譯失敗。
? 3. 在子類構(gòu)造方法中,super(...)調(diào)用父類構(gòu)造時(shí),必須是子類構(gòu)造函數(shù)中第一條語句。
? 4. super(...)只能在子類構(gòu)造方法中出現(xiàn)一次,并且不能和this同時(shí)出現(xiàn)
1.7 super和this
super和this都可以在成員方法中用來訪問:成員變量和調(diào)用其他的成員函數(shù),都可以作為構(gòu)造方法的第一條語句,那他們之間有什么區(qū)別呢?
【相同點(diǎn)】
1. 都是Java中的關(guān)鍵字
2. 只能在類的非靜態(tài)方法中使用,用來訪問非靜態(tài)成員方法和字段
3. 在構(gòu)造方法中調(diào)用時(shí),必須是構(gòu)造方法中的第一條語句,并且不能同時(shí)存在
【不同點(diǎn)】
? 1. this是當(dāng)前對(duì)象的引用,當(dāng)前對(duì)象即調(diào)用實(shí)例方法的對(duì)象,super相當(dāng)于是子類對(duì)象中從父類繼承下來部分成員的引用.
? 2. 在非靜態(tài)成員方法中,this用來訪問本類的方法和屬性,super用來訪問父類繼承下來的方法和屬性.
? 3. 在構(gòu)造方法中:this(...)用于調(diào)用本類構(gòu)造方法,super(...)用于調(diào)用父類構(gòu)造方法,兩種調(diào)用不能同時(shí)在構(gòu)造方法中出現(xiàn).
? 4. 構(gòu)造方法中一定會(huì)存在super(...)的調(diào)用,用戶沒有寫編譯器也會(huì)增加,但是this(...)用戶不寫則沒有.
1.8 再談初始化
我們還記得之前講過的代碼塊嗎?我們簡單回顧一下幾個(gè)重要的代碼塊:實(shí)例代碼塊和靜態(tài)代碼塊。在沒有繼承關(guān)系時(shí)的執(zhí)行順序
class Person {
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("構(gòu)造方法執(zhí)行");
}
{
System.out.println("實(shí)例代碼塊執(zhí)行");
}
static {
System.out.println("靜態(tài)代碼塊執(zhí)行");
}
}
public class TestDemo {
public static void main(String[] args) {
Person person1 = new Person("bit",10);
System.out.println("============================");
Person person2 = new Person("gaobo",20);
}
}
執(zhí)行結(jié)果:
靜態(tài)代碼塊執(zhí)行
實(shí)例代碼塊執(zhí)行
構(gòu)造方法執(zhí)行
============================
實(shí)例代碼塊執(zhí)行
構(gòu)造方法執(zhí)行
? ? 1. 靜態(tài)代碼塊先執(zhí)行,并且只執(zhí)行一次,在類加載階段執(zhí)行
? ? 2. 當(dāng)有對(duì)象創(chuàng)建時(shí),才會(huì)執(zhí)行實(shí)例代碼塊,實(shí)例代碼塊執(zhí)行完成后,最后構(gòu)造方法執(zhí)行
?【繼承關(guān)系上的執(zhí)行順序】
class Person {
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("Person:構(gòu)造方法執(zhí)行");
}
{
System.out.println("Person:實(shí)例代碼塊執(zhí)行");
}
static {
System.out.println("Person:靜態(tài)代碼塊執(zhí)行");
}
}
class Student extends Person{
public Student(String name,int age) {
super(name,age);
System.out.println("Student:構(gòu)造方法執(zhí)行");
}
{
System.out.println("Student:實(shí)例代碼塊執(zhí)行");
}
static {
System.out.println("Student:靜態(tài)代碼塊執(zhí)行");
}
}
public class TestDemo4 {
public static void main(String[] args) {
Student student1 = new Student("張三",19);
System.out.println("===========================");
Student student2 = new Student("gaobo",20);
}
public static void main1(String[] args) {
Person person1 = new Person("bit",10);
System.out.println("============================");
Person person2 = new Person("gaobo",20);
}
}
?執(zhí)行結(jié)果:
Person:靜態(tài)代碼塊執(zhí)行
Student:靜態(tài)代碼塊執(zhí)行
Person:實(shí)例代碼塊執(zhí)行
Person:構(gòu)造方法執(zhí)行
Student:實(shí)例代碼塊執(zhí)行
Student:構(gòu)造方法執(zhí)行
===========================
Person:實(shí)例代碼塊執(zhí)行
Person:構(gòu)造方法執(zhí)行
Student:實(shí)例代碼塊執(zhí)行
Student:構(gòu)造方法執(zhí)行
?通過分析執(zhí)行結(jié)果,得出以下結(jié)論:
1、父類靜態(tài)代碼塊優(yōu)先于子類靜態(tài)代碼塊執(zhí)行,且是最早執(zhí)行
2、父類實(shí)例代碼塊和父類構(gòu)造方法緊接著執(zhí)行
3、子類的實(shí)例代碼塊和子類構(gòu)造方法緊接著再執(zhí)行
4、第二次實(shí)例化子類對(duì)象時(shí),父類和子類的靜態(tài)代碼塊都將不會(huì)再執(zhí)行
1.9 protected 關(guān)鍵字
在類和對(duì)象章節(jié)中,為了實(shí)現(xiàn)封裝特性,Java中引入了訪問限定符,主要限定:類或者類中成員能否在類外或者其他包中被訪問。
?
(注:圖片來自網(wǎng)絡(luò),如有侵權(quán),請(qǐng)聯(lián)系刪除)??
那父類中不同訪問權(quán)限的成員,在子類中的可見性又是什么樣子的呢?
// 為了掩飾基類中不同訪問權(quán)限在子類中的可見性,為了簡單類B中就不設(shè)置成員方法了
// extend01包中
public class B {
private int a;
protected int b;
public int c;
int d;
}
// extend01包中
// 同一個(gè)包中的子類
public class D extends B{
public void method(){
// super.a = 10; // 編譯報(bào)錯(cuò),父類private成員在相同包子類中不可見
super.b = 20; // 父類中protected成員在相同包子類中可以直接訪問
super.c = 30; // 父類中public成員在相同包子類中可以直接訪問
super.d = 40; // 父類中默認(rèn)訪問權(quán)限修飾的成員在相同包子類中可以直接訪問
}
}
// extend02包中
// 不同包中的子類
public class C extends B {
public void method(){
// super.a = 10; // 編譯報(bào)錯(cuò),父類中private成員在不同包子類中不可見
super.b = 20; // 父類中protected修飾的成員在不同包子類中可以直接訪問
super.c = 30; // 父類中public修飾的成員在不同包子類中可以直接訪問
//super.d = 40; // 父類中默認(rèn)訪問權(quán)限修飾的成員在不同包子類中不能直接訪問
}
}
// extend02包中
// 不同包中的類
public class TestC {
public static void main(String[] args) {
C c = new C();
c.method();
// System.out.println(c.a); // 編譯報(bào)錯(cuò),父類中private成員在不同包其他類中不可見
// System.out.println(c.b); // 父類中protected成員在不同包其他類中不能直接訪問
System.out.println(c.c); // 父類中public成員在不同包其他類中可以直接訪問
// System.out.println(c.d); //父類中默認(rèn)訪問權(quán)限修飾的成員在不同包其他類中不能直接訪問
}
}
?注意:父類中private成員變量雖然在子類中不能直接訪問,但是也繼承到子類中了
什么時(shí)候下用哪一種呢?
我們希望類要盡量做到 "封裝", 即隱藏內(nèi)部實(shí)現(xiàn)細(xì)節(jié), 只暴露出 必要 的信息給類的調(diào)用者.
因此我們?cè)谑褂玫臅r(shí)候應(yīng)該盡可能的使用 比較嚴(yán)格 的訪問權(quán)限. 例如如果一個(gè)方法能用 private, 就盡量不要用 public.
另外, 還有一種 簡單粗暴 的做法: 將所有的字段設(shè)為 private, 將所有的方法設(shè)為 public. 不過這種方式屬于是對(duì)訪問權(quán)限的濫用, 還是更希望同學(xué)們能寫代碼的時(shí)候認(rèn)真思考, 該類提供的字段方法到底給 "誰" 使用(是類內(nèi)部自己用, 還是類的調(diào)用者使用, 還是子類使用).
?
1.10 繼承方式
在現(xiàn)實(shí)生活中,事物之間的關(guān)系是非常復(fù)雜,靈活多樣,比如:?
?(注:圖片來自網(wǎng)絡(luò),如有侵權(quán),請(qǐng)聯(lián)系刪除)??
但在Java中只支持以下幾種繼承方式:
?(注:圖片來自網(wǎng)絡(luò),如有侵權(quán),請(qǐng)聯(lián)系刪除)
注意:Java中不支持多繼承。
? ?時(shí)刻牢記, 我們寫的類是現(xiàn)實(shí)事物的抽象. 而我們真正在公司中所遇到的項(xiàng)目往往業(yè)務(wù)比較復(fù)雜, 可能會(huì)涉及到一系列復(fù)雜的概念, 都需要我們使用代碼來表示, 所以我們真實(shí)項(xiàng)目中所寫的類也會(huì)有很多. 類之間的關(guān)系也會(huì)更加復(fù)雜.
? ?但是即使如此, 我們并不希望類之間的繼承層次太復(fù)雜. 一般我們不希望出現(xiàn)超過三層的繼承關(guān)系.如果繼承層次太多, 就需要考慮對(duì)代碼進(jìn)行重構(gòu)了.
? ?如果想從語法上進(jìn)行限制繼承, 就可以使用 final 關(guān)鍵字
?1.11 final 關(guān)鍵字
final關(guān)鍵可以用來修飾變量、成員方法以及類。
?1. 修飾變量或字段,表示常量(即不能修改)
final int a = 10;
a = 20; // 編譯出錯(cuò)
2. 修飾類:表示此類不能被繼承
?
final public class Animal {
...
}
public class Bird extends Animal {
...
}
// 編譯出錯(cuò)
Error:(3, 27) java: 無法從最終com.bit.Animal進(jìn)行繼
我們平時(shí)是用的 String 字符串類, 就是用 final 修飾的, 不能被繼承
3. 修飾方法:表示該方法不能被重寫
1.12 繼承與組合
和繼承類似, 組合也是一種表達(dá)類之間關(guān)系的方式, 也是能夠達(dá)到代碼重用的效果。組合并沒有涉及到特殊的語法
(諸如 extends 這樣的關(guān)鍵字), 僅僅是將一個(gè)類的實(shí)例作為另外一個(gè)類的字段。
繼承表示對(duì)象之間是is-a的關(guān)系,比如:狗是動(dòng)物,貓是動(dòng)物
組合表示對(duì)象之間是has-a的關(guān)系,比如:汽車
??(注:圖片來自網(wǎng)絡(luò),如有侵權(quán),請(qǐng)聯(lián)系刪除)
汽車和其輪胎、發(fā)動(dòng)機(jī)、方向盤、車載系統(tǒng)等的關(guān)系就應(yīng)該是組合,因?yàn)槠囀怯羞@些部件組成的。
?
// 輪胎類
class Tire{
// ...
}
// 發(fā)動(dòng)機(jī)類
class Engine{
// ...
}
// 車載系統(tǒng)類
class VehicleSystem{
// ...
}
class Car{
private Tire tire; // 可以復(fù)用輪胎中的屬性和方法
private Engine engine; // 可以復(fù)用發(fā)動(dòng)機(jī)中的屬性和方法
private VehicleSystem vs; // 可以復(fù)用車載系統(tǒng)中的屬性和方法
// ...
}
// 奔馳是汽車
class Benz extend Car{
// 將汽車中包含的:輪胎、發(fā)送機(jī)、車載系統(tǒng)全部繼承下來
}
組合和繼承都可以實(shí)現(xiàn)代碼復(fù)用,應(yīng)該使用繼承還是組合,需要根據(jù)應(yīng)用場(chǎng)景來選擇,一般建議:能用組合盡量用組合
2.多態(tài)
2.1 多態(tài)的概念?
多態(tài)的概念:通俗來說,就是多種形態(tài),具體點(diǎn)就是去完成某個(gè)行為,當(dāng)不同的對(duì)象去完成時(shí)會(huì)產(chǎn)生出不同 的狀態(tài)。
?
?(注:圖片來自網(wǎng)絡(luò),如有侵權(quán),請(qǐng)聯(lián)系刪除)
?總的來說:同一件事情,發(fā)生在不同對(duì)象身上,就會(huì)產(chǎn)生不同的結(jié)果。
2.2 多態(tài)實(shí)現(xiàn)條件
在java中要實(shí)現(xiàn)多態(tài),必須要滿足如下幾個(gè)條件,缺一不可:
? 1. 必須在繼承體系下
? 2. 子類必須要對(duì)父類中方法進(jìn)行重寫
? 3. 通過父類的引用調(diào)用重寫的方法
多態(tài)體現(xiàn):在代碼運(yùn)行時(shí),當(dāng)傳遞不同類對(duì)象時(shí),會(huì)調(diào)用對(duì)應(yīng)類中的方法。
?
public class Animal {
String name;
int age;
public Animal(String name, int age){
this.name = name;
this.age = age;
}
public void eat(){
System.out.println(name + "吃飯");
}
}
public class Cat extends Animal{
public Cat(String name, int age){
super(name, age);
}
@Override
public void eat(){
System.out.println(name+"吃魚~~~");
}
}
public class Dog extends Animal {
public Dog(String name, int age){
super(name, age);
}
@Override
public void eat(){
System.out.println(name+"吃骨頭~~~");
}
}
///分割線//
public class TestAnimal {
// 編譯器在編譯代碼時(shí),并不知道要調(diào)用Dog 還是 Cat 中eat的方法
// 等程序運(yùn)行起來后,形參a引用的具體對(duì)象確定后,才知道調(diào)用那個(gè)方法
// 注意:此處的形參類型必須時(shí)父類類型才可以
public static void eat(Animal a){
a.eat();
}
public static void main(String[] args) {
Cat cat = new Cat("元寶",2);
Dog dog = new Dog("小七", 1);
eat(cat);
eat(dog);
}
}
運(yùn)行結(jié)果:
元寶吃魚~~~
元寶正在睡覺
小七吃骨頭~~~
小七正在睡覺
在上述代碼中, 分割線上方的代碼是 類的實(shí)現(xiàn)者 編寫的, 分割線下方的代碼是 類的調(diào)用者 編寫的.
當(dāng)類的調(diào)用者在編寫 eat 這個(gè)方法的時(shí)候, 參數(shù)類型為 Animal (父類), 此時(shí)在該方法內(nèi)部并不知道, 也不關(guān)注當(dāng)前的a 引用指向的是哪個(gè)類型(哪個(gè)子類)的實(shí)例. 此時(shí) a這個(gè)引用調(diào)用 eat方法可能會(huì)有多種不同的表現(xiàn)(和 a 引用的實(shí)例相關(guān)), 這種行為就稱為 多態(tài)
?
2.3 重寫
重寫(override):也稱為覆蓋。重寫是子類對(duì)父類非靜態(tài)、非private修飾,非final修飾,非構(gòu)造方法等的實(shí)現(xiàn)過程進(jìn)行重新編寫, 返回值和形參都不能改變。即外殼不變,核心重寫!重寫的好處在于子類可以根據(jù)需要,定義特定于自己的行為。 也就是說子類能夠根據(jù)需要實(shí)現(xiàn)父類的方法。
【方法重寫的規(guī)則】
? ? 子類在重寫父類的方法時(shí),一般必須與父類方法原型一致: 返回值類型 方法名 (參數(shù)列表) 要完全一致
? ? 被重寫的方法返回值類型可以不同,但是必須是具有父子關(guān)系的
? ? 訪問權(quán)限不能比父類中被重寫的方法的訪問權(quán)限更低。例如:如果父類方法被public修飾,則子類中重寫該方法就不能聲明為 protected
? ? 父類被static、private修飾的方法、構(gòu)造方法都不能被重寫。
重寫的方法, 可以使用 @Override 注解來顯式指定. 有了這個(gè)注解能幫我們進(jìn)行一些合法性校驗(yàn). 例如不小心將方法名字拼寫錯(cuò)了 (比如寫成 aet), 那么此時(shí)編譯器就會(huì)發(fā)現(xiàn)父類中沒有 aet 方法, 就會(huì)編譯報(bào)錯(cuò), 提示無法構(gòu)成重寫.
?【重寫和重載的區(qū)別】
?(注:圖片來自網(wǎng)絡(luò),如有侵權(quán),請(qǐng)聯(lián)系刪除)
即:方法重載是一個(gè)類的多態(tài)性表現(xiàn),而方法重寫是子類與父類的一種多態(tài)性表現(xiàn).
【重寫的設(shè)計(jì)原則】
對(duì)于已經(jīng)投入使用的類,盡量不要進(jìn)行修改。最好的方式是:重新定義一個(gè)新的類,來重復(fù)利用其中共性的內(nèi)容,并且添加或者改動(dòng)新的內(nèi)容。
例如:若干年前的手機(jī),只能打電話,發(fā)短信,來電顯示只能顯示號(hào)碼,而今天的手機(jī)在來電顯示的時(shí)候,不僅僅可以顯示號(hào)碼,還可以顯示頭像,地區(qū)等。在這個(gè)過程當(dāng)中,我們不應(yīng)該在原來老的類上進(jìn)行修改,因?yàn)樵瓉淼念?,可能還在有用戶使用,正確做法是:新建一個(gè)新手機(jī)的類,對(duì)來電顯示這個(gè)方法重寫就好了,這樣就達(dá)到了我們當(dāng)今的需求了。
?
靜態(tài)綁定:也稱為前期綁定(早綁定),即在編譯時(shí),根據(jù)用戶所傳遞實(shí)參類型就確定了具體調(diào)用那個(gè)方法。典型代表函數(shù)重載。
動(dòng)態(tài)綁定:也稱為后期綁定(晚綁定),即在編譯時(shí),不能確定方法的行為,需要等到程序運(yùn)行時(shí),才能夠確定具體調(diào)用那個(gè)類的方法
2.4 向上轉(zhuǎn)移和向下轉(zhuǎn)型
2.4.1 向上轉(zhuǎn)型?
向上轉(zhuǎn)型:實(shí)際就是創(chuàng)建一個(gè)子類對(duì)象,將其當(dāng)成父類對(duì)象來使用。
語法格式:父類類型 對(duì)象名 = new 子類類型()
Animal animal = new Cat("狗蛋",2);
?animal是父類類型,但可以引用一個(gè)子類對(duì)象,因?yàn)槭菑男》秶虼蠓秶霓D(zhuǎn)換。
【使用場(chǎng)景】
1. 直接賦值
2. 方法傳參
3. 方法返回
?
public class TestAnimal {
// 2. 方法傳參:形參為父類型引用,可以接收任意子類的對(duì)象
public static void eatFood(Animal a){
a.eat();
}
// 3. 作返回值:返回任意子類對(duì)象
public static Animal buyAnimal(String var){
if("狗".equals(var) ){
return new Dog("狗狗",1);
}
else if("貓" .equals(var)){
return new Cat("貓貓", 1);
}
else{
return null;
}
}
public static void main(String[] args) {
Animal cat = new Cat("元寶",2); // 1. 直接賦值:子類對(duì)象賦值給父類對(duì)象
Dog dog = new Dog("小七", 1);
eatFood(cat);
eatFood(dog);
Animal animal = buyAnimal("狗");
animal.eat();
animal = buyAnimal("貓");
animal.eat();
}
}
向上轉(zhuǎn)型的優(yōu)點(diǎn):讓代碼實(shí)現(xiàn)更簡單靈活。
向上轉(zhuǎn)型的缺陷:不能調(diào)用到子類特有的方法。
2.4.2 向下轉(zhuǎn)型?
將一個(gè)子類對(duì)象經(jīng)過向上轉(zhuǎn)型之后當(dāng)成父類方法使用,再無法調(diào)用子類的方法,但有時(shí)候可能需要調(diào)用子類特有的
方法,此時(shí):將父類引用再還原為子類對(duì)象即可,即向下轉(zhuǎn)換。
?
public class TestAnimal {
public static void main(String[] args) {
Cat cat = new Cat("元寶",2);
Dog dog = new Dog("小七", 1);
// 向上轉(zhuǎn)型
Animal animal = cat;
animal.eat();
animal = dog;
animal.eat();
// 編譯失敗,編譯時(shí)編譯器將animal當(dāng)成Animal對(duì)象處理
// 而Animal類中沒有bark方法,因此編譯失敗
// animal.bark();
// 向上轉(zhuǎn)型
// 程序可以通過編程,但運(yùn)行時(shí)拋出異常---因?yàn)椋篴nimal實(shí)際指向的是狗
// 現(xiàn)在要強(qiáng)制還原為貓,無法正常還原,運(yùn)行時(shí)拋出:ClassCastException
cat = (Cat)animal;
cat.mew();
// animal本來指向的就是狗,因此將animal還原為狗也是安全的
dog = (Dog)animal;
dog.bark();
}
}
向下轉(zhuǎn)型用的比較少,而且不安全,萬一轉(zhuǎn)換失敗,運(yùn)行時(shí)就會(huì)拋異常。Java中為了提高向下轉(zhuǎn)型的安全性,引入了 instanceof ,如果該表達(dá)式為true,則可以安全轉(zhuǎn)換
?
public class TestAnimal {
public static void main(String[] args) {
Cat cat = new Cat("元寶",2);
Dog dog = new Dog("小七", 1);
// 向上轉(zhuǎn)型
Animal animal = cat;
animal.eat();
animal = dog;
animal.eat();
if(animal instanceof Cat){
cat = (Cat)animal;
cat.mew();
}
if(animal instanceof Dog){
dog = (Dog)animal;
dog.bark();
}
}
}
instanceof 關(guān)鍵詞官方介紹:https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.20.2
?(注:圖片來自網(wǎng)絡(luò),如有侵權(quán),請(qǐng)聯(lián)系刪除!!)文章來源:http://www.zghlxwxcb.cn/news/detail-755718.html
希望對(duì)鐵鐵們有所幫助,感謝閱讀?。。?文章來源地址http://www.zghlxwxcb.cn/news/detail-755718.html
到了這里,關(guān)于Java SE 繼承和多態(tài) (圖文搭配,萬字詳解!!)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!