Java基礎系列4:抽象類與接口的前世此生

該系列博文會告訴你如何從入門到進階,一步步地學習Java基礎知識,並上手進行實戰,接着瞭解每一個Java知識點背後的實現原理,更完整地瞭解整個Java技術體系,造成本身的知識框架。java

 

一、抽象類:

 

當編寫一個類時,經常會爲該類定義一些方法,這些方法用以描述該類的行爲方式,那麼這些方法都有具體的方法體。但在某些狀況下,某個父類只是知道其子類應該包含怎樣的方法,但沒法準確地知道這些子類如何實現這些方法。例如定義了一個Shape類,這個類應該提供一個計算周長的方法calPerimeter(),但不一樣Shape子類對周長的計算方法是不同的,即Shape類沒法準確地知道其子類計算周長的方法。設計模式

可能有讀者會提出,既然Shape類不知道如何實現calPerimeter()方法,那就乾脆不要管它了!這不是一個好思路:假設有一個Shape引用變量,該變量實際上引用到Shape子類的實例,那麼這個Shape變量就沒法調用calPerimeter()方法,必須將其強制類型轉換爲其子類類型,纔可調用calPerimeter0方法,這就下降了程序的靈活性。框架

如何既能讓Shape類裏包含calPerimeter()方法,又無須提供其方法實現呢?使用抽象方法便可知足該要求:抽象方法是隻有方法簽名,沒有方法實現的方法。ide

 

定義

抽象方法和抽象類必須使用abstract修飾符來定義,有抽象方法的類只能被定義成抽象類,抽象類裏面能夠沒有抽象方法。學習

抽象方法和抽象類的規則以下:測試

  • 抽象類必須使用abstract修飾符來修飾,抽象方法也必須使用abstract修飾符來修飾,抽象方法不能有方法體。
  • 抽象類不能被實例化,沒法使用new關鍵字來調用抽象類的構造器建立抽象類的實例。即便抽象類裏不包含抽象方法,這個抽象類也不能建立實例。
  • 抽象類能夠包含成員變量、方法(普通方法和抽象方法均可以)、構造器、初始化塊、內部類(接口、枚舉)5種成分。抽象類的構造器不能用於建立實例,主要是用於被其子類調用。
  • 含有抽象方法的類(包括直接定義了一個抽象方法;或繼承了一個抽象父類,但沒有徹底實現父類包含的抽象方法;或實現了一個接口,但沒有徹底實現接口包含的抽象方法三種狀況)只能被定義成抽象類。

 

說了一大堆概念,聽得有點糊塗了,下面咱們來看一段代碼:this

下面定義一個Shape抽象類:設計

public abstract class Shape {
	
	{
		System.out.println("執行Shape的初始化塊");
	}
	
	private String color;
	
	//定義一個計算周長的抽象方法
	public abstract double calPerimeter();
	
	//定義一個返回形狀的抽象方法
	public abstract String getType();
	
	//定義Shape的構造器,該構造器並非用於建立對象,而是被子類調用
	public Shape() {}
	
	public Shape(String color) {
		System.out.println("執行Shape的構造器");
		this.color=color;
	}

	public String getColor() {
		return color;
	}

	public void setColor(String color) {
		this.color = color;
	}
	

}

  

上面的Shape類裏包含了兩個抽象方法:calPerimeter()和 getType(),因此這個Shape類只能被定義成抽象類。Shape類裏既包含了初始化塊,也包含了構造器,這些都不是在建立 Shape對象時被調用的,而是在建立其子類的實例時被調用。對象

抽象類不能用做建立實例,只能當作父類被其餘子類繼承。blog

下面定義一個三角形類,三角形類被定義成普通類,繼承Shape抽象類,所以必須實現Shape類中的抽象方法

public class Triangle extends Shape {
	
	//定義三角形的三邊
	private double a;
	private double b;
	private double c;
	
	public Triangle(String color,double a,double b,double c) {
		super(color);
		setSize(a, b, c);
	}
	
	public void setSize(double a,double b,double c) {
		if(a+b<=c||a+c<=b||b+c<=a) {
			System.out.println("三角形兩邊之和必須大於第三邊");
			return;
		}
		this.a=a;
		this.b=b;
		this.c=c;
	}
	
	

	//重寫Shape類計算周長的方法
	@Override
	public double calPerimeter() {
		return a+b+c;
	}

