介面和內部類別為我們提供了一種將介面與實作分離的更結構化的方法。
抽象類別與介面是Java語言中定義為抽象概念的兩種機制,正是由於他們的存在才賦予java強大的物件導向的能力。他們兩者之間對抽象概念的支持有很大的相似,甚至可以互換,但也有區別。
一、抽象類別
我們都知道在物件導向的領域中一切都是物件,同時所有的物件都是透過類別來描述的,但是所有的類別來描述物件並不是所描述的物件。如果一個類別沒有足夠的資訊來描述一個具體的對象,而需要其他具體的類別來支撐它,那麼這樣的類別我們就稱它為抽象類別。例如new Animal(),我們都知道這個是產生一個動物Animal對象,但是這個Animal具體長成什麼樣子我們並不知道,它沒有一個具體動物的概念,所以他就是一個抽象類,需要一個具體的動物,如狗、貓來對它有特定的描述,我們才知道它長成啥樣。
在物件導向領域由於抽象的概念在問題領域沒有對應的具體概念,所以用以表徵抽象概念的抽象類別是不能實例化的。
同時,抽象類別體現了資料抽象的思想,是實現多態的一種機制。它定義了一組抽象的方法,至於這組抽象方法的具體表現形式有衍生類別來實現。同時抽象類別提供了繼承的概念,它的出發點就是為了繼承,否則它沒有存在的任何意義。所以說定義的抽象類別一定是用來繼承的,同時在一個以抽象類別為節點的繼承關係等級鏈中,葉子節點一定是具體的實作類別。 (不知這樣理解是否有錯誤!!!高手指點….)
在使用抽象類別時需要注意幾點:
1、抽象類別不能被實例化,且實例化的工作實例化,實例化的工作實例為它的子類別完成,它只需要有一個引用。
2、抽象方法必須由子類別來進行重寫。
3、只要包含一個抽象方法的抽象類,此方法必須定義成抽象類別,且不管是否有其他方法。
4、抽象類別中可以包含特定的方法,當然也可以不含抽象方法。
5、子類別中的抽象方法不能與父類別的抽象方法同名。
6、abstract不能與final並列修飾同一類別。
7、abstract 不能與private、static、final或native並列修飾同一個方法。 ,
實例:
將一個抽象動物類Animal,並提供抽象方法稱為cry(),貓、狗都是動物類的子類,由於cry()為抽象方法,所以Cat、Dogcry )方法。如下:
public abstract class Animal { public abstract void cry(); } public class Cat extends Animal{ @Override public void cry() { System.out.println("猫叫:喵喵..."); } } public class Dog extends Animal{ @Override public void cry() { System.out.println("狗叫:汪汪..."); } } public class Test { public static void main(String[] args) { Animal a1 = new Cat(); Animal a2 = new Dog(); a1.cry(); a2.cry(); } } -------------------------------------------------------------------- Output: 猫叫:喵喵... 狗叫:汪汪...
創建抽象類別和抽象方法非常有用,因為他們可以使類別的抽象性明確起來,並告訴使用者和編譯器打算怎樣使用他們.抽象類別還是有用的重構器,因為它們使我們可以很容易地將公共方法沿著繼承層次結構向上移動。 (From:Think in java )
二、介面
介面是比抽象類別更為抽象的「類別」。這裡給「類」加引號是我找不到更好的字來表示,但是我們要明確一點就是,介面本身就不是類,從我們不能實例化一個介面就可以看出。如new Runnable();肯定是錯誤的,我們只能new它的實作類別。
介面是用來建立類別與類別之間的協議,它所提供的只是一種形式,而沒有特定的實作。同時實作該介面的實作類別必須要實作該介面的所有方法,透過使用implements關鍵字,他表示該類別在遵循某個或某組特定的接口,同時也表示「interface只是它的外貌,但是現在需要聲明它是如何工作的」。
接口是抽象类的延伸,java了保证数据安全是不能多重继承的,也就是说继承只能存在一个父类,但是接口不同,一个类可以同时实现多个接口,不管这些接口之间有没有关系,所以接口弥补了抽象类不能多重继承的缺陷,但是推荐继承和接口共同使用,因为这样既可以保证数据安全性又可以实现多重继承。
在使用接口过程中需要注意如下几个问题:
1、个Interface的方所有法访问权限自动被声明为public。确切的说只能为public,当然你可以显示的声明为protected、private,但是编译会出错!
2、接口中可以定义“成员变量”,或者说是不可变的常量,因为接口中的“成员变量”会自动变为为public static final。可以通过类命名直接访问:ImplementClass.name。
3、接口中不存在实现的方法。
4、实现接口的非抽象类必须要实现该接口的所有方法。抽象类可以不用实现。
5、不能使用new操作符实例化一个接口,但可以声明一个接口变量,该变量必须引用(refer to)一个实现该接口的类的对象。可以使用 instanceof 检查一个对象是否实现了某个特定的接口。例如:if(anObject instanceof Comparable){}。
6、在实现多接口的时候一定要避免方法名的重复。
三、抽象类与接口的区别
尽管抽象类和接口之间存在较大的相同点,甚至有时候还可以互换,但这样并不能弥补他们之间的差异之处。下面将从语法层次和设计层次两个方面对抽象类和接口进行阐述。
3.1语法层次
在语法层次,java语言对于抽象类和接口分别给出了不同的定义。下面已Demo类来说明他们之间的不同之处。
使用抽象类来实现:
public abstract class Demo { abstract void method1(); void method2(){ //实现 } }
使用接口来实现
interface Demo { void method1(); void method2(); }
抽象类方式中,抽象类可以拥有任意范围的成员数据,同时也可以拥有自己的非抽象方法,但是接口方式中,它仅能够有静态、不能修改的成员数据(但是我们一般是不会在接口中使用成员数据),同时它所有的方法都必须是抽象的。在某种程度上来说,接口是抽象类的特殊化。
对子类而言,它只能继承一个抽象类(这是java为了数据安全而考虑的),但是却可以实现多个接口。
3.2设计层次
上面只是从语法层次和编程角度来区分它们之间的关系,这些都是低层次的,要真正使用好抽象类和接口,我们就必须要从较高层次来区分了。只有从设计理念的角度才能看出它们的本质所在。一般来说他们存在如下三个不同点:
1、 抽象层次不同。抽象类是对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。
2、 跨域不同。抽象类所跨域的是具有相似特点的类,而接口却可以跨域不同的类。我们知道抽象类是从子类中发现公共部分,然后泛化成抽象类,子类继承该父类即可,但是接口不同。实现它的子类可以不存在任何关系,共同之处。例如猫、狗可以抽象成一个动物类抽象类,具备叫的方法。鸟、飞机可以实现飞Fly接口,具备飞的行为,这里我们总不能将鸟、飞机共用一个父类吧!所以说抽象类所体现的是一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在"is-a" 关系,即父类和派生类在概念本质上应该是相同的。对于接口则不然,并不要求接口的实现者和接口定义在概念本质上是一致的, 仅仅是实现了接口定义的契约而已。
3、 设计层次不同。对于抽象类而言,它是自下而上来设计的,我们要先知道子类才能抽象出父类,而接口则不同,它根本就不需要知道子类的存在,只需要定义一个规则即可,至于什么子类、什么时候怎么实现它一概不知。比如我们只有一个猫类在这里,如果你这是就抽象成一个动物类,是不是设计有点儿过度?我们起码要有两个动物类,猫、狗在这里,我们在抽象他们的共同点形成动物抽象类吧!所以说抽象类往往都是通过重构而来的!但是接口就不同,比如说飞,我们根本就不知道会有什么东西来实现这个飞接口,怎么实现也不得而知,我们要做的就是事前定义好飞的行为接口。所以说抽象类是自底向上抽象而来的,接口是自顶向下设计出来的。
(上面纯属个人见解,如有出入、错误之处,望各位指点!!!!)
为了更好的阐述他们之间的区别,下面将使用一个例子来说明。该例子引自:http://blog.csdn.net/ttgjz/article/details/2960451
我们有一个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的功能,那么上述的定义方式就要反过来了。
批注:
ISP(Interface Segregation Principle):面向对象的一个核心原则。它表明使用多个专门的接口比使用单一的总接口要好。
一个类对另外一个类的依赖性应当是建立在最小的接口上的。
一個介面代表一個角色,不應當將不同的角色都交給一個介面。沒有關係的接口合併在一起,形成一個臃腫的大接口,這是對角色和接口的污染。
四、總結
1、 抽象類別在java語言中所表示的是一種繼承關係,一個子類別只能存在一個父類,但是可以存在多個介面。
2、 在抽象類別中可以擁有自己的成員變數和非抽象類別方法,但是介面中只能存在靜態的不可變的成員資料(不過一般都不在介面中定義成員資料),而且它的所有方法都是抽象的。
3、抽象類別與介面所反映的設計理念是不同的,抽象類別所代表的是「is-a」的關係,而介面所代表的是「like-a」的關係。
抽象類別和介面是java語言中兩種不同的抽象概念,他們的存在對多態提供了非常好的支持,雖然他們之間有很大的相似性。但是對於他們的選擇往往反應了您對問題領域的理解。只有對問題域的本質有良好的理解,才能做出正確、合理的設計。
鞏固基礎,提陞技術,不畏困難,攀登高峰! ! ! ! ! !
以上就是 java提高篇(四)-----抽象類別與介面的內容,更多相關內容請關注PHP中文網(www.php.cn)!