Java基礎學習筆記(十三)—— 抽象類與接口

Java基礎學習筆記(十三)—— 抽象類與接口

Nothing just happens,it's all part of a plan.

| @Author:TTODS


爲什麼要使用抽象類?

假設我們有一個2D畫版,我們可以在畫版上畫出各種形狀。我們先建一個Shape類,它有成員變量 color,draw方法,clear方法等

public class Shape{
	String color;
	Shape(){
		color = "Black";
	}
	void draw() {} //由於Shape本身是不明確的所以無法繪製
	public void clear(){}
	public void setColor(String c) {
		color = c;
	}
	public String getColor(String c) {
		return color;
	}
}

然後爲Shape類建兩個子類,Circle類和Rectangle

public class Circle extends Shape{
	private int radius;
	Circle(int r){
		radius  = r;
	}
	void draw() {
		System.out.printf("在畫版上繪製了一個半徑爲 %d,顏色爲 %s 的圓\n",radius,color);
	}
	void clear() {
		System.out.println("成功擦去畫版上的圓");
	}
}
public class Rectangle extends Shape {
	private int length,width;
	Rectangle(int l,int w){
		length = l;
		width = w;
	}
	void draw() {
		System.out.printf("在畫版上繪製了一個長爲 %d,寬爲%d,顏色爲 %s 的矩形\n", length,width,color);
	}
	void clear() {
		System.out.println("成功擦去畫版上的矩形");
	}
}

新建一個Painting類,在main方法中新建Circle類、Rectangle類和Shape類的實例,並分別調用它們的draw方法和clear方法。

public class Painting{
	public static void main(String[] args) {
		Circle c = new Circle(10);
		Rectangle r = new Rectangle(10,5);
		Shape s = new Shape();
		r.setColor("red");
		c.draw();
		c.clear();
		r.draw();
		r.clear();
		s.draw();//Shape的draw方法體爲空
		s.clear();//Shape的clear方法體爲空
	}
}

輸出

在畫版上繪製了一個半徑爲 10,顏色爲 Black 的圓
成功擦去畫版上的圓
在畫版上繪製了一個長爲 10,寬爲5,顏色爲 red 的矩形
成功擦去畫版上的矩形

從上例中我們發現Shape類的draw方法和clear方法的方法體都是空的,因爲Shape本來就是一個概念性的東西,無法在畫版上畫出,換句話說在本例中將Shape的實例化實際上沒有任何意義。對於像Shapedrawclear這種在類中沒有具體實現,要在子類中來實現的方法,我們可以將其聲明爲抽象方法,而一個含有抽象方法的類,必須要聲明爲抽象類

抽象類

抽象方法的聲明:

abstract void  f();

抽象類的定義:

abstract class ClassName{
	abstract void f()
}

使用抽象類應該注意的點:

  1. 一個類如果包含了一個或多個抽象方法,那這個類必須是抽象類。
  2. 一個類即使沒有抽象的方法,它也可以被聲明爲抽象類(通常被用來防止該類被實例化)
  3. 一個抽象類的子類必須要實現父類中所有的抽象方法,否則子類也必須聲明爲抽象類(這很容易推導,假設子類繼承了父類的一個抽象方法,卻沒有將其實現,那子類也包含了抽象方法)
  4. 抽象方法無法被聲明是private的,事實上抽象方法只能是public(默認),protected

什麼是接口?

接口是比抽象類更加抽象的類,或者說抽象類是介於普通類與接口之間的手段。
接口是完全抽象的類,不提供任何已實現的方法(這是java8之前的說法,因爲java8中,允許接口包含靜態方法和默認方法)。

接口的創建

接口的創建於類類似,使用interface關鍵字代替class關鍵字

public interface Shape{
	 void draw();
	 void clear();
}

在實現接口時我們使用implements來指定接口,若有多個接口有逗號(,)隔開

public class Circle implements  Shape{
	private int radius;
	Circle(int r){
		radius  = r;
	}
	public void draw() {
		System.out.printf("在畫版上繪製了一個半徑爲 %d 的圓\n",radius);
	}
	public void clear() {
		System.out.println("成功擦去畫版上的圓");
	}
}
public class Rectangle implements Shape {
	private int length,width;
	Rectangle(int l,int w){
		length = l;
		width = w;
	}
	public void draw() {
		System.out.printf("在畫版上繪製了一個長爲 %d,寬爲%d\n", length,width);
	}
	public void  clear() {
		System.out.println("成功擦去畫版上的矩形");
	}
}

值得注意的是:

  1. 大家可能注意到在Shape接口中我刪去了成員變量color,實際上接口中可以包含成員變量,但是接口中的成員變量都是靜態成員變量,即使我們不設計,也會被隱式指明爲static final.
  2. 接口中的抽象方法,我們無須加上abstract修飾(當然加上也不會報錯),因爲編譯器已經知道接口中的方法都是抽象的。