	//重寫Shape類返回形狀的方法
	@Override
	public String getType() {
		// TODO Auto-generated method stub
		return "三角形";
	}

}

  

上面的Triangle類繼承了Shape抽象類,並實現了Shape類中兩個抽象方法,是一個普通類,所以能夠建立 Triangle類的實例,可讓一個Shape類型的引用變量指向Triangle對象。

 

下面編寫測試代碼:

public class TestShape {

	public static void main(String[] args) {
		Shape s1=new Triangle("黑色", 3, 4, 5);
		System.out.println(s1.getColor());
		System.out.println(s1.getType());
	}
}

  

輸出結果:

執行Shape的初始化塊
執行Shape的構造器
黑色
三角形

  

利用抽象類和抽象方法的優點,能夠更好的發揮多態的優點,使得程序更加靈活。

 

使用抽象類有如下幾點須要注意:

 

一、當使用abstract修飾類時,代表這個類時抽象類,不能實例化,只能被繼承;當使用abstract修飾方法時,代表這個方法必須由子類去實現。

二、final修飾的類不能被繼承,final修飾的方法不能被重寫,所以final和abstract不能同時出現。

三、abstract不能用於修飾成員變量,不能用於修飾局部變量,即沒有抽象變量、沒有抽象成員變量等說法;abstract也不能用於修飾構造器,沒有抽象構造器,抽象類裏定義的構造器只能是普通構造器。

四、當使用static修飾一個方法時,代表這個方法屬於該類自己,即經過類就可調用該方法,但若是該方法被定義成抽象方法,則將致使經過該類來調用該方法時出現錯誤(調用了一個沒有方法體的方法確定會引發錯誤)。所以static和abstract不能同時修飾某個方法,即沒有所謂的類抽象方法。

五、abstract修飾的方法沒有方法體,必須被之類重寫纔有意義,因此抽象方法不能用private修飾,也就是private和abstract不能同時使用。

 

抽象類的做用:

  • 從前面的示例程序能夠看出,抽象類不能建立實例,只能當成父類來被繼承。從語義的角度來看,抽象類是從多個具體類中抽象出來的父類,它具備更高層次的抽象。從多個具備相同特徵的類中抽象出一個抽象類,以這個抽象類做爲其子類的模板,從而避免了子類設計的隨意性。
  • 抽象類體現的就是一種模板模式的設計,抽象類做爲多個子類的通用模板,子類在抽象類的基礎上進行擴展、改造,但子類整體上會大體保留抽象類的行爲方式。
  • 若是編寫一個抽象父類,父類提供了多個子類的通用方法,並把一個或多個方法留給其子類實現,這就是一種模板模式,模板模式也是十分常見且簡單的設計模式之一。例如前面介紹的 Shape、Triangle類,已經使用了模板模式。

 

 

二、接口

抽象類是從多個類中抽象出來的模板,若是將這種抽象進行得更完全,則能夠提煉出一種更加特殊的「抽象類」——接口(interface)。Java9對接口進行了改進,容許在接口中定義默認方法和類方法,默認方法和類方法均可以提供方法實現,Java9爲接口增長了一種私有方法,私有方法也可提供方法實

 

定義:

和類定義的不一樣,定義接口再也不使用class關鍵字,而是使用interface關鍵字。接口的基本語法以下:

[修飾符] interface 接口名稱 extends 父接口1 父接口2 ....

{

  零到多個常量定義...

  零到多個抽象方法定義...

  零到多個內部類,接口,枚舉定義...

  零到多個私有方法,默認方法或者類方法定義...

}

 

  1. 修飾符能夠是public或者省略,若是省略了public訪問控制符,則默認採用包權限訪問控制符,即只有在相同包結構下才能夠訪問該接口。
  2. 接口名應與類名採用相同的命名規則,即若是僅從語法角度來看,接口名只要是合法的標識符便可;若是要遵照Java可讀性規範,則接口名應由多個有意義的單詞連綴而成,每一個單詞首字母大寫,單詞與單詞之間無須任何分隔符。接口名一般可以使用形容詞。
  3. 一個接口能夠有多個直接父接口,但接口只能繼承接口,不能繼承類。
  4. 接口中能夠包含成員變量(只能是靜態常量),方法(只能是抽象實例方法、類方法、默認方法或私有方法),內部類(包括內部接口,枚舉)定義
  5. 接口中定義的常量系統會自動爲常量加上static和final兩個修飾符
  6. 接口中定義的方法只能是抽象實例方法、類方法、默認方法或私有方法,若是定義的不是類方法,默認方法和私有方法,系統將自動爲普通方法增長abstract修飾符,接口中的普通方法老是用public abstract來修飾,但類方法,默認方法,私有方法都必須有方法體實現。

 

