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
的實例化實際上沒有任何意義。對於像Shape
中draw
和clear
這種在類中沒有具體實現,要在子類中來實現的方法,我們可以將其聲明爲抽象方法,而一個含有抽象方法的類,必須要聲明爲抽象類;
抽象類
抽象方法的聲明:
abstract void f();
抽象類的定義:
abstract class ClassName{
abstract void f()
}
使用抽象類應該注意的點:
- 一個類如果包含了一個或多個抽象方法,那這個類必須是抽象類。
- 一個類即使沒有抽象的方法,它也可以被聲明爲抽象類(通常被用來防止該類被實例化)
- 一個抽象類的子類必須要實現父類中所有的抽象方法,否則子類也必須聲明爲抽象類(這很容易推導,假設子類繼承了父類的一個抽象方法,卻沒有將其實現,那子類也包含了抽象方法)
- 抽象方法無法被聲明是
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("成功擦去畫版上的矩形");
}
}
值得注意的是:
- 大家可能注意到在Shape接口中我刪去了成員變量
color
,實際上接口中可以包含成員變量,但是接口中的成員變量都是靜態成員變量,即使我們不設計,也會被隱式指明爲static final
. - 接口中的抽象方法,我們無須加上
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();
}
}
上面的Interface1
和Interface2
都有一個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的接口實現,幾個注意點:
- 接口中抽象方法必須實現
- 默認方法根據選擇覆蓋
- 靜態方法不需要實現
- 多繼承中,類的成員變量仍來自一個類,接口不提供成員變量(接口中的變量都是
final static
)
接口繼承
接口和類一樣,可以用extends
實現接口間的繼承
public interface Interface1 extends Interface2{
.......
}
接口與虛類的區別
- 接口支持多繼承,而抽象類(包括具體類)只能繼承一個父類。
- 接口中不能有實例成員變量,接口所聲明的成員變量全部是靜態常量,即便是變量不加public static final修飾符也是靜態常量。抽象類與普通類一樣各種形式的成員變量都可以聲明。
- 接口中沒有包含構造方法,由於沒有實例成員變量,也就不需要構造方法了。抽象類中可以有實例成員變量,也需要構造方法。
- 抽象類中可以聲明抽象方法和具體方法。Java 8之前接口中只有抽象方法,而Java 8之後接口中也可以聲明具體方法,具體方法通過聲明默認方法實現。