接口中的默認方法

java8爲default關鍵字添加了一個新的用途(之前僅在switch語句體中用到),接口中用default聲明的方法允許實現接口時沒有實現default方法的類使用接口中默認的方法體。看了下面這個例子也許能更好的理解default的用法與用途。
我們修改上面的Shape接口,爲其添加一個新的shift()方法,用來實現形狀在畫版上的移動;

public interface Shape{
	 void draw();
	 void clear();
	 void shift();
}

點擊保存,此時我們的編譯器報錯了,原因是我們之前寫好的Circle類中並沒有實現shift方法,也就是說之前所有基於接口Shape的類都必須更改。這種情況下我們可以使用default關鍵字了,修改Shape接口代碼如下

public interface Shape{
	 void draw();
	 void clear();
	 default void shift() {
		 System.out.println("沒有找到適合該實例類型shift方法(可能該實例類型是在Shape接口新增shift方法之前實現的)");
	 };
}

這樣編譯器不僅不會報錯,我們甚至可以用Circle實例調用默認的shift方法,修改下Painting類中的main函數

public class Painting{
	public static void main(String[] args) {
		Circle c = new Circle(10);
		Rectangle r = new Rectangle(10,5);
		c.draw();
		c.clear();
		c.shift();//新增
		r.draw();
		r.clear();
		r.shift();//新增
	}
}

輸出

在畫版上繪製了一個半徑爲 10 的圓
成功擦去畫版上的圓
沒有找到適合該實例類型shift方法(可能該實例類型是在Shape接口新增shift方法之前實現的)
在畫版上繪製了一個長爲 10,寬爲5
成功擦去畫版上的矩形
沒有找到適合該實例類型shift方法(可能該實例類型是在Shape接口新增shift方法之前實現的)

接口與多繼承

我們知道在C++中允許一個類繼承多個父類,這就帶來了問題,如果這多個父類中存在方法名與參數列表一樣的函數(方法),那麼子類實例調用的方法是那個父類的呢?這就帶來了問題,因此java中規定一個類只能繼承一個父類。但java可以通過實現多個接口來實現多繼承,因爲接口中的方法都是抽象的,即使是名字和參數列表一樣的方法,它們也都是沒有實現的,最終還是取決於子類中對這些方法的實現。

public interface Interface1{
	 void methodA();
	 void methodB();
}
public interface Interface2{
	 void methodA();
	 void methodC();
}
public class Test implements Interface1,Interface2{
	public void methodA() {
		System.out.println("This is methodA()");
	}
		public void methodB() {
		System.out.println("This is methodB()");
	}
		public void methodC() {
		System.out.println("This is methodC()");
	}
	public static void main(String[] args) {
		Test test1 =new Test();
		test1.methodA();
		test1.methodB();
		test1.methodC();
	}
}

上面的Interface1Interface2都有一個method方法,而Test實現 了這兩個接口,這是不會產生問題的。

但是java8中新增了默認方法,產生了類似C++中的衝突問題,在java中對於方法名和參數類別相同的默認方法,我們可以通過覆寫衝突的方法,指定繼承那個接口的方法,如下

public interface Interface1{
	 void methodA();
	 default void methodB() {
		 System.out.println("This is Interface1.methodB()");
	 }
}
public interface Interface2{
	 void methodA();
	 default void methodB() {
		 System.out.println("This is Interface1.methodB()");
	 }
}
public class Test implements Interface1,Interface2{
	public void methodA() {
		System.out.println("This is methodA");
	}
	@Override
	public void methodB() {
		Interface1.super.methodB(); //繼承Interface1的methodB()方法
		//Interface2.super.methodB();  繼承Interface1的methodB()方法
	}
	public static void main(String[] args) {
		Test test1 =new Test();
		test1.methodA();
	}
}

關於java的接口實現,幾個注意點:

  1. 接口中抽象方法必須實現
  2. 默認方法根據選擇覆蓋
  3. 靜態方法不需要實現
  4. 多繼承中,類的成員變量仍來自一個類,接口不提供成員變量(接口中的變量都是final static

接口繼承

接口和類一樣,可以用extends實現接口間的繼承

public interface Interface1 extends Interface2{
 .......
}

接口與虛類的區別

  1. 接口支持多繼承,而抽象類(包括具體類)只能繼承一個父類。
  2. 接口中不能有實例成員變量,接口所聲明的成員變量全部是靜態常量,即便是變量不加public static final修飾符也是靜態常量。抽象類與普通類一樣各種形式的成員變量都可以聲明。
  3. 接口中沒有包含構造方法,由於沒有實例成員變量,也就不需要構造方法了。抽象類中可以有實例成員變量,也需要構造方法。
  4. 抽象類中可以聲明抽象方法和具體方法。Java 8之前接口中只有抽象方法,而Java 8之後接口中也可以聲明具體方法,具體方法通過聲明默認方法實現。

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