下面來看一個具體的接口:

public interface Output {
	
	//接口中定義的成員變量只能是常量
	int MAX_CACHE_LINE=50;
	
	//接口中定義的普通方法只能是public abstract抽象方法
	void out();
	
	//在接口中定義默認方法,須要用default修飾	
	default void print(String...msg) {
		for (String str : msg) {
			System.out.println(str);
		}
	}
	
	//在接口中定義類方法,須要使用static修飾
	static String staticTest() {
		return "接口中的類方法";
	}
	
	//定義私有方法
	private void foo() {
		System.out.println("接口中的私有方法");
	}
	
	//定義私有靜態方法
	private static void bar() {
		System.out.println("bar私有靜態方法");
	}

}

  

接口的繼承:

接口的繼承和類繼承不同,接口徹底支持多繼承,即一個接口能夠有多個直接父接口。和類繼承類似,子接口擴展某個父接口,將會得到父接口裏定義的全部抽象方法、常量。

一個接口繼承多個父接口時,多個父接口排在extends關鍵字以後,多個父接口之間以英文逗號(,)隔開。下面程序定義了三個接口,第三個接口繼承了前面兩個接口。

 

interface InterfaceA{
	int PROP_A=5;
	void testA();
}

interface InterfaceB{
	int PROP_B=6;
	void testB();
}

interface InterfaceC extends InterfaceA,InterfaceB{
	int PROP_C=7;
	void testC();
}

public class InterfaceExtendsTest {
	
	public static void main(String[] args) {
		System.out.println(InterfaceC.PROP_A);
		System.out.println(InterfaceC.PROP_B);
		System.out.println(InterfaceC.PROP_C);
	}

}

  

 

使用接口:

接口不能用於建立實例,但接口能夠用於聲明引用類型變量。當使用接口來聲明引用類型變量時,這個引用類型變量必須引用到其實現類的對象。除此以外,接口的主要用途就是被實現類實現。概括起來,接口主要有以下用途。

  1. 定義變量,也可用於進行強制類型轉化
  2. 調用接口中定義的常量
  3. 被其餘類實現

一個類但是實現多個接口,用關鍵字implements實現,類實現接口的語法格式以下:

[修飾符] class 類名 extends 父類 implements 接口1,接口2...{

  類體部分

}

注意:類實現接口時必需要實現接口中全部的抽象方法

 

interface interfaceA{
	void printA();
}

interface interfaceB{
	void printB();
}

public class ImplmentsTest implements interfaceA,interfaceB {

	@Override
	public void printB() {
		System.out.println("printB");
	}

	@Override
	public void printA() {
		System.out.println("printA");
	}

}

  

上述代碼的ImplmentsTest實現了兩個接口,並重寫了其中的抽象方法

 

接口和抽象類的區別:

相同點:

  • 接口和抽象類都不能被實例化,它們都位於繼承樹的頂端,用於被其餘類實現和繼承。
  • 接口和抽象類均可以包含抽象方法,實現接口或繼承抽象類的普通子類都必須實現這些抽象方法。

 

不一樣點:

  • 接口裏只能包含抽象方法、靜態方法、默認方法和私有方法,不能爲普通方法提供方法實現;抽象類則徹底能夠包含普通方法。
  • 接口裏只能定義靜態常量,不能定義普通成員變量;抽象類裏則既能夠定義普通成員變量,也能夠定義靜態常量。
  • 接口裏不包含構造器;抽象類裏能夠包含構造器,抽象類裏的構造器並非用於建立對象,而是讓其子類調用這些構造器來完成屬於抽象類的初始化操做。
  • 接口裏不能包含初始化塊;但抽象類則徹底能夠包含初始化塊。
  • 一個類最多隻能有一個直接父類,包括抽象類;但一個類能夠直接實現多個接口,經過實現多個接口能夠彌補Java單繼承的不足。
相關文章
相關標籤/搜索