java-繼承進階_抽象類_接口
一, 繼承的進階
1.1,成員變量
重點明確原理。
特殊情況:
子父類中定義了一模一樣的成員變量。
都存在於子類對象中。
如何在子類中直接訪問同名的父類中的變量呢?
通過關鍵字 super來完成。
super和this的用法很相似。
this:代表的是本類的對象的引用。
super:代表的是父類的內存空間。
註意:這種情況開發見不到,因為父類一旦描述完了屬性,子類直接使用就可以了。
//父類。 class Fu { /*private int num1 = 3;*///父類中私有的內容子類不可以直接訪問。 int num = 3; } classZi extends Fu { /*int num2 = 4;*/ int num = 4; void show() { // System.out.println("num1="+num1); // System.out.println("num2="+num2); System.out.println("zi num="+num); // num=4 System.out.println("fu num="+super.num);// num=3 } } class ExtendsDemo {public static void main(String[] args) { Zi z = new Zi(); z.show(); } }
圖解
1.2 成員函數@@
子父類中成員函數的特點
特殊情況:
子父類中的定義了一模一樣的函數。
運行的結果:子類的函數在運行。
這種情況在子父類中,是函數的另一個特性:override(重寫,覆蓋,復寫)
【重寫什麽時候用?】
舉例:
//描述手機。
class Phone { int number; //打電話。 void call(){} //來電顯示。 void show() { sop("電話號碼.."+number); } } Phone p = new Phone(); p.show();
隨著電話的升級,只顯示號碼不爽,希望顯示姓名,大頭貼。
修改源碼,雖然費勁但是可以解決,不利於後期的維護和擴展。
為了擴展方便。新功能是不是新的電話具備呢?
單獨描述單獨封裝。新電話也是電話中的一種。繼承。直接獲取父類中的功能。
但是新電話的來顯功能已經變化了。需要重新定義。
那麽定義一個新功能合適嗎?比如newShow,不合適,因為父類已經將來顯功能定義完了,
子類完全不需要重新定義新功能。直接用就可以了。如果子類的來顯功能內容不同。
直需要保留來顯功能,定義子類的內容即可:這就是重寫的應用!
class NewPhone extends Phone { String name; String picPath;//圖片路徑。 void show() { //sop("電話號碼"); super.show();//如果還需要父類中原有的部分功能,可以通過super調用。@@@@@ sop("姓名"+name); sop("大頭貼"+picPath); } }
【重寫(覆蓋)的註意事項】@@
1,子類覆蓋父類,必須保證全要大於或者等於父類的權限。
Fu:
private void show(){}
Zi:
public void show(){}
2,靜態覆蓋靜態。
寫法上稍微註意:必須一模一樣:函數的返回值類型 , 函數名 參數列表都要一樣。
【重寫總結】
當一個類是另一個類中的一種時,可以通過繼承,來擴展功能。
如果從父類具備的功能內容需要子類特殊定義時,使用重寫。
*/ class Fu { int show() { System.out.println("fu show run"); return 0; } } class Zi extends Fu { void show() { System.out.println("zi show run"); } } class ExtendsDemo2 { public static void main(String[] args) { Zi z = new Zi(); z.show(); } }
圖解
1.3 構造函數
【子父類中構造函數的特點】
class Fu { int Fu() { System.out.println("fu show run"); return 0; } } class Zi extends Fu { void Zi() { System.out.println("zi show run"); } } class ExtendsDemo2 { public static void main(String[] args) { new Zi(); } }
當子父類都有構造函數時,發現結果為:
fu constructor run
zi constructor run
先執行了父類的構造函數,再執行子類的構造函數。
【這是為啥呢?】
因為子類的所有的構造函數中的第一行都有一句隱式語句 super(); //默認調用的是父類中的空參數的構造函數。
【子類中的構造函數為什麽有一句隱式的super()呢?】
原因:子類會繼承父類中的內容,所以子類在初始化時,必須先到父類中去執行父類的初始化動作。
才可以更方便的使用父類中的內容。
【小結】
當父類中沒有空參數構造函數時,子類的構造函數必須同構顯示的super語句指定要訪問的父類中的構造函數。
這就是傳說中的子類實例化過程。*/
class Fu { Fu() { //super(); //顯示初始化。 System.out.println("fu constructor run..A.."); } Fu(int x) { //顯示初始化。 System.out.println("fu constructor run..B.."+x); } } class Zi extends Fu { Zi() { System.out.println("zi constructor run..C.."); } Zi(int x) { System.out.println("zi constructor run..D.."+x); } } class ExtendsDemo3 { public static void main(String[] args) { // new Zi(); // AC@@ new Zi(6); // AD@@ } }
【實例化過程的細節】
1,如果子類的構造函數第一行寫了this調用了本類其他構造函數,那麽super調用父類的語句還有嗎?
沒有的,因為this()或者super(),只能定義在構造函數的第一行,因為初始化動作要先執行。
2,父類構造函數中是否有隱式的super呢?
也是有的。記住:只要是構造函數默認第一行都是super();
父類的父類是誰呢?super調用的到底是誰的構造函數呢?
Java體系在設計,定義了一個所有對象的父類Object ,
【總結】
類中的構造函數默認第一行都有隱式的super()語句,在訪問父類中的構造函數。
所以父類的構造函數既可以給自己的對象初始化,也可以給自己的子類對象初始化。
如果默認的隱式super語句沒有對應的構造函數,必須在構造函數中通過this或者super的形式明確調用的構造函數。
【問題】@@@
1,this語句和super語句是否可以在同一個構造函數中出現呢?不行,因為必須定義在第一行。
2,為什麽要定義在第一行呢?因為初始化動作要先執行。
//子類的實例化過程的應用。也是super調用的應用。
//什麽時候用super調用父類中構造函數。只要使用父類的指定初始化動作,就在子類中通過super(參數列表)格式進行調用。
class Person { private String name; private int age; public Person(String name,int age) { this.name = name; this.age = age; } public void setName(String name) { this.name = name; } public String getName() { return name; } public void setAge(int age) { this.age = age; } public int getAge() { return age; } } class Student extends Person { public Student(String name,int age) { //調用父類。使用父類的初始化動作。 super(name,age); } public void study() {} } class Worker extends Person { public Worker(String name,int age) { //調用父類。使用父類的初始化動作。 super(name,age); } }
1.4 final
可以修飾 類,方法,變量
/*
繼承的弊端:打破封裝性。
不讓其他類繼承該類,就不會有重寫。
怎麽能實現呢?通過Java中的一個關鍵字來實現,final(最終化)。
【final關鍵字】
是一個修飾符,可以修飾類,方法,變量(成員變量,局部變量,靜態變量)。
【特點】
1,final修飾的類是一個最終類,不能在派生子類。
如果類中從出現部分可以重寫,部分不可以?怎麽辦?只要讓指定的方法最終化就可以了。
2,final修飾的方法是最終方法,不可以給重寫。
3,final修飾的變量是一個常量,只能被賦值一次。
【什麽時候會在程序中定義final常量呢?】
當程序中一個數據使用時是固定不變的,這時為了增加閱讀性,可以該該數據起個名字。
這就是變量,為了保證這個變量的值不被修改,加上final修飾,這就一個閱讀性很強的常量。
書寫規範,被final修飾的常量名所有的字母都是大寫的。如果由多個單詞組成單詞間通過 _ 連接。
*/ /*final*/class Fu { /*final*/ void show() { //調用到一些系統的功能。 //功能的內容是不可以改變的。 } } class Zi extends Fu { static final int number = 9;//最終化的是顯示初始化值。 static final double PI = 3.14; //重寫 void show() { final int count = 21; // count = 2; System.out.println(count); } } class FinalDemo { public static void main(String[] args) { System.out.println("Hello World!"); } }
二 抽象類
2.1 產生
/*
描述狗,行為:吼叫。
描述狼,行為:吼叫。
發現他們之間有共性,可以進行向上抽取。
當然是抽取它們的所屬共性類型:犬科。
犬科這類事物:都具備吼叫行為,但是具體怎麽叫,是不確定的,是由具體的子類來明確的。
這時在描述犬科時,發現了有些功能不具體,這些不具體的功能,需要在類中標識出來,通過java中的關鍵字abstract(抽象)。
定義了抽象函數的類也必須被abstract關鍵字修飾,被abstract關鍵字修飾的類是抽象類。
*/
//抽象類:在描述事物時,沒有足夠的信息描述一個事物,這時該事物就是抽象事物。
2.2 抽象類的特點
1,抽象類和抽象方法都需要被abstract修飾。
抽象方法一定要定義在抽象類中。
2,抽象類不可以創建實例,原因:調用抽象方法沒有意義。
3,只有覆蓋了抽象類中所有的抽象方法後,其子類才可以實例化。
否則該子類還是一個抽象類。
之所以繼承,更多的是在思想,是面對共性類型操作會更簡單。
【細節問題】
1,抽象類一定是個父類?
是的,因為不斷抽取而來的。
2,抽象類是否有構造函數?
有,雖然不能給自己的對象初始化,但是可以給自己的子類對象初始化。
抽象類和一般類的異同點:
相同:
1,它們都是用來描述事物的。
2,它們之中都可以定義屬性和行為。
不同:
1,一般類可以具體的描述事物。
抽象類描述事物的信息不具體
2,抽象類中可以多定義一個成員:抽象函數。
3,一般類可以創建對象,而抽象類不能創建對象。
3,抽象類中是否可以不定義抽象方法。
是可以的,那這個抽象類的存在到底有什麽意義呢?僅僅是不讓該類創建對象。
4,抽象關鍵字abstract不可以和哪些關鍵字共存?
1,final:
2,private:
3,static:
*/ abstract class 犬科 //extends Object { static abstract void 吼叫();//抽象函數。需要abstract修飾,並分號;結束 } //代碼體現。 class Dog extends 犬科 { void 吼叫() { System.out.println("汪汪汪汪"); } } class Wolf extends 犬科 { void 吼叫() { System.out.println("嗷嗷嗷嗷"); } } class AbstractDemo { public static void main(String[] args) { System.out.println("Hello World!"); } }
2.2 例子
/*
需求:公司中程序員有姓名,工號,薪水,工作內容。
項目經理除了有姓名,工號,薪水,還有獎金,工作內容。
對給出需求進行數據建模。
在問題領域中先找尋其中涉及的對象。
程序員
屬性:姓名,工號,薪水
行為:工作
項目經理
屬性:姓名,工號,薪水,獎金
行為:工作
這些對象是否有關系呢?因為發現了他們之間的共性,應該存在著關系。
可以將他們的共性向上抽取到共性類型:員工。
員工:
屬性:姓名,工號,薪水
行為:工作
發現員工的工作內容本身就不具體。應該是抽象的,由具體的子類來體現的。
一定要動手!
*/ abstract class Employee { private String name; private String id; private double pay; /** 構造一個員工對象,一初始化就具備著三個屬性。 */ public Employee(String name,String id,double pay) { this.name = name; this.id = id; this.pay = pay; } /** 工作行為。 */ public abstract void work(); } //具體的子類:程序員。 class Programmer extends Employee { public Programmer(String name,String id,double pay) { super(name,id,pay); } public void work() { System.out.println("code...."); } } //具體的子類:經理。 class Manager extends Employee { //特有屬性。 private double bonus; public Manager(String name,String id,double pay,double bonus) { super(name,id,pay); this.bonus = bonus; } public void work() { System.out.println("manage"); } } class AbstractTest { public static void main(String[] args) { System.out.println("Hello World!"); } }
三 接口
3.1 產生
/*
抽象類中可以定義抽象方法的 。
當一個抽象類中的方法全是抽象的。
這時,可以通過另一種特殊的形式來體現。
用接口來表示。
3.2 定義
接口該如何定義呢?
interface abstract class Demo { abstract void show1(); abstract void show2(); } */ /*
3.3 接口中的成員
接口中的成員已經被限定為固定的幾種。
【接口的定義格式先介紹兩種:】
1,定義變量,但是變量必須有固定的修飾符修飾,public static final 所以接口中的變量也稱之為常量。
2,定義方法,方法也有固定的修飾符,public abstract
註意:如果你不寫編譯器會給你自動加上
例如當你只寫一個final的時候,編譯器會給你自動加上public static final
接口中的成員都是公共的。
3.4 接口的特點
1,接口不可以創建對象。
2,子類必須覆蓋掉接口中所有的抽象方法後,子類才可以實例化。
否則子類是一個抽象類。
*/ interface Demo//定義一個名稱為Demo的接口。 { public static final int NUM = 3; public abstract void show1(); public abstract void show2(); }
//定義子類去覆蓋接口中的方法。子類必須和接口產生關系,類與類的關系是繼承,類與接口之間的關系是 實現。通過 關鍵字 implements 註意: 定義接口時命名 名+Impl
class DemoImpl implements Demo//子類實現Demo接口。 { //重寫接口中的方法。 public void show1(){} public void show2(){} }
【接口最重要的體現】
解決多繼承的弊端。將多繼承這種機制在java中通過多實現完成了。
interface A { void show1(); } interface B { void show2(); } class C implements A,B// 多實現。同時實現多個接口。 { public void show1(){} public void show2(){} }
【怎麽解決多繼承的弊端呢?】
弊端:多繼承時,當多個父類中有相同功能時,子類調用會產生不確定性。
其實核心原因就是在於多繼承父類中功能有主體,而導致調用運行時,不確定運行哪個主體內容。
為什麽多實現就解決了呢?
因為接口中的功能都沒有方法體,由子類來明確。
interface A { void show(); } interface B { void show(); } class C implements A,B// 多實現。同時實現多個接口。 { public void show(); } C c = new C(); c.show();
【基於接口的擴展。】
class Fu { public void show(){} } //子類通過繼承父類擴展功能,通過繼承擴展的功能都是子類應該具備的基礎功能。 //如果子類想要繼續擴展其他類中的功能呢?這時通過實現接口來完成。 interface Inter { pulbic void show1(); } class Zi extends Fu implements Inter { public void show1() { } }
[ 接口的好處]
接口的出現避免了單繼承的局限性。
父類中定義的事物的基本功能。
接口中定義的事物的擴展功能。
3.5 接口出現後的一些小細節
1,類與類之間是繼承(is a)關系,類與接口之間是實現(like a)關系,
接口與接口之間是繼承關系,而且可以多繼承。
interface InterA { void show1(); } interface InterAA { void show11(); } interface InterB extends InterA,InterAA//接口的多繼承。 { void show2(); } class Test implements InterB { public void show1(){} public void show2(){} public void show11(){} } class InterfaceDemo { public static void main(String[] args) { DemoImpl d = new DemoImpl(); d.show1(); d.show2(); } }
3.6 接口的思想
舉例:筆記本電腦,USB接口的故事。
1,接口的出現對功能是實現了擴展。
2,接口的出現定義了規則。
3,接口的出現降低了耦合性(解耦)。
接口的出現,完成了解耦,說明有兩方,一方在使用這個規則,另一方在實現這個規則。
比如筆記本電腦使用這個規則,而外圍設備在實現這個規則。
3.7 接口和抽象類的區別
描述事物。
犬:按照功能分類: 導盲犬,緝毒犬...
犬的行為
吼叫();
吃飯();
abstract class 犬 { public abstract void 吼叫(); public abstract void 吃飯(); } class 緝毒犬 extends 犬 { public void 吼叫(){} public void 吃飯(){} public void 緝毒(){} } //對於緝毒,有可能還有緝毒豬,具備者緝毒功能,應該將緝毒功能抽取。 //對緝毒進行描述。 abstract class 緝毒 { public abstract void 緝毒(); }
緝毒犬既需要犬的功能又需要緝毒的功能。
無法直接多繼承。
是否可以多實現呢?可以的。
犬是接口,緝毒也是接口。緝毒犬多實現即可。
類負責描述的是事物的基本功能。接口負責描述事物的擴展功能。
緝毒犬是犬中一種。is a 關系,
將犬定義成類。而緝毒是犬的一個擴展功能。這時將緝毒定義接口。
這時描述就變成了這樣:
interface 緝毒able { public abstract void 緝毒(); } class 緝毒犬 extends 犬 implements 緝毒able { public void 吼叫(){code..} public void 吃飯(){} public void 緝毒(){} }
總結:
1,抽象類是描述事物的基本功能,可以定義非抽象的方法。
接口中定義只能是抽象方法,負責功能的擴展。
2,類與類之間是繼承關系 is a關系。
類與接口之間是實現關系 like a 關系。
門 open(); close(); 報警門。 class 門 { } interface 報警 { } class { public static void main(String[] args) { System.out.println("Hello World!"); } }
java-繼承進階_抽象類_接口