在jdk的源碼中,存在這樣的一些接口,他們不包含任何的(抽象)方法,但是卻廣泛的存在。
這種接口我們稱之為Mark Interface,也就是標記接口。
這些接口呢,我們不用來實現任何的方法,他們的作用就是當某個類實現這個接口的時候,我們就認為這個類擁有了這個接口標記的某種功能了。
下面通過三個例子,分別介紹java中常用的三個標記接口:
RandomAccess 、Cloneable、java.io.Serializable
(1)RandomAccess
在C#中經常會有很多人在爭論,在遍歷集合時,到底是應該用for還是用foreach。
在Java中,卻完全不用再糾結這個問題:
java中有這樣一個接口
1 public interface RandomAccess { 2 }
這個接口的作用是判斷集合是否能快速訪問的。也就是傳入一個Index後,指針能否快速的移動到對應的元素上,還是需要像訪問隊列一樣依次移動到指定元素上。
如果我們在實現某個容器類時,容器(防盜連接:本文首發自http://www.cnblogs.com/jilodream/ )中的元素可以通過index快速的訪問到(一般核心的存儲接口是使用數組),那麼你在該類的定義處,就可以像ArrayList這樣打上一個RandomAccess接口的實現標簽:
1 public class ArrayList<E> extends AbstractList<E> 2 implements List<E>, RandomAccess, Cloneable, java.io.Serializable 3 { 4 //... 5 } 6 而LinkedList就沒有實現該標記接口 7 public class LinkedList<E> 8 extends AbstractSequentialList<E> 9 implements List<E>, Deque<E>, Cloneable, java.io.Serializable 10 { 11 //... 12 }
在使用的過程中,通過判斷是否實現RandomAccess接口,就可以決定采取哪種遍歷的方式了。
如下:
1 import java.util.List; 2 import java.util.RandomAccess; 3 4 public class SourceLearning 5 { 6 public void iteratorElements(List<String> list) 7 { 8 if (list instanceof RandomAccess) 9 { 10 for (int i = 0, size = list.size(); i < size; i++) 11 { 12 String element = list.get(i); 13 } 14 } 15 else 16 { 17 for (String element : list) 18 { 19 } 20 } 21 } 22 }
這樣針對於不同的List采取不同的遍歷形式,可以讓遍歷的速度更快。
(2)Cloneable
這個接口大家都非常熟悉,在深度拷貝的時候,常常用到該接口。這個接口也是一個標記接口:
1 public interface Cloneable { 2 }
他的作用是標記該對象的是否擁有克隆的能力。很多認或許會覺得疑惑,Object類本身就已經實現了 protected native Object clone() throws CloneNotSupportedException;
方法
按道理來說每一個類都應該可以運行clone方法的,為什麼還需要打上這樣一個接口。這樣的好處是以接口的形式標記對象是否擁有某種能力。想一想,倘若不通過標記接口的形式,我們在平常的工作中,如何實現呢?一般來說都是通過設定枚舉,或者增加變量來控制。這樣或許能解決問題,但是往往不能從面向對象的角度來優雅的解決問題。
想想接口的作用是什麼吧?接口就是用來標記某個類擁有了哪些功能、特性。而標記接口則是在面向對象的角度來看,更高層級的一種抽象:即使你擁有這個方法也不行,因為你沒有這個功能的標記接口。
所以在調用的clone的過程中,倘若對象沒有實現Cloneable 接口,那麼虛擬就會拋出一個CloneNotSupportedException,不支持的clone的異常。
(3)java.io.Serializable
這個接口是用來標記類是否支持序列化的。所謂的序列化就是將對象的各種信息轉化成可以存儲或者傳輸的一種形式。我記得我剛參加工作的時候,對這個序列化非常難以理解,覺得server返回一個對象,client接收即可,為什麼總要(防盜連接:本文首發自http://www.cnblogs.com/jilodream/ )序列化,反序列化的折騰。後來leader告訴我這是因為很多時候,由於通信協議的原因,在傳輸的過程中,復雜的類對象是不支持傳來傳去的,所以一般來說要轉化成流的形式,放在包中傳來傳去。言歸正傳,java.io.Serializable和Cloneable 接口一樣,倘若一個類沒有實現該接口,而被拿去序列化,虛擬機就會拋出不支持的異常,盡管從代碼的調用來說,不存在任何問題。
請看java源碼注釋中的第二個拋出異常:一個將要被序列化,但是未實現序列化接口的Object:
1 /** 2 * Write the specified object to the ObjectOutputStream. The class of the 3 * object, the signature of the class, and the values of the non-transient 4 * and non-static fields of the class and all of its supertypes are 5 * written. Default serialization for a class can be overridden using the 6 * writeObject and the readObject methods. Objects referenced by this 7 * object are written transitively so that a complete equivalent graph of 8 * objects can be reconstructed by an ObjectInputStream. 9 * 10 * <p>Exceptions are thrown for problems with the OutputStream and for 11 * classes that should not be serialized. All exceptions are fatal to the 12 * OutputStream, which is left in an indeterminate state, and it is up to 13 * the caller to ignore or recover the stream state. 14 * 15 * @throws InvalidClassException Something is wrong with a class used by 16 * serialization. 17 * @throws NotSerializableException Some object to be serialized does not 18 * implement the java.io.Serializable interface. 19 * @throws IOException Any exception thrown by the underlying 20 * OutputStream. 21 */ 22 public final void writeObject(Object obj) throws IOException { 23 if (enableOverride) { 24 writeObjectOverride(obj); 25 return; 26 } 27 try { 28 writeObject0(obj, false); 29 } catch (IOException ex) { 30 if (depth == 0) { 31 writeFatalException(ex); 32 } 33 throw ex; 34 } 35 }
至此,通過三種常用的標記接口,應該已經闡述清標記接口的使用情況了,個人認為這是一種非常抽象的面向對象的方式。即只通過向對象以添加標簽的形式,來標記這個對象可以或不可以實現某種功能,這要比直接定義變量或枚舉,要優雅的多。