介面是什麼,抽象類別又是什麼,有了抽象類別,我們為什麼還要有介面呢?
抽象類別的概念:
抽象類別是相對於普通類別而言的,普通類別是一個完善的功能類,可以直接產生實例化對象,並且在普通類別中可以包含建構方法、普通方法、static方法、常數和變數等內容。而抽象類別是指在普通類別的結構裡面增加抽象方法的組成部分,或直接將類別宣告為抽象型別;
同樣抽象方法是相對於普通方法而言的,在所有的普通方法上面都會有一個“{}”,這個表示方法體,有方法體的方法一定可以被物件直接使用。而抽象方法,是指沒有方法體的方法,同時抽象方法也必須使用關鍵字abstract做修飾。而擁有抽象方法的類別就是抽象類,抽象類別要使用abstract關鍵字聲明。
package abstractdemo;
//抽象类 public abstract class AbstractDemo { //抽象方法,没有方法体 public abstract void test();
public void test1(){
System.out.println("普通方法");
}
public static void main(String[] args) {
Class18f2cef21ebec83e6009fc1ed61f2bdf c = AbstractDemo.class;
System.out.println(c.getName());
}
}
package abstractdemo;
public abstract class AbstractTest {
public static void main(String[] args) {
AbstractDemo ad = new AbstractDemo() {
@Override public void test() {
System.out.println("实例抽象类要重写抽象类中所有的抽象方法!");
}
};
ad.test();
System.out.println(ad.getClass());
}
}
從上可知,抽象類別是無法直接進行實例化操作。為什麼不能直接實例化呢?當一個類別實例化之後,就意味著這個物件可以呼叫類別中的屬性或方法了,但在抽象類別裡存在抽象方法,而抽象方法沒有方法體,沒有方法體就無法進行呼叫。既然無法進行方法呼叫的話,又怎麼去產生實例化物件呢。所以實例化抽象類別時一定會先讓你重寫抽象類別中的抽象方法;如果一個抽象類別中沒有抽象方法,那麼這個類別不能被實例化,會在實例化時編譯報錯;
抽象類別的使用原則如下:
(1)抽象方法必須為public或protected(因為如果為private,則不能被子類別繼承,子類別便無法實現此方法),預設情況下預設為public;
(2)抽象類別不能直接實例化,需要依賴子類別採用向上轉型的方式處理;
(3)抽象類別必須有子類,使用extends繼承,一個子類別只能繼承一個抽象類別;
(4)子類別(如果不是抽象類別)則必須覆寫抽象類別之中的全部抽象方法(如果子類別沒有實現父類別的抽象方法,那麼子類別也是抽象類別)
package abstractdemo; //抽象类 public abstract class AbstractDemo { //抽象方法,没有方法体 public abstract void test(); public void test1(){ System.out.println("普通方法"); } public static void main(String[] args) { Class18f2cef21ebec83e6009fc1ed61f2bdf c = AbstractDemo.class; System.out.println(c.getName()); } } package abstractdemo; public class Test extends AbstractDemo{ @Override public void test() { System.out.println("继承抽象类,一定要重写抽象类中的抽象方法,如果不重写,那么该类也是抽象类!"); } } package abstractdemo; public class Demo { public static void main(String[] args) { AbstractDemo ad = new Test(); ad.test(); } }###
现在就可以清楚的发现:
(1)抽象类继承子类里面有明确的方法覆写要求,而普通类可以有选择性的来决定是否需要覆写;
(2)抽象类实际上就比普通类多了一些抽象方法而已,其他组成部分和普通类完全一样;
(3)普通类对象可以直接实例化,但抽象类的对象必须经过向上转型之后才可以得到。
虽然一个类的子类可以去继承任意的一个普通类,可是从开发的实际要求来讲,普通类尽量不要去继承另外一个普通类,而是去继承抽象类。
抽象类的使用限制:
1.由于抽象类里会存在一些属性,那么抽象类中一定存在构造方法,其存在目的是为了属性的初始化。抽象类中是有构造函数的,所以子类继承抽象类,构造方法的调用顺序仍然是先父类构造函数,然后子类构造函数
2.抽象类不可以修饰为final,因为抽象类有子类,而final修饰的类不能被继承;
3.外部抽象类不能被修饰为static,外部抽象类不允许使用static声明,而内部的抽象类运行使用static声明。使用static声明的内部抽象类相当于一个外部抽象类的成员,继承的时候使用“外部类.内部类”的形式表示类名称。
package com.wz.abstractdemo;static abstract class A{//定义一个抽象类public abstract void print(); }class B extends A{public void print(){ System.out.println("**********"); } }public class TestDemo {public static void main(String[] args) { A a = new B();//向上转型 a.print(); } }
执行结果
Exception in thread "main" java.lang.Error: Unresolved compilation problem: Illegal modifier for the class A; only public, abstract & final are permitted at com.wz.abstractdemo.A.<init>(TestDemo.java:3) at com.wz.abstractdemo.B.<init>(TestDemo.java:9) at com.wz.abstractdemo.TestDemo.main(TestDemo.java:18)
再看一个关于内部抽象类:
package com.wz.abstractdemo;abstract class A{//定义一个抽象类static abstract class B{//static定义的内部类属于外部类public abstract void print(); } }class C extends A.B{public void print(){ System.out.println("**********"); } }public class TestDemo {public static void main(String[] args) { A.B ab = new C();//向上转型 ab.print(); } }
执行结果:
**********
4.任何时候,如果要执行类中的static方法的时候,都可以在没有对象的情况下直接调用,对于抽象类也一样。但是修饰为abstract的抽象方法,不能修饰为static,没有意义;
5.有时候由于抽象类中只需要一个特定的系统子类操作,所以可以忽略掉外部子类。这样的设计在系统类库中会比较常见,目的是对用户隐藏不需要知道的子类。
范例如下:
package com.wz.abstractdemo;abstract class A{//定义一个抽象类public abstract void print();private static class B extends A{//内部抽象类子类public void print(){//覆写抽象类的方法System.out.println("Hello World !"); } }//这个方法不受实例化对象的控制public static A getInstance(){return new B(); } }public class TestDemo {public static void main(String[] args) {//此时取得抽象类对象的时候完全不需要知道B类这个子类的存在A a = A.getInstance(); a.print(); } }
运行结果:
Hello World !
抽象类的应用——模板设计模式
例如,现在有三类事物:
(1)机器人:充电,工作;
(2)人:吃饭,工作,睡觉;
(3)猪:进食,睡觉。
现要求实现一个程序,可以实现三种不同事物的行为。
先定义一个抽象行为类:
package com.wz.abstractdemo;public abstract class Action{public static final int EAT = 1 ;public static final int SLEEP = 3 ;public static final int WORK = 5 ;public abstract void eat();public abstract void sleep();public abstract void work();public void commond(int flags){ switch(flags){case EAT:this.eat();break;case SLEEP:this.sleep();break;case WORK:this.work();break;case EAT + SLEEP:this.eat();this.sleep();break;case SLEEP + WORK:this.sleep();this.work();break;default:break; } } }
定义一个机器人的类:
package com.wz.abstractdemo;public class Robot extends Action{ @Overridepublic void eat() { System.out.println("机器人充电"); } @Overridepublic void sleep() { } @Overridepublic void work() { System.out.println("机器人工作"); } }
定义一个人的类:
package com.wz.abstractdemo;public class Human extends Action{ @Overridepublic void eat() { System.out.println("人吃饭"); } @Overridepublic void sleep() { System.out.println("人睡觉"); } @Overridepublic void work() { System.out.println("人工作"); } }
定义一个猪的类:
package com.wz.abstractdemo;public class Pig extends Action{ @Overridepublic void eat() { System.out.println("猪进食"); } @Overridepublic void sleep() { System.out.println("猪睡觉"); } @Overridepublic void work() { } }
测试主类:
package com.wz.abstractdemo;public class AbstractDemo {public static void main(String[] args) { fun(new Robot()); fun(new Human()); fun(new Pig()); }public static void fun(Action act){ act.commond(Action.EAT); act.commond(Action.SLEEP); act.commond(Action.WORK); } }
运行结果:
机器人充电 机器人工作 人吃饭 人睡觉 人工作 猪进食 猪睡觉
所有的子類別如果要想正常的完成操作,必須按照指定的方法進行覆寫才可以,而這個時候抽象類別所起的功能就是一個類別定義模板的功能。
----------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- --------------------------------------------
介面是比抽象類別更抽象的「類別」。這裡給「類」加引號是我找不到更好的字來表示,但是我們要明確一點就是,介面本身就不是類,從我們不能實例化一個介面就可以看出。如new Runnable();肯定是錯誤的,我們只能new它的實作類別。
介面是用來建立類別與類別之間的協議,它所提供的只是一種形式,而沒有特定的實作。同時實作該介面的實作類別必須要實作該介面的所有方法,透過使用implements關鍵字,他表示該類別在遵循某個或某組特定的接口,同時也表示「interface只是它的外貌,但是現在需要聲明它是如何工作的」。
介面是抽象類別的延伸,Java了保證資料安全是不能多重繼承的,也就是說繼承只能存在一個父類,但是介面不同,一個類別可以同時實現多個接口,不管這些接口之間有沒有關係,所以接口彌補了抽象類別不能多重繼承的缺陷,但是推薦繼承和接口共同使用,因為這樣既可以保證數據安全性又可以實現多重繼承。
在使用介面過程中需要注意以下幾個問題:
1、1個Interface的所有方法存取權自動被宣告為public。確切的說只能為public,當然你可以顯示的聲明是protected、private,但是編譯會出錯!
2、介面中可以定義“成員變數”,或者說是不可變的常數,因為介面中的“成員變數”會自動變成為public static final。可以透過類別命名直接存取:ImplementClass.name。
3、介面中不存在實作的方法。
4、實作介面的非抽象類別必須實作該介面的所有方法。抽象類別可以不用實作。
5、不能使用new運算元實例化一個接口,但可以宣告一個介面變量,該變數必須引用(refer to)一個實作該介面的類別的物件。可以使用 instanceof 檢查一個物件是否實作了某個特定的介面。例如:if(anObject instanceof Comparable){}。
6、實作多重介面的時候一定要避免方法名稱的重複。
# 在Java語言中,abstract class和interface是支持抽象類別定義的兩種機制。正是由於這兩種機制的存在,才賦予了Java強大的物件導向能力。 abstract class和interface之間在對於抽象類別定義的支援方面具有很大的相似性,甚至可以相互替換,因此許多開發者在進行抽象類別定義時對於abstract class和interface的選擇顯得比較隨意。其實,兩者之間還是有很大的區別的,對於它們的選擇甚至反映出對於問題領域本質的理解、對於設計意圖的理解是否正確、合理。
|
#Abstract class |
Interface |
############### ##########不能##################不能###### |
||
類別 |
#一種繼承關係,一個類別只能使用一次繼承關係。可以透過繼承多個介面實作多重繼承 |
一個類別可以實作多個interface |
#資料成員 |
可有自己的 |
靜態的不能被修改即必須是static final,一般不在此定義 |
可以私有的,非abstract方法,抽象方法必須實作 |
#不能有私有的,預設是public,abstract 類型 |
|
變數 |
#可有私有的,預設是friendly型,其值可以在子類別中重新定義,也可以重新賦值 |
#不可有私有的,預設是public static final 型,且必須給予其初值,實作類別中不能重新定義,不能改變其值。 |
#設計概念 |
表示的是「is- a」關係 |
表示的是「like-a」關係 |
abstract class和interface在Java語言中都是用來進行抽象類別(本文中的抽象類別並非從abstract class翻譯而來,它表示的是一個抽象體,而abstract class為Java語言中用來定義抽象類別的一種方法)定義的,那麼什麼是抽象類,使用抽象類別能為我們帶來什麼好處呢?
宣告方法的存在而不去實作它的類別被叫做抽象類別(abstract class),它用來建立一個體現某些基本行為的類別,並為該類別宣告方法,但不能在該類別中實作該類別的情況。不能建立abstract 類別的實例。然而可以創建一個變量,其類型是一個抽象類,並讓它指向具體子類的一個實例。不能有抽象建構子或抽象靜態方法。 Abstract 類別的子類別為它們父類別中的所有抽象方法提供實現,否則它們也是抽象類別。取而代之,在子類別中實作該方法。知道其行為的其它類別可以在類別中實作這些方法。
介面(interface)是抽象類別的變體。在介面中,所有方法都是抽象的。多繼承性可透過實現這樣的介面而獲得。介面中的所有方法都是抽象的,沒有一個有程式體。介面只可以定義static final成員變數。介面的實作與子類別相似,除了該實作類別不能從介面定義中繼承行為。當類別實作特殊介面時,它定義(即將程序體給予)所有這種介面的方法。 然後,它可以在實作了該介面的類別的任何物件上呼叫介面的方法。由於有抽象類,它允許使用介面名作為引用變數的類型。通常的動態聯編將生效。參考可以轉換到 介面類型或從介面類型轉換,instanceof 運算子可以用來決定某物件的類別是否實作了介面。
介面可以繼承介面。抽象類別可以實作(implements)接口,抽象類別是可以繼承實體類,但前提是實體類別必須有明確的建構子。 介面更專注於“能實現什麼功能”,而不管“怎麼實現的”。
1.相同點
A. 兩者都是抽象類,都不能實例化。
B. interface實作類別及abstrct class的子類別都必須實作已經宣告的抽象方法。
2. 不同點
A. interface需要實現,要用implements,而abstract class需要繼承,要用extends。
B. 一個類別可以實作多個interface,但一個類別只能繼承一個abstract class。
C. interface強調特定功能的實現,而abstract class強調所屬關係。
D. 雖然interface實作類別及abstrct class的子類別都必須實現對應的抽象方法,但實作的形式不同。 interface中的每一個方法都是抽象方法,都只是宣告的
(declaration, 沒有方法體),實作類別必須實現。而abstract class的子類別可以選擇性地實作。
這個選擇有兩點意義:
一是Abastract class中並非所有的方法都是抽象的,只有那些冠有abstract的方法才是抽象的,子類別必須實作。那些沒有abstract的方法,在Abstrct class中必須定義方法體。
二是abstract class的子類別在繼承它時,對非抽象方法既可以直接繼承,也可以覆寫;而對抽象方法,可以選擇實現,也可以透過再次聲明其方法為抽象的方式,無需實現,留給其子類別來實現,但此類必須也聲明為抽象類別。既是抽象類,當然也不能實例化。
E. abstract class是interface與Class的中介。
interface是完全抽象的,只能宣告方法,而且只能宣告public的方法,不能宣告private及protected的方法,不能定義方法體,也 不能宣告實例變數。然而,interface卻可以宣告常數變量,並且在JDK中不難找出這種例子。但將常數變數放在interface中違背了其作為接 口的作用而存在的宗旨,也混淆了interface與類別的不同價值。如果的確需要,可以放在對應的abstract class或Class中。
abstract class在interface及Class中起到了承上啟下的作用。一方面,abstract class是抽象的,可以宣告抽象方法,以規範子類別必須實現的功能;另一方面,它又可以定義缺省的方法體,供子類別直接使用或覆寫。另外,它還可以定義自己 的實例變量,以供子類別透過繼承來使用。
interface的應用場合
#A. 類別與類別之前需要特定的介面進行協調,而不在乎其如何實作。
B. 作為能夠實現特定功能的標識存在,也可以是什麼介面方法都沒有的純粹標識。
C. 需要將一組類視為單一的類,而呼叫者只透過介面來與這組類發生聯繫。
D. 需要實現特定的多項功能,而這些功能之間可能完全沒有任何關聯。
abstract class的應用場合
#一句話,在既需要統一的接口,又需要實例變數或缺省的方法的情況下,就可以使用它。最常見的有:
A. 定義了一組接口,但又不想強迫每個實作類別都必須實作所有的接口。可以用abstract class定義一組方法體,甚至可以是空方法體,然後由子類別選擇自己感興趣的方法來覆寫。
B. 某些場合下,只靠純粹的介面不能滿足類別與類別之間的協調,也必需類別中表示狀態的變數來區別不同的關係。 abstract的中介作用可以很好地滿足這一點。
C. 規範了一組相互協調的方法,其中一些方法是共同的,與狀態無關的,可以共享的,無需子類分別實現;而另一些方法卻需要各個子類別根據自己特定的狀態來實現特定的功能。
介面與抽象類別的好處:
#好像定义接口是提前做了个多余的工作。下面我给大家总结了4点关于JAVA中接口存在的意义:
1、重要性:在Java语言中, abstract class 和interface 是支持抽象类定义的两种机制。正是由于这两种机制的存在,才赋予了Java强大的 面向对象能力。
2、简单、规范性:如果一个项目比较庞大,那么就需要一个能理清所有业务的架构师来定义一些主要的接口,这些接口不仅告诉开发人员你需要实现那些业务,而且也将命名规范限制住了(防止一些开发人员随便命名导致别的程序员无法看明白)。
3、维护、拓展性:比如你要做一个画板程序,其中里面有一个面板类,主要负责绘画功能,然后你就这样定义了这个类。
可是在不久将来,你突然发现这个类满足不了你了,然后你又要重新设计这个类,更糟糕是你可能要放弃这个类,那么其他地方可能有引用他,这样修改起来很麻烦。
如果你一开始定义一个接口,把绘制功能放在接口里,然后定义类时实现这个接口,然后你只要用这个接口去引用实现它的类就行了,以后要换的话只不过是引用另一个类而已,这样就达到维护、拓展的方便性。
4、安全、严密性:接口是实现软件松耦合的重要手段,它描叙了系统对外的所有服务,而不涉及任何具体的实现细节。这样就比较安全、严密一些(一般软件服务商考虑的比较多)。
尽管抽象类和接口之间存在较大的相同点,甚至有时候还可以互换,但这样并不能弥补他们之间的差异之处。下面将从语法层次和设计层次两个方面对抽象类和接口进行阐述。
在语法层次,java语言对于抽象类和接口分别给出了不同的定义。下面已Demo类来说明他们之间的不同之处。
使用抽象类来实现:
public abstract class Demo { abstract void method1(); void method2(){ //实现 } }
使用接口来实现
interface Demo { void method1(); void method2(); }
抽象类方式中,抽象类可以拥有任意范围的成员数据,同时也可以拥有自己的非抽象方法,但是接口方式中,它仅能够有静态、不能修改的成员数据(但是我们一般是不会在接口中使用成员数据),同时它所有的方法都必须是抽象的。在某种程度上来说,接口是抽象类的特殊化。
对子类而言,它只能继承一个抽象类(这是java为了数据安全而考虑的),但是却可以实现多个接口。
上面只是从语法层次和编程角度来区分它们之间的关系,这些都是低层次的,要真正使用好抽象类和接口,我们就必须要从较高层次来区分了。只有从设计理念的角度才能看出它们的本质所在。一般来说他们存在如下三个不同点:
1、 抽象层次不同。抽象类是对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。
2、 跨域不同。抽象类所跨域的是具有相似特点的类,而接口却可以跨域不同的类。我们知道抽象类是从子类中发现公共部分,然后泛化成抽象类,子类继承该父类即可,但是接口不同。实现它的子类可以不存在任何关系,共同之处。例如猫、狗可以抽象成一个动物类抽象类,具备叫的方法。鸟、飞机可以实现飞Fly接口,具备飞的行为,这里我们总不能将鸟、飞机共用一个父类吧!所以说抽象类所体现的是一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在"is-a" 关系,即父类和派生类在概念本质上应该是相同的。对于接口则不然,并不要求接口的实现者和接口定义在概念本质上是一致的, 仅仅是实现了接口定义的契约而已。
3、 设计层次不同。对于抽象类而言,它是自下而上来设计的,我们要先知道子类才能抽象出父类,而接口则不同,它根本就不需要知道子类的存在,只需要定义一个规则即可,至于什么子类、什么时候怎么实现它一概不知。比如我们只有一个猫类在这里,如果你这是就抽象成一个动物类,是不是设计有点儿过度?我们起码要有两个动物类,猫、狗在这里,我们在抽象他们的共同点形成动物抽象类吧!所以说抽象类往往都是通过重构而来的!但是接口就不同,比如说飞,我们根本就不知道会有什么东西来实现这个飞接口,怎么实现也不得而知,我们要做的就是事前定义好飞的行为接口。所以说抽象类是自底向上抽象而来的,接口是自顶向下设计出来的。
我们有一个Door的抽象概念,它具备两个行为open()和close(),此时我们可以定义通过抽象类和接口来定义这个抽象概念:
抽象类:
abstract class Door{ abstract void open(); abstract void close(); }
接口
interface Door{ void open(); void close(); }
至于其他的具体类可以通过使用extends使用抽象类方式定义Door或者Implements使用接口方式定义Door,这里发现两者并没有什么很大的差异。
但是现在如果我们需要门具有报警的功能,那么该如何实现呢?
解决方案一:给Door增加一个报警方法:clarm();
abstract class Door{ abstract void open(); abstract void close(); abstract void alarm(); }
或者
interface Door{ void open(); void close(); void alarm(); }
这种方法违反了面向对象设计中的一个核心原则 ISP (Interface Segregation Principle)(参见:),在Door的定义中把Door概念本身固有的行为方法和另外一个概念"报警器"的行为方 法混在了一起。这样引起的一个问题是那些仅仅依赖于Door这个概念的模块会因为"报警器"这个概念的改变而改变,反之依然。
解决方案二
既然open()、close()和alarm()属于两个不同的概念,那么我们依据ISP原则将它们分开定义在两个代表两个不同概念的抽象类里面,定义的方式有三种:
1、两个都使用抽象类来定义。
2、两个都使用接口来定义。
3、一个使用抽象类定义,一个是用接口定义。
由于java不支持多继承所以第一种是不可行的。后面两种都是可行的,但是选择何种就反映了你对问题域本质的理解。
如果选择第二种都是接口来定义,那么就反映了两个问题:1、我们可能没有理解清楚问题域,AlarmDoor在概念本质上到底是门还报警器。2、如果我们对问题域的理解没有问题,比如我们在分析时确定了AlarmDoor在本质上概念是一致的,那么我们在设计时就没有正确的反映出我们的设计意图。因为你使用了两个接口来进行定义,他们概念的定义并不能够反映上述含义。
第三种,如果我们对问题域的理解是这样的:AlarmDoor本质上Door,但同时它也拥有报警的行为功能,这个时候我们使用第三种方案恰好可以阐述我们的设计意图。AlarmDoor本质是们,所以对于这个概念我们使用抽象类来定义,同时AlarmDoor具备报警功能,说明它能够完成报警概念中定义的行为功能,所以alarm可以使用接口来进行定义。如下:
abstract class Door{ abstract void open(); abstract void close(); } interface Alarm{ void alarm(); } class AlarmDoor extends Door implements Alarm{ void open(){} void close(){} void alarm(){} }
这种实现方式基本上能够明确的反映出我们对于问题领域的理解,正确的揭示我们的设计意图。其实抽象类表示的是"is-a"关系,接口表示的是"like-a"关系,大家在选择时可以作为一个依据,当然这是建立在对问题领域的理解上的,比如:如果我们认为AlarmDoor在概念本质上是报警器,同时又具有Door的功能,那么上述的定义方式就要反过来了。
批注:
|
1、 抽象類別在java語言中所表示的是繼承關係,一個子類別只能存在一個父類,但可以存在多個介面。
2、 在抽象類別中可以擁有自己的成員變數和非抽象類別方法,但是介面中只能存在靜態的不可變的成員資料(不過一般都不在介面中定義成員資料),而且它的所有方法都是抽象的。
3、抽象類別和介面所反映的設計概念是不同的,抽象類別所代表的是「is-a」的關係,而介面所代表的是「like- a」的關係。
抽象類別和介面是java語言中兩種不同的抽象概念,他們的存在對多態提供了非常好的支持,雖然他們之間存在著很大的相似性。但是對於他們的選擇往往反應了您對問題領域的理解。只有對問題域的本質有良好的理解,才能做出正確、合理的設計。
以上是接口是什麼?抽象類別是什麼?的詳細內容。更多資訊請關注PHP中文網其他相關文章!