1.繼承:extends
2.好處:
1.提高了代碼的複用性
2.讓類與類之間產生關係,給第三個特徵多態提供了前提。沒繼承沒多態。
開發的核心內容:創建,使用對象,維護對象之間的關係。
3.舉例:
//繼承extends
class Person //父類,超類,基類...
{
String name;
int age;
}
class Student extends Person //子類
{
void study()
{
System.out.println(name + "...Student study." + age);
}
}
class Worker extends Person
{
void work()
{
System.out.println("Worker work.");
}
}
class Test
{
public static void main(String[] args)
{
Student s = new Student();
s.name = "zhangsan";
s.age = 22;
s.study();
}
}
4.單繼承,多繼承:
1)Java中支持單繼承,不直接支持多繼承,但多C++中的多繼承機制進行了改良。
2)單繼承:一個子類只能有一個直接父類。
多繼承:一個子類可以有多個直接父類。(Java中不允許,進行改良)
不直接支持,因爲多個父類中有相同成員,會產生調用不確定性。
在Java中是通過“多實現”的方式來體現。
class A{
void show(){
syso("a");
}
}
class B{
void show(){
syso("a");
}
}
class C extends A,B{} //多繼承出現調用不確定性,所以Java不支持
5.Java支持多重(多層)繼承:
C繼承B,B繼承A
就會出現繼承體系。
當要使用一個繼承體系(集合,io也一樣)時:
1.查看該體系的頂層類,瞭解該體系的基本功能。
2.創建體系中的最子類對象,完成功能的使用。
6.注意
1.不要爲了代碼複用性而去使用繼承,繼承是依賴關係的。
2.什麼時候定義繼承:
當類與類之間存在所屬關係的時候,就定義繼承。xxx是yyy的一種。
所屬關係is a
7.在子父類中,成員的特點體現:
1.成員變量
2.成員函數
3.構造函數
1)成員變量:
當本類的成員變量和局部變量同名用this區分。
當子父類中的成員變量同名用super區分父類.
this和super的用法很相似。
this:代表一個本類對象的引用
super:代表一個父類的空間。
class Person
{
int num = 4;
}
class Student extends Person //存在super=父類的空間(存儲在子類堆中)。
{
int num = 5; //在開發中幾乎沒有,父類中定的共性內容,在子類中無需定義。但是面試的時候多見。
void show()
{
System.out.println(this.num + "..." +super.num); //同名時要寫super,不同名則默認添加
//子類不能直接訪問父類的私有內容,可以在父類中定義接口函數set,get讓子類可以間接訪問私有內容。
}
}
class Worker extends Person
{
}
class Test
{
public static void main(String[] args)
{
Student s = new Student();
s.show();
}
}
內存分析:
父類先加載到方法區,父類沒有對象,它的成員變量存儲在子類堆中,和子類成員分開存儲。
子類不能直接訪問父類的私有內容,可以在父類中定義接口函數set,get讓子類可以間接訪問私有內容。
2)成員函數:
覆蓋:
當子父類中出現成員函數一模一樣的情況,會運行子類的函數。
這種現象,稱爲覆蓋操作,這是函數在子父類中的特性。
一模一樣:函數聲明一模一樣
函數有兩個特性:
1.重載:同一個類中。和返回值無關
2.覆蓋:子類中。也稱重寫,覆寫。
覆蓋注意事項:
1.子類方法覆蓋父類方法時,子類權限必須大於等於父類的權限。(public default private)
2.當父類方法設置爲私有,則不叫覆蓋操作。因爲父類方法就不能用。就是子類自己的新方法。
3.靜態只能覆蓋靜態,或是被靜態覆蓋。
什麼時候使用覆蓋:
當對一個類進行子類的擴展時,子類需要保留父類的功能聲明,但是要對定義子類中該功能的特有功能時,就使用覆蓋操作完成。
例子:
class Phone
{
void show
{
System.out.println("number");
}
}
class NewPhone extends Phone
{
void show() //覆蓋
{
System.out.println("name");
System.out.println("pic");
super.show(); //代碼複用
}
}
3)構造函數:
在子類構造對象時,發現,訪問子類構造函數時,父類也運行了。
原因:
在子類的構造函數中第一句有一個默認的隱式語句:super();
當構造函數調用了其他初始化構造函數的語句,必須寫在第一行。
子類的實例化過程:
子類中所有的構造函數默認都會訪問父類中的空參數的構造函數。
class Fu
{
fu(int x)
{
System.out.println("fu run");
}
}
class Zi extends Fu
{
Zi()
{
//super(); //隱式語句,調用父類中的空參數的構造函數,所以這時父類空參必須存在。除非自己調用父類有參構造函數
super(4);
System.out.println("zi run");
}
}
class Test
{
public static void main(String[] args)
{
Student s = new Student();
s.show();
}
}
爲什麼子類實例化的時候必須訪問父類中的構造函數:
因爲子類繼承了父類,獲取到了父類中的內容(屬性),所以在使用父類內容之前,要先看父類是如何對自己的內容進行初始化的。
爲了完成這必須的動作,就在子類的構造函數中就入了super()語句。
如果父類中沒有定義空參數構造函數,那麼子類的構造函數必須用super明確要調用父類的哪個構造函數。
注意:super語句必須要定義在子類構造函數的第一行。因爲父類的初始化要先完成。
同時子類構造函數中如果使用this調用了本類的構造函數時,那麼super就沒有了,因爲super和this都只能定義在第一行。所以只能有一個。但是可以保證的是,子類中肯定會有其他構造函數訪問父類的構造函數。
例子:
class Fu
{
fu(int x)
{
System.out.println("fu run");
}
}
class Zi extends Fu
{
Zi()
{
//super();
super(4); //調用父類構造方法
System.out.println("zi run");
}
Zi(int x)
{
this(); //此時沒有默認添加super(); 但是在調用的Zi()函數裏還是會調用super(),其實父類的構造方法至少會被調用一次。
System.out.println("zi(x) run");
}
}
8.Object類:是所有的類的直接或者間接父類,是根類。
class Demo //extends Objects
{
/*
Demo();
return;
*/
}
9.構造函數-子類的實例化過程 圖解:
重點:子類構造方法向父類進行構造方法的調用,將一些共性初始化內容放在父類的構造函數中。
過程:
Person p = new Person();
1.JVM讀取指定路徑下的person.class文件,並加載進內存,並會先加載Person的父類(如果有直接父類的情況下)。
2.在堆內存中開闢空間,分配內存地址
3.並在對象空間中,對對象中的屬性進行默認初始化。
4.調用對應的構造函數進行初始化。
5.在構造函數中,第一行先調用父類中構造函數進行初始化。
6.父類初始化完畢後,再對子類的屬性進行顯示初始化。
7.再進行子類構造函數的特定初始化。
8.初始化完畢後,將地址值賦值給引用變量。
子類成員變量 默認 初始化(int 爲 0)後,調用子類構造器(this)進棧,由第一行的super(),到了父類構造器(子類this)進棧,父類構造器初始化完畢出棧,子類成員變量再完成顯示初始化,然後子類構造器完成特定初始化,將地址值賦值給引用變量。
簡單點:
子類構造器加載,父類構造器加載完成,子類成員變量顯示初始化,子類加載器繼續完成。
class Fu
{
Fu()
{ //super();
(this.)show(); //this指向的是子類
}
void show()
{
System.out.println("fu show");
}
}
class Zi extends Fu
{
int num = 8;
Zi() //分水嶺
{
//super(); // 通過super初始化父類內容時,子類的成員變量並未初始化。等super()父類初始化完畢後,才進行子類的成員變量顯示初始化。
System.out.println(num)
//return;
}
void show()
{
System.out.println("zi show..." + num);
}
}
class ExtendsDemo
{
public static void main(String[] args)
{
Zi z = new Zi(); //輸出zi show...0,因爲此時父類構造器還沒有結束,而num的顯示初始化過程是再父類構造器初始化結束後進行的,所num的默認值爲0,然後在父類初始化的過程中,自帶的子類this回調了子類的show()函數,如果子類沒有show()函數,則再找父類的show()函數。
z.show(); //輸出:
//8 //父類構造器初始化完畢後,然後進行num的顯示初始化,再繼續執行子類的構造器,輸出num爲8
//zi...8 對象創建完畢,輸出特有數據num爲8
}
}
10.關鍵字final: 修飾符,最終化。
繼承的弊端:打破了封裝性。
1.final可以修飾類,方法,變量
2.final修飾的類不可以繼承
3.final修飾的方法不可以被覆蓋
4.final修飾的變量是一個常量,只能賦值一次。
①使用final和直接使用9區別在於,可以取常量名,加強閱讀性。
②final只固定變量的顯示初始化值,所以必須顯示賦值。
③一般都會加靜態static。如果再加public:
public static final int x = 4; 這稱爲全局常量。
爲什麼使用final:
其實程序如果一個數值是固定的,那麼直接使用這個數據就可以了,但是這樣閱讀性差,所以它該給數據起個名稱。而這個變量名稱的值不能變化,所以加上final固定。
命名規範:
1.變量名:首個單詞小寫,後面的每個單詞的首字母大寫
2.函數名:首個單詞小寫,後面的每個單詞的首字母大寫()
3.類名:每個單詞的首字母都大寫。
4.常量名:每個字母都大寫,單詞之間用下劃線分開。
final class Fu //不能被繼承
{
final void method()
{
//調用了底層系統的資源。
}
}
class Zi extends Fu
{
void method()
{
final int x = 9; //之後無法再賦值,使用final和直接使用9區別在於,可以取常量名,加強閱讀性。
System.out.println("haha");// 打破了父類的封裝性
}
}
11.抽象類:通過子類繼承抽象類,去調用裏面的非抽象方法。
抽象:籠統,模糊,看不懂。不具體。
特點:
1. 方法只有聲明沒有實現時,該方法就是抽象方法,需要被abstract修飾
抽象方法必須定義再抽象類中,該類必須被abstract修飾。
2.抽象類不可以被實例化,因爲調用抽象方法沒有意義。
3.抽象類必須有其子類覆蓋了所有的抽象方法後,子類纔可以實例化。
否者這個子類還是抽象類。
細節:
1.抽象類中的構造函數用於給子類對象進行初始化。
2.抽象類中可以不定義抽象方法。少見,目的就是不讓該類創建對象。AWT的適配器對象就是這種類。
通常這個類中的方法有方法體,但是沒有內容。
abstract class Demo
{
void show1(){}
void show2(){}
}
3. private 不可以和abstract組合:因爲子類要覆蓋抽象類中的所有方法。
static 不可以和 abstract組合:沒有任何意義
final 不可以和 abstract組合:final不可以被覆蓋。
4.抽象類和一般類的異同點:
相同點:都是用來描述事物,都在內定義了成員。
不同點:
1.一般類有足夠的信息描述事物。
抽象類描述事物的信息可能不足。
2.一般類中不能定義抽象方法,只能定義非抽象方法。
抽象類都可以。
3.一般類可以被實例化。
抽象類不可以被實例化。
5.抽象類一定是個父類,因爲需要子類覆蓋其方法後纔可以對子類實例化。
例子:
/*抽象類練習:
僱員示例:
需求:公司中程序員有姓名,工號,薪水,工作內容。
項目經理除了有姓名,工號,薪水,工作內容,還有獎金。
對給出需求進行數據建模。
分析:
找出設計的對象:
程序員:
屬性:姓名,工號,薪水
行爲:工作內容
經理:
屬性:姓名,工號,薪水,獎金
行爲:工作內容
程序員和經理不存在着直接繼承關係,但具有共性內容,可以進行抽取,都是公司的僱員。
可以將程序員和經理進行抽取,建立體系。
*/
//描述僱員
abstract class Employee
{
private String name;
private String id;
private double pay;
Employee(String name,String id, double pay)
{
this.name = name;
this.id = id;
this.pay = pay;
}
public void tell()
{
System.out.println(name + " : " + id + " " + pay + " RMB/month " );
}
public abstract void work();
}
//描述程序員
class Programmer extends Employee
{
Programmer(String name,String id, double pay)
{
super(name,id,pay); //父類構造函數調用。
}
public void work()
{
System.out.println("code...");
}
}
//描述經理
class Manager extends Employee
{
private int bonus;
Manager(String name,String id, double pay,int bonus)
{
super(name,id,pay);
this.bonus = bonus;
}
public void work()
{
System.out.println("manage");
}
}
class AbstractDemo2
{
public static void main(String[] args)
{
Programmer p = new Programmer("czq","001",20000);
p.tell();
p.work();
}
}
12.接口interface:不通過繼承,通過實現 implements。
1.類與類之間是繼承關係。
2.類與接口之間是實現關係。
3.接口與接口之間是繼承關係,而且接口可以多繼承,多繼承的關鍵是有沒有方法體。
1.當一個抽象類中的方法都是抽象的時候,這時可以將該抽象類用另一種形式給定義和表示,就是接口 interface。
2.注意點:
1.定義接口使用的關鍵字不是class,是interface
2.對於接口當中常見的成員,而且這些成員都有固定的修飾符(可以不寫,但是閱讀性極差):
1.全局常量 public static final
2.抽象方法 public abstract
接口中的成員都是公共的權限。
3.接口不可以實例化。
只能由實現了接口的子類並覆蓋了接口中所有的抽象方法後,該子類纔可以實例化。
否則,這個子類就是一個抽象類。
4.在Java中不直接支持多繼承,因爲會出現調用的不確定性。
所以Java將多繼承繼承進行改良,在java中變成了多實現。
一個類可以實現多個接口。
5.一個類在繼承另一個類的同時,還可以實現多個類。
接口的出現避免了單繼承的侷限性。
6.接口與接口之間是繼承關係,而且接口可以多繼承,多繼承的關鍵是有沒有方法體。
interface Demo
{
public static final int NUM = 4;
public abstract void show1();
public abstract void show2();
}
class DemoImpl implements Demo
{
public void show1()
{
}
public void show2()
{
}
}
/*
在Java中不直接支持多繼承,因爲會出現調用的不確定性。
所以Java將多繼承繼承進行改良,在java中變成了多實現。
一個類可以實現多個接口。
*/
//多實現解決了多繼承的不確定性
interface A
{
public void show();
}
interface Z
{
public void show(); //如果改成int,出現漏洞
}
class Test implements A,Z
{
public void show() //同時覆蓋,不存在繼承的不確定性
{
}
}
/*
一個類在繼承另一個類的同時,還可以實現多個類。
接口的出現避免了單繼承的侷限性。
*/
class Q
{
public void method()
{
}
}
class Test2 extends Q implements A,Z
{
}
class InterfaceDemo
{
public static void main(String[] arfs)
{
DemoImpl d = new DemoImpl();
System.out.println(d.NUM);
System.out.println(DemoImpl.NUM);
System.out.println(Demo.NUM);
}
}
5.接口和抽象類的區別:
相同點:都是不斷向上抽取而來的。
不同點:
1.抽象類需要被繼承,而且只能單繼承
接口需要被實現,而且可以多實現。
2.抽象類中可以定義抽象方法和非抽象方法,子類繼承後,可以直接使用非抽象方法。
接口中只能定義抽象方法,必須由子類去實現。
3.抽象類的繼承是 is a 關係,在定義該體系的基本共性內容。
接口的實現是 like a 關係,在定義體系的額外功能。
例子:
犬按功能分:導盲犬,搜爆犬
abstract class 犬 //基本功能用類定義
{
abstract void 吼叫();
}
interface 導盲 //導盲是額外功能,用接口
{
abstract void 導盲();
}
class 導盲犬 extends 犬 implements 導盲
{
public void 吼叫(){}
public void 導盲(){}
}
4.接口的特點:
①接口是對外暴露的規則。
②接口是程序的功能擴展。
③接口的出現降低耦合性。
接口可以用來多實現。
類與接口之間是實現關係,而且類可以繼承一個類的同時實現多個接口。
接口和接口之間可以有繼承關係,可以多繼承
5.接口的應用:
接口:
1.定義規則,使用規則。
2.實現規則
/*
筆記本電腦使用。
爲了擴展筆記本的功能,但日後出現什麼功能設備不知道。
定義一個規則,只要然後出現的設備都符合這個規則就可以了。
規則在Java中就是接口。
*/
interface USB //定義規則,①暴露的規則
{
public void open();
public void close();
}
class BookPC
{
public static void main(String[] args)
{
useUSB(new UPan()); //②功能擴展,插入U盤,並使用,
useUSB(new UsbMouse());
}
public static void useUSB(USB u) //**使用規則,傳入一個接口類型的引用變量,用於接收(指向)接口的子類對象。** //多態的形式。
{
u.open();
u.close();
}
}
//一年後。
//③這些設備和電腦的耦合性降低了,解耦
class UPan implements USB //實現規則,U盤要符合USB規則
{
public void open()
{
System.out.println("upan open");
};
public void close()
{
System.out.println("upan close");
};
}
/*
//不知道是什麼設備,所以應該用接口來實現。
class Mouse
{
}
class BookPC
{
public static void main(String[] args)
{
}
public static void useMouse(Mouse m)
{
m.open();
}
}*/