【Java基礎】——java面向對象(中)—繼承、抽象類、接口


一、面向對象——繼承

1、繼承概述

①什麼是繼承?

繼承是面向對象的一個重要方面。當多個類存在相同屬性和行爲時,將這些類抽取到單獨一個類中,那麼多個類無需再定義這些屬性和行爲,只需要繼承那個類即可。關鍵字extends表明正在構造的新生類派生於一個已存在的類。已存在的類被稱爲超類(superclss)、基類(base class)或父類(parent class);新類被稱爲子類(subclass)。“is-a”關係是繼承的一個明顯特徵。

②通過extends關鍵字來實現類與類的繼承,如:
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、什麼是方法覆蓋?(子父類中)方法覆蓋是指在子類中,出現和父類一模一樣的方法聲明的時候,會運行子類的函數,這種現象稱爲覆蓋操作。
方法覆蓋會發生在有繼承關係的父類和子類之間,而且是在子類類型中,子類繼承到父類的方法之後,覺得方法實現已經不足以滿足新一代的要求了,於是就給出了新的方法實現。
覆蓋注意事項:
  • 子類方法覆蓋父類方法時,子類權限必須大於等於父類中的權限。
  • 靜態只能覆蓋靜態或者被靜態覆蓋。
c、如何判斷方法是不是重載呢?
  • 方法名必須相同
  • 返回值類型可能不同
  • 參數列表必須不同:參數類型不同,參數個數不同,參數順序不同。

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可以修飾類,方法,變量
*/

//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();
	}
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章