一、面向對象——繼承
1、繼承概述
①什麼是繼承?
繼承是面向對象的一個重要方面。當多個類存在相同屬性和行爲時,將這些類抽取到單獨一個類中,那麼多個類無需再定義這些屬性和行爲,只需要繼承那個類即可。關鍵字extends表明正在構造的新生類派生於一個已存在的類。已存在的類被稱爲超類(superclss)、基類(base class)或父類(parent class);新類被稱爲子類(subclass)。“is-a”關係是繼承的一個明顯特徵。
class 子類名 extends 父類名{}
有了繼承以後,我們在定義一個類的時候,可以在一個已經存在的類的基礎上,還可以定義自己的新成員。
但是,我們需要注意的是,在java中支持單繼承,不直接支持多繼承。但對c++中的多繼承機制進行了改良。
a、單繼承:一個子類只能有一個直接父類
b、多繼承:一個子類可以有多個直接父類。(java中不允許)因爲多個父類中有相同成員時,會產生調用的不確定性。
c、java支持多層繼承(多重繼承):如C繼承B,B繼承A,這樣就出現了繼承體系。
當我們需要使用一個繼承體系時,我們首先要查看該體系中的頂層類,瞭解該體系的基本功能,然後再創建體系中的最子類對象,完成功能的使用。
③繼承的設計原則:"高內聚低耦合"
所謂高內聚低耦合,簡單的理解,內聚就是指自己完成某件事情的能力,耦合就是類與類之間的關係。我們在設計繼承時候的原則就是:自己能完成的就不要麻煩別人,這樣將來別人產生了修改,就對我的影響較小。由此可見:在開發中使用繼承其實是在使用一把雙刃劍。今天我們還是以繼承的好處來使用,因爲繼承還有很多其他的特性。
④繼承的好處
- 將多個類相同的成員可以放到一個類中,提高了代碼的複用性。
- 如果功能的代碼需要修改,修改一處即可,提高了代碼的維護性。
- 讓類與類之間產生了關係,是多態的前提。但這也造成了高耦合,也是其弊端之一。
/*
繼承概述
*/
//使用繼承前
/*
class Student {
public void eat() {
System.out.println("喫飯");
}
public void sleep() {
System.out.println("睡覺");
}
}
class Teacher {
public void eat() {
System.out.println("喫飯");
}
public void sleep() {
System.out.println("睡覺");
}
}
*/
//使用繼承後,老師和學生都是人的範疇,抽取了老師和學生的共性功能,作爲人的功能,讓老師和學生去繼承。
class Person {
public void eat() {
System.out.println("喫飯");
}
public void sleep() {
System.out.println("睡覺");
}
}
class Student extends Person {}
class Teacher extends Person {}
class ExtendsDemo {
public static void main(String[] args) {
Student s = new Student();
s.eat();
s.sleep();
System.out.println("-------------");
Teacher t = new Teacher();
t.eat();
t.sleep();
}
}
2、繼承注意事項
①子類只能繼承父類所有非私有的成員(成員方法和成員變量)
②子類不能繼承父類的構造方法,但是可以通過super關鍵字去訪問父類構造方法。
③不要爲了部分功能去繼承。
④當類之間出現了”is-a“的關係時,我們纔可以去繼承。不能因爲兩個類中有部分代碼相同就使用繼承,這是不對的。
代碼演示如下:
/*
繼承的注意事項:
class A {
public void show1(){}
public void show2(){}
}
class B {
public void show2(){}
public void show3(){}
}
//我們發現B類中出現了和A類一樣的show2()方法,所以,我們就用繼承來體現
class B extends A {
public void show3(){}
}
這樣其實不好,因爲這樣你不但有了show2(),還多了show1()。
有可能show1()不是你想要的。
繼承其實體現的是一種關係:"is a"。
採用假設法。
如果有兩個類A,B。只有他們符合A是B的一種,或者B是A的一種,就可以考慮使用繼承。
*/
class Father {
private int num = 10;
public int num2 = 20;
//私有方法,子類不能繼承
private void method() {
System.out.println(num);
System.out.println(num2);
}
public void show() {
System.out.println(num);
System.out.println(num2);
}
}
class Son extends Father {
public void function() {
//num可以在Father中訪問private
//System.out.println(num); //子類不能繼承父類的私有成員變量
System.out.println(num2);
}
}
class ExtendsDemo3 {
public static void main(String[] args) {
// 創建對象
Son s = new Son();
//s.method(); //子類不能繼承父類的私有成員方法
s.show();
s.function();
}
}
3、super關鍵字
①super關鍵字和this的用法很像,this代表的是對應的引用,而super代表父類存儲空間的標識(父類引用)。
②super關鍵字的用法:
- 訪問成員變量:super.成員變量
- 訪問構造方法:super(……)
- 訪問成員方法:super.成員方法()。
爲什麼子類所有的構造方法默認都會訪問父類空參數的構造方法?
因爲子類會繼承父類的數據,可能還會使用父類的數據。所以,子類初始化之前,一定要先完成父類數據的初始化。爲了實現這個效果,在子類構造的第一條語句上默認有一個super()。
/*
繼承中構造方法的關係
A:子類中所有的構造方法默認都會訪問父類中空參數的構造方法
B:爲什麼呢?
因爲子類會繼承父類中的數據,可能還會使用父類的數據。
所以,子類初始化之前,一定要先完成父類數據的初始化。
注意:子類每一個構造方法的第一條語句默認都是:super();
*/
class Father {
int age;
public Father() {
System.out.println("Father的無參構造方法");
}
public Father(String name) {
System.out.println("Father的帶參構造方法");
}
}
class Son extends Father {
public Son() {
//super();
System.out.println("Son的無參構造方法");
}
public Son(String name) {
//super();
System.out.println("Son的帶參構造方法");
}
}
class ExtendsDemo6 {
public static void main(String[] args) {
//創建對象
Son s = new Son();
System.out.println("------------");
Son s2 = new Son("林青霞");
}
}
④繼承中成員方法的關係
a、當子父類中方法聲明不一樣的時候,通過子類對象去訪問方法,這個很容易訪問。
b、當子父類方法聲明一樣的時候,首先是通過子類對象去訪問方法,先查找子類中有沒有該方法,如果有該方法,就使用。如果子類中沒有該方法,則去父類中查找有沒有該方法,如果父類中有該方法,則使用。如果字父類中都沒有該方法,則報錯。
如下代碼所示:
/*
繼承中成員方法的關係:
*/
//父類
class Father {
public void show() {
System.out.println("show Father");
}
}
//子類
class Son extends Father {
public void method() {
System.out.println("method Son");
}
public void show() {
System.out.println("show Son");
}
}
class ExtendsDemo8 {
public static void main(String[] args) {
//創建對象
Son s = new Son();
s.show();//找子類。
s.method();//method son。先找子類中,子類有則調用。
//s.fucntion(); //找不到符號,子父類中都沒有該方法。
}
}
⑤方法重載(overroad)和方法覆蓋(override)a、什麼是方法重載?(同一個類中)方法重載是指在同一個類中,出現方法名相同,參數列表不同的情況。
b、什麼是方法覆蓋?(子父類中)方法覆蓋是指在子類中,出現和父類一模一樣的方法聲明的時候,會運行子類的函數,這種現象稱爲覆蓋操作。
方法覆蓋會發生在有繼承關係的父類和子類之間,而且是在子類類型中,子類繼承到父類的方法之後,覺得方法實現已經不足以滿足新一代的要求了,於是就給出了新的方法實現。
覆蓋注意事項:
- 子類方法覆蓋父類方法時,子類權限必須大於等於父類中的權限。
- 靜態只能覆蓋靜態或者被靜態覆蓋。
- 方法名必須相同
- 返回值類型可能不同
- 參數列表必須不同:參數類型不同,參數個數不同,參數順序不同。
3、阻止繼承:final類和方法
有時候,可能希望阻止人們利用某個類定義子類。不允許擴展的類被稱爲final類。如果在定義類的時候使用了final修飾符就表明這個類是final類。
①final關鍵字是最終的意思,可以修飾類,成員變量,成員方法。
- final關鍵字修飾的類不可以被繼承。
- final修飾的方法不可以被覆蓋。
- final修飾的變量是一個常量,只能被覆蓋。
②類中的方法也可以被聲明爲final。如果這樣做,子類就不能覆蓋這個方法(final類中的所有方法自動稱爲final方法。)將方法或類聲明爲final主要是鑑於以下原因:
- 確保它們不會在子類中改變語義。例如:Calendar類中的getTime個setTime方法都聲明爲final。這表明Calendar類的設計者負責實現Date類與日曆狀態之間的轉換,而不允許子類處理這些問題。同樣的,String類也是final類,這意味着不允許任何人定義String的子類。換而言之,如果有一個String的引用,它引用的一定是一個String對象,而不可能是其它對象。
其實在程序中如果一個數據是固定的,那麼直接使用這個數據就可以了。但是這樣閱讀性很差,所以就給該數據起個名稱,而且這個變量名稱的值不能再變化,所以加上final固定。
寫法規範:常量所有字符都大寫,如果多個單詞,中間用"_"連接。
代碼示範如下:
/*
final可以修飾類,方法,變量
*/
//final class Fu //無法從最終Fu進行繼承
class Fu {
public int num = 10;
public final int NUM_2 = 20;
// Zi中的show()無法覆蓋Fu中的show()
/* public final void show() {
System.out.println(num);
}
*/
}
class Zi extends Fu {
// Zi中的show()無法覆蓋Fu中的show()
public void show() {
num = 100;
System.out.println(num);
//無法爲最終變量num2分配值
//NUM_2 = 200;
System.out.println(NUM_2);
}
}
class FinalDemo {
public static void main(String[] args) {
Zi z = new Zi();//100
z.show();//20
}
}
④final關鍵字面試題
final修飾局部變量
- 在方法內部,該變量不可改變。
- 在方法聲明上,如果是基本類型,則值不能改變;如果是引用類型,則是地址值不能改變。
/*
面試題:final修飾局部變量的問題
基本類型:基本類型的值不能發生改變。
引用類型:引用類型的地址值不能發生改變,但是,該對象的堆內存的值是可以改變的。
*/
class Student {
int age = 10;
}
class FinalTest {
public static void main(String[] args) {
//局部變量是基本數據類型
int x = 10;
x = 100;
System.out.println(x);//100
final int y = 10;
//無法爲最終變量y分配值(基本類型)
//y = 100;
System.out.println(y);//10
System.out.println("--------------");
//局部變量是引用數據類型
Student s = new Student();
System.out.println(s.age);//10
s.age = 100;
System.out.println(s.age);//100
System.out.println("--------------");
//引用數據類型的地址值不可改變,但堆內存的值是可變的!
final Student ss = new Student();
System.out.println(ss.age);//10
ss.age = 100;
System.out.println(ss.age);//100
//重新分配內存空間
//錯誤:無法爲最終變量ss分配值(引用類型)
//ss = new Student();
}
}
final修飾變量的初始化時機:在構造對象前完畢即可。二、面向對象——抽象類
1、什麼是抽象?
定義:抽象類就從多個事物中,將共性的、本質的東西提取出來。
例如:老師和學生,都是人,都具有如姓名、性別、年齡等屬性。將其抽取出來,放置於繼承關係較高層次的通用超類人中。
2、抽象類
①什麼是抽象類?
Java中可以定義沒有方法體的方法,該方法的具體實現由子類完成,該方法稱爲抽象方法,包含抽象方法的類就是抽象類。
抽象類的由來:多個對象都具備相同的功能,但是功能具體內容有所不同,那麼在抽取過程中,只抽取了功能定義,並未抽取功能主體,那麼只有功能聲明,沒有功能主題的方法稱爲抽象方法。
②抽象類的特點
- 方法只有聲明沒有實現時,該方法就是抽象方法,需要abstract修飾,抽象方法必須定義在抽象類中,該類也必須被abstract修飾。
- 抽象類不能被實例化,因爲調用抽象方法沒有意義。抽象類按照多臺的方式,由具體的子類實例化。
- 抽象類必須有其子類覆蓋了所有的抽象方法後,該子類纔可以初始化,否則這個子類還是抽象類。
- 抽象類不一定有抽象方法,有抽象方法的類一定是抽象類。
/*
抽象類的概述:
動物不應該定義爲具體的東西,而且動物中的喫,睡等也不應該是具體的。
我們把一個不是具體的功能稱爲抽象的功能,而一個類中如果有抽象的功能,該類必須是抽象類。
抽象類的實例化其實是靠具體的子類實現的。是多態的方式。
People p = new Student();
*/
//abstract class People //抽象類的聲明格式
abstract class People {
//抽象方法
//public abstract void eat(){} //空方法體,這個會報錯。抽象方法不能有主體
public abstract void eat();
public People(){}
}
//子類是抽象類
abstract class Teacher extends People {}//沒有實現父類的抽象方法。還是抽象類。
//子類是具體類,重寫抽象方法
class Student extends People {
public void eat() {
System.out.println("學生喫飯");
}
}
class AbstractDemo {
public static void main(String[] args) {
//創建對象
//people是抽象的; 無法實例化
//People p = new People();
//通過多態的方式
People p = new Student();
p.eat();
}
}
3、抽象類的幾個問題
①抽象類中有構造方法嗎?有,用於給子類對象進行初始化。
②抽象類可以定義非抽象方法嗎?
可以。但是很少見,目的是不讓該類創建對象,AWT的適配器對象就是這種類,通常這個類中的方法有方法體,但是卻沒有內容。
③抽象的關鍵字不可以和哪些關鍵字共存?
private不行,static不行,final不行。
原因在於:
- final:被final修飾的類不能有子類。而abstract修飾的類一定是父類。
- private:抽象類中的私有的抽象方法,不能被子類所知,也就無法複寫,而抽象方法的出現就是要被複寫。
- static:如果static修飾抽象方法,那麼對象都不用new了。直接類名.方法調用就行了。
/*
一個類如果沒有抽象方法,可不可以定義爲抽象類?如果可以,有什麼意義?
A:可以。
B:不讓創建對象。
abstract不能和哪些關鍵字共存?
private 衝突
final 衝突
static 無意義
*/
abstract class Fu {
//public abstract void show();
//非法的修飾符組合: abstract和private
//private abstract void show();
//非法的修飾符組合
//final abstract void show();
//非法的修飾符組合
static abstract void show();
public static void method() {
System.out.println("method");
}
}
class Zi extends Fu {
public void show() {}
}
class AbstractDemo {
public static void main(String[] args) {
Fu.method();
}
}
4、抽象類和一般類的異同點
①相同點:抽象類和一般類都是用來描述事物的,都是內部定義了成員。②不同點:
- 一般類有足夠的信息描述事物;抽象類中描述的信息可能不足。
- 一般類不能定義抽象方法,只能定義非抽象方法;抽象類可以定義抽象方法,也可以定義非抽象方法。
- 一般類可以實例化;抽象類不可以實例化。
三、接口
1、接口的概念
接口可以被看成是一個特殊的抽象類。當一個抽象類中的方法都是抽象的時候,這是可以將抽象類用另一種形式來定義和表示,就是接口(interface)。定義接口使用的關鍵字不是class,而是interface,接口當中的常見成員,而且這些成員都有固定的修飾符:全局變量,抽象方法。
2、接口的特點
①接口用關鍵字interface表示。格式:interface接口名{}。
②類實現接口用implements表示。格式:class類名implements接口名{}。
③接口不是類,不能實例化。尤其不能用new運算符實例化一個接口。按照多態的方式,由具體的子類實例化。其實這也是多態的一種,接口多態。
④接口的子類要麼是抽象類,要麼重寫接口中的所有方法。
⑤不能構造接口的對象,但卻能聲明接口的變量。如:Comparable x;
⑥接口變量必須引用實現了接口的類對象。如:x = new XXXX();
⑦接口中不能包含實例域或靜態方法,但卻可以包含常量。
3、接口成員的特點
①成員變量:只能是常量。默認修飾符是public static final。
②構造方法:沒有,因爲接口主要是擴展功能的,而沒有具體存在
③成員方法:只能是抽象方法,默認修飾符public abstract。
4、接口必須掌握的知識
- 接口當中的成員都是公共的,是自動屬於public的。因此,在接口方法聲明中,可以不寫public。
- 類與類之間的是繼承關係(extends);類與接口之間是實現關係(implments)。
- 接口不可以實例化,只能由實現類接口的子類並覆蓋了接口中所有的抽象方法後,該子類纔可以實例化,否則,這個子類就是一個抽象類。
- 接口實現的步驟:a、將類聲明爲實現給定的接口。b、對接口中的所有方法進行定義。
- 在java中不直接支持多繼承,因爲會出現調用的不確定性,所以java將多繼承機制進行了改良,在java中可以多實現,即一個類可以實現多個接口。
- 一個類在繼承另一個類的同時,還能實現多個接口。接口的出現避免了單繼承的侷限性。
- 接口與接口之間可以繼承,而且接口可以多繼承。
5、抽象類和接口的異同點
①相同點:都是不斷向上抽取而來的。
②不同點:
- 抽象類需要被繼承,而且只能單繼承;接口需要被實現,而且能多實現。
- 抽象類中可以定義抽象方法和非抽象方法,子類繼承後,可以直接使用非抽象方法;接口中只能定義抽象方法,必須由接口去實現。
- 抽象類的繼承是is-a關係,在定義該體系內的基本共性內容;接口的實現是like-a關係,在定義體系的額外功能。
/*
老師和學生案例,加入抽菸的額外功能
分析:從具體到抽象
老師:姓名,年齡,喫飯,睡覺
學生:姓名,年齡,喫飯,睡覺
由於有共性功能,我們提取出一個父類,人類。
人類:
姓名,年齡
喫飯();
睡覺(){}
抽菸的額外功能不是人或者老師,或者學生一開始就應該具備的,所以,我們把它定義爲接口
抽菸接口。
部分老師抽菸:實現抽菸接口
部分學生抽菸:實現抽菸接口
實現:從抽象到具體
使用:具體
*/
//定義抽菸接口
interface Smoking {
//抽菸的抽象方法
public abstract void smoke();
}
//定義抽象人類
abstract class Person {
//姓名
private String name;
//年齡
private int age;
public Person() {}
//構造器初始化
public Person(String name,int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//喫飯方法;
public abstract void eat();
//睡覺睡覺方法{}
public void sleep() {
System.out.println("睡覺覺了");
}
}
//具體老師類
class Teacher extends Person {
public Teacher() {}
public Teacher(String name,int age) {
super(name,age);
}
public void eat() {
System.out.println("喫大白菜");
}
}
//具體學生類
class Student extends Person {
public Student() {}
public Student(String name,int age) {
super(name,age);
}
public void eat() {
System.out.println("喫紅燒肉");
}
}
//抽菸的老師
class SmokingTeacher extends Teacher implements Smoking {
public SmokingTeacher() {}
public SmokingTeacher(String name,int age) {
super(name,age);
}
//抽菸方法
public void smoke() {
System.out.println("抽菸的老師");
}
}
//抽菸的學生
class SmokingStudent extends Student implements Smoking {
public SmokingStudent() {}
public SmokingStudent(String name,int age) {
super(name,age);
}
//實現抽菸方法
public void smoke() {
System.out.println("抽菸的學生");
}
}
class InterfaceTest2 {
public static void main(String[] args) {
//測試學生
SmokingStudent ss = new SmokingStudent();
ss.setName("黃祥");
ss.setAge(23);
System.out.println(ss.getName()+"---"+ss.getAge());
ss.eat();
ss.sleep();
ss.smoke();
System.out.println("-------------------");
//測試老師
SmokingTeacher st = new SmokingTeacher();
st.setName("劉老師");
st.setAge(45);
System.out.println(st.getName()+"---"+st.getAge());
st.eat();
st.sleep();
st.smoke();
}
}