1. 程式人生 > >接口和抽象類有什麽區別?如何保證集合是線程安全的?談談你知道的設計模式?

接口和抽象類有什麽區別?如何保證集合是線程安全的?談談你知道的設計模式?

arr ora 組合模式 分段 queue 附加 接口實現 table 點數據

接著上篇繼續更新。

/*請尊重作者勞動成果,轉載請標明原文鏈接:*/

/* https://www.cnblogs.com/jpcflyer/p/10808649.html * /

題目一:接口和抽象類有什麽區別?

一般回答: 接口是對行為的抽象,它是抽象方法的集合,利用接口可以達到 API 定義和實現分離的目的。接口,不能實例化;不能包含任何非常量成員,任何 field 都是隱含著 public static final 的意義;同時,沒有非靜態方法實現,也就是說要麽是抽象方法,要麽是靜態方法。Java 標準類庫中,定義了非常多的接口,比如 java.util.List。 抽象類是不能實例化的類,用 abstract 關鍵字修飾 class,其目的主要是代碼重用。除了不能實例化,形式上和一般的 Java 類並沒有太大區別,可以有一個或者多個抽象方法,也可以沒有抽象方法。抽象類大多用於抽取相關 Java 類的共用方法實現或者是共同成員變量,然後通過繼承的方式達到代碼復用的目的。Java 標準庫中,比如 collection 框架,很多通用部分就被抽取成為抽象類,例如 java.util.AbstractList。
Java 類實現 interface 使用 implements 關鍵詞,繼承 abstract class 則是使用 extends 關鍵詞,我們可以參考 Java 標準庫中的 ArrayList。 下面再給出一種擴展回答: 1.語法層面上的區別 1)抽象類可以提供成員方法的實現細節,而接口中只能存在public abstract 方法; 2)抽象類中的成員變量可以是各種類型的,而接口中的成員變量只能是public static final類型的; 3)一個類只能繼承一個抽象類,而一個類卻可以實現多個接口。 2.設計層面上的區別 1)抽象類是對一種事物的抽象,即對類抽象,而接口是對行為的抽象。抽象類是對整個類整體進行抽象,包括屬性、行為,但是接口卻是對類局部(行為)進行抽象。舉個簡單的例子,飛機和鳥是不同類的事物,但是它們都有一個共性,就是都會飛。那麽在設計的時候,可以將飛機設計為一個類Airplane,將鳥設計為一個類Bird,但是不能將 飛行 這個特性也設計為類,因此它只是一個行為特性,並不是對一類事物的抽象描述。此時可以將 飛行 設計為一個接口Fly,包含方法fly( ),然後Airplane和Bird分別根據自己的需要實現Fly這個接口。然後至於有不同種類的飛機,比如戰鬥機、民用飛機等直接繼承Airplane即可,對於鳥也是類似的,不同種類的鳥直接繼承Bird類即可。從這裏可以看出,繼承是一個 "是不是"的關系,而 接口 實現則是 "有沒有"的關系。如果一個類繼承了某個抽象類,則子類必定是抽象類的種類,而接口實現則是有沒有、具備不具備的關系,比如鳥是否能飛(或者是否具備飛行這個特點),能飛行則可以實現這個接口,不能飛行就不實現這個接口。
2)設計層面不同,抽象類作為很多子類的父類,它是一種模板式設計。而接口是一種行為規範,它是一種輻射式設計。什麽是模板式設計?最簡單例子,大家都用過ppt裏面的模板,如果用模板A設計了ppt B和ppt C,ppt B和ppt C公共的部分就是模板A了,如果它們的公共部分需要改動,則只需要改動模板A就可以了,不需要重新對ppt B和ppt C進行改動。而輻射式設計,比如某個電梯都裝了某種報警器,一旦要更新報警器,就必須全部更新。也就是說對於抽象類,如果需要添加新的方法,可以直接在抽象類中添加具體的實現,子類可以不進行變更;而對於接口則不行,如果接口進行了變更,則所有實現這個接口的類都必須進行相應的改動。
下面看一個網上流傳最廣泛的例子:門和警報的例子:門都有open( )和close( )兩個動作,此時我們可以定義通過抽象類和接口來定義這個抽象概念:

1
abstract class Door { 2 public abstract void open(); 3 public abstract void close(); 4 }
或者:

1
interface Door { 2 public abstract void open(); 3 public abstract void close(); 4 }
但是現在如果我們需要門具有報警alarm( )的功能,那麽該如何實現?下面提供兩種思路: 1)將這三個功能都放在抽象類裏面,但是這樣一來所有繼承於這個抽象類的子類都具備了報警功能,但是有的門並不一定具備報警功能; 2)將這三個功能都放在接口裏面,需要用到報警功能的類就需要實現這個接口中的open( )和close( ),也許這個類根本就不具備open( )和close( )這兩個功能,比如火災報警器。 從這裏可以看出, Door的open() 、close()和alarm()根本就屬於兩個不同範疇內的行為,open()和close()屬於門本身固有的行為特性,而alarm()屬於延伸的附加行為。因此最好的解決辦法是單獨將報警設計為一個接口,包含alarm()行為,Door設計為單獨的一個抽象類,包含open和close兩種行為。再設計一個報警門繼承Door類和實現Alarm接口。

1
interface Alram { 2 void alarm(); 3 } 4 5 abstract class Door { 6 void open(); 7 void close(); 8 } 9 10 class AlarmDoor extends Door implements Alarm { 11 void oepn() { 12 //.... 13 } 14 void close() { 15 //.... 16 } 17 void alarm() { 18 //.... 19 } 20 }

題目二:如何保證集合是線程安全的?

一般回答: Java 提供了不同層面的線程安全支持。在傳統集合框架內部,除了 Hashtable 等同步容器,還提供了所謂的同步包裝器(Synchronized Wrapper),我們可以調用 Collections 工具類提供的包裝方法,來獲取一個同步的包裝容器(如 Collections.synchronizedMap),但是它們都是利用非常粗粒度的同步方式,在高並發情況下,性能比較低下。 另外,更加普遍的選擇是利用並發包提供的線程安全容器類,它提供了: 各種並發容器,比如 ConcurrentHashMap、CopyOnWriteArrayList。 各種線程安全隊列(Queue/Deque),如 ArrayBlockingQueue、SynchronousQueue。 各種有序容器的線程安全版本等。 具體保證線程安全的方式,包括有從簡單的 synchronize 方式,到基於更加精細化的,比如基於分離鎖實現的 ConcurrentHashMap 等並發實現等。具體選擇要看開發的場景需求,總體來說,並發包內提供的容器通用場景,遠優於早期的簡單同步實現。 接下來繼續擴展下HashMap和ConcurrentHashMap的實現。因為JAVA7和JAVA8的實現區別很大,下面分別從JAVA7和JAVA8簡單介紹下其實現。

Java7 HashMap

HashMap 是最簡單的,一來我們非常熟悉,二來就是它不支持並發操作,所以源碼也非常簡單。 首先,我們用下面這張圖來介紹 HashMap 的結構。 技術分享圖片技術分享圖片

大方向上,HashMap 裏面是一個數組,然後數組中每個元素是一個單向鏈表。 上圖中,每個綠色的實體是嵌套類 Entry 的實例,Entry 包含四個屬性:key, value, hash 值和用於單向鏈表的 next。

Java7 ConcurrentHashMap

ConcurrentHashMap 和 HashMap 思路是差不多的,但是因為它支持並發操作,所以要復雜一些。 整個 ConcurrentHashMap 由一個個 Segment 組成,Segment 代表”部分“或”一段“的意思,所以很多地方都會將其描述為分段鎖。註意,行文中,我很多地方用了“槽”來代表一個 segment。 簡單理解就是,ConcurrentHashMap 是一個 Segment 數組,Segment 通過繼承 ReentrantLock 來進行加鎖,所以每次需要加鎖的操作鎖住的是一個 segment,這樣只要保證每個 Segment 是線程安全的,也就實現了全局的線程安全。 技術分享圖片

技術分享圖片 concurrencyLevel:並行級別、並發數、Segment 數,怎麽翻譯不重要,理解它。默認是 16,也就是說 ConcurrentHashMap 有 16 個 Segments,所以理論上,這個時候,最多可以同時支持 16 個線程並發寫,只要它們的操作分別分布在不同的 Segment 上。這個值可以在初始化的時候設置為其他值,但是一旦初始化以後,它是不可以擴容的。 再具體到每個 Segment 內部,其實每個 Segment 很像之前介紹的 HashMap,不過它要保證線程安全,所以處理起來要麻煩些。

Java8 HashMap

Java8 對 HashMap 進行了一些修改,最大的不同就是利用了紅黑樹,所以其由 數組+鏈表+紅黑樹 組成。 根據 Java7 HashMap 的介紹,我們知道,查找的時候,根據 hash 值我們能夠快速定位到數組的具體下標,但是之後的話,需要順著鏈表一個個比較下去才能找到我們需要的,時間復雜度取決於鏈表的長度,為 O(n)。 為了降低這部分的開銷,在 Java8 中,當鏈表中的元素超過了 8 個以後,會將鏈表轉換為紅黑樹,在這些位置進行查找的時候可以降低時間復雜度為 O(logN)。 來一張圖簡單示意一下吧: 技術分享圖片

技術分享圖片
註意,上圖是示意圖,主要是描述結構,不會達到這個狀態的,因為這麽多數據的時候早就擴容了。
下面,我們還是用代碼來介紹吧,個人感覺,Java8 的源碼可讀性要差一些,不過精簡一些。 Java7 中使用 Entry 來代表每個 HashMap 中的數據節點,Java8 中使用 Node,基本沒有區別,都是 key,value,hash 和 next 這四個屬性,不過,Node 只能用於鏈表的情況,紅黑樹的情況需要使用 TreeNode。 我們根據數組元素中,第一個節點數據類型是 Node 還是 TreeNode 來判斷該位置下是鏈表還是紅黑樹的。

Java8 ConcurrentHashMap

Java7 中實現的 ConcurrentHashMap 說實話還是比較復雜的,Java8 對 ConcurrentHashMap 進行了比較大的改動。建議讀者可以參考 Java8 中 HashMap 相對於 Java7 HashMap 的改動,對於 ConcurrentHashMap,Java8 也引入了紅黑樹。 說實話,Java8 ConcurrentHashMap 源碼真心不簡單,最難的在於擴容,數據遷移操作不容易看懂。 我們先用一個示意圖來描述下其結構: 技術分享圖片 技術分享圖片 結構上和 Java8 的 HashMap 基本上一樣,不過它要保證線程安全性,所以在源碼上確實要復雜一些。

題目三:談談你知道的設計模式?

大致按照模式的應用目標分類,設計模式可以分為創建型模式、結構型模式和行為型模式。 創建型模式,是對對象創建過程的各種問題和解決方案的總結,包括各種工廠模式(Factory、Abstract Factory)、單例模式(Singleton)、構建器模式(Builder)、原型模式(ProtoType)。 結構型模式,是針對軟件設計結構的總結,關註於類、對象繼承、組合方式的實踐經驗。常見的結構型模式,包括橋接模式(Bridge)、適配器模式(Adapter)、裝飾者模式(Decorator)、代理模式(Proxy)、組合模式(Composite)、外觀模式(Facade)、享元模式(Flyweight)等。 行為型模式,是從類或對象之間交互、職責劃分等角度總結的模式。比較常見的行為型模式有策略模式(Strategy)、解釋器模式(Interpreter)、命令模式(Command)、觀察者模式(Observer)、叠代器模式(Iterator)、模板方法模式(Template Method)、訪問者模式(Visitor)。 關於設計模式就不再擴展了,童鞋們感興趣的話,可以對每種設計模式的使用場景進行思考總結。

接口和抽象類有什麽區別?如何保證集合是線程安全的?談談你知道的設計模式?