文章出處:https://www.javazhiyin.com/tag/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F
作者:凸凹裏歐
方法迭代:代的更迭,從初代到末代的遍歷,指對某類集合中的每個元素按順序取出的行爲。舉個例子,通常我們讀小說是從前往後翻,一頁接着一頁地讀,這樣我們纔可以瞭解一個連續完整的故事,那這就需要我們順序地迭代整本書的每一頁內容。
相信大家都用過集合類吧,最常用的比如List,Set,Map以及各種各樣不同數據表示實現,總之是把某一批類似的元素按某種數據結構集合起來作爲一個整體來引用,不至於元素丟的到處都是難以維護,當要用到每個元素的時候,我們需要將它們一個個的取出來,但是對不同的數據類型訪問方式各有不同,於是我們就需要定義統一的迭代器來標準化這種遍歷行爲。
在此着重補充下:接口繼承接口,接口不實現接口;抽象類繼承接口 ;類繼承類/抽象類。
爲何會有各種各樣的遍歷方式呢?比如說彈夾,裝填子彈的時候要一顆一顆的進行壓棧,等到射擊的時候就需要迭代操作,先出棧拿出最後裝填的子彈再進行射擊,然後反向往前遍歷直到最初裝填的子彈直到射完爲止,此刻也代表着迭代的結束,整個過程像是內存棧的操作,先進後出,後進先出,當然這並不代表其迭代器非要先進後出,這裏只是舉例說明針對不同數據類型進行不同的迭代方式。
以上數據結構及迭代器其實都有現成的類去實現,那麼我們這裏來自定義一種全新的數據結構,可以防止碰瓷!?牛吹得有點大了,我們就以行車記錄儀舉例,大家先想想怎麼來記錄一段一段的視頻呢?如果我們簡單的利用ArrayList去記錄,那它得有多大空間去支持一直拍攝視頻?
我們知道其實它是循環覆寫的,待空間不夠用時,最新的視頻總會去覆蓋掉最老的視頻,以首尾相接的環形結構解決空間有限的問題。好,開始代碼實戰,首先我們定義一個行車記錄儀類。
public class DrivingRecorder {
private int index = -1;// 當前記錄位置
private String[] records = new String[10];// 假設只能記錄10條視頻
public void append(String record) {
if (index == 9) {// 循環覆蓋
index = 0;
} else {
index++;
}
records[index] = record;
}
public void display() {// 循環數組並顯示所有10條記錄
for (int i = 0; i < 10; i++) {
System.out.print("[" + i + "]:" + records[i] + " ");
}
}
public void displayInOrder() {//按順序從新到舊顯示10條記錄
for (int i = index, loopCount = 0; loopCount < 10; i = i == 0 ? i = 9 : i - 1, loopCount++) {
System.out.print(records[i]+" ");
}
}
public static void main(String[] args) {
DrivingRecorder drivingRecorder = new DrivingRecorder();
for (int i = 0; i < 11; i++) {
drivingRecorder.append(String.valueOf(i));
}
drivingRecorder.display();
System.out.println();
drivingRecorder.displayInOrder();
System.out.println();
}
}
輸出結果:
[0]:10 [1]:1 [2]:2 [3]:3 [4]:4 [5]:5 [6]:6 [7]:7 [8]:8 [9]:9
10 9 8 7 6 5 4 3 2 1
假設我們的記錄儀存儲空間只夠錄10段視頻,我們定義一個原始的字符串數組(第3行)來模擬記錄,並且用一個遊標(第2行)來記錄當前記錄所在位置。當插入視頻的時候(第5行)我們得先看有沒有錄滿到頭了,如果是的話就要把遊標調整到頭以後再記錄視頻。視頻目前可以循環記錄了,但總得給用戶顯示出來看吧,於是我們又提供了兩個顯示方法,一個是按默認數組順序顯示,一個是按用戶習慣從新到舊地顯示內容(邏輯稍微複雜了點但這裏不是重點,讀者可以略過),開始寫用戶類來使用這個記錄儀。
public class Client {
public static void main(String[] args) {
DrivingRecorder dr = new DrivingRecorder();
//假設記錄了12條視頻
for (int i = 0; i < 12; i++) {
dr.append("視頻_" + i);
}
dr.display();
/*按原始順序顯示,視頻0與1分別被10與11覆蓋了。
0: 視頻_10
1: 視頻_11
2: 視頻_2
3: 視頻_3
4: 視頻_4
5: 視頻_5
6: 視頻_6
7: 視頻_7
8: 視頻_8
9: 視頻_9
*/
dr.displayInOrder();
/*按順序從新到舊顯示
視頻_11
視頻_10
視頻_9
視頻_8
視頻_7
視頻_6
視頻_5
視頻_4
視頻_3
視頻_2
*/
}
}
輸出結果:
[0]:視頻_10 [1]:視頻_11 [2]:視頻_2 [3]:視頻_3 [4]:視頻_4 [5]:視頻_5 [6]:視頻_6 [7]:視頻_7 [8]:視頻_8 [9]:視頻_9 視頻_11 視頻_10 視頻_9 視頻_8 視頻_7 視頻_6 視頻_5 視頻_4 視頻_3 視頻_2
我們以視頻_0開始,假設空間已經記錄到視頻_11,一共12條視頻會不會撐爆空間呢?我們來運行以下看會發生什麼。奇蹟出現了,視頻_10和視頻_11分別覆蓋了最早記錄的視頻_0和視頻_1,完美!產品可以量產了!
正當我們要舉杯歡慶的時候客戶開始吐槽了,你只是簡單在屏幕上顯示一下就完事了麼?功能也太差了點!我要的是把原始視頻拿出來給我,我好上報交警作爲證據,總之你甭管我怎麼加工處理,你總得把原始數據拿出來給我。
這可把我們難住了,這些數據都是在記錄儀內部封裝好的私有數據,如果直接改成public暴露出去,試想用戶隨意增加刪除,完全不管遊標位置,這會破壞掉內部邏輯機制,數據封裝的意義何在?我們鬼斧神工設計將瞬間崩塌,用戶數據安全無法保證,bug肆虐。
所以,我們絕不能更改數據的私有化封裝,而之前暴露給用戶的顯示方法顯得非常死板,擴展性極差,我們決定以迭代器取而代之,如此提供給用戶遍歷數據的功能,拿出去的數據用戶便可以隨意使用,這樣就避免了用戶染指內部機件的危險。首先我們需要定義一個迭代器接口標準來規範抽象,看代碼。
package com.liuxd;
/**
* Created by Liuxd on 2018/11/7.
*/
public interface Iterator<E> {
E next();//返回下一個元素
boolean hasNext();//是否還有下一個元素
}
很簡單吧?此接口標準定義了兩個方法,next方法用於返回下一個數據元素,而hasNext用於詢問迭代器是否還有下一個元素,當然我們也可以不定義這個接口,而是直接用JDK中util包自帶的。接下來更改我們的行車記錄儀,加入iterator方法用於獲取迭代器,開始我們的迭代器實現。
package com.liuxd;
/**
* Created by Liuxd on 2018/11/7.
*/
public class DrivingRecorder2 {
private int index = -1;// 當前記錄位置
private String[] records = new String[10];// 假設只能記錄10條視頻
public void append(String record) {
if (index == 9) {// 循環覆蓋
index = 0;
} else {
index++;
}
records[index] = record;
}
public Iterator<String> iterator() {
return new Itr();
}
private class Itr implements Iterator<String> {
int cursor = index;// 迭代器遊標,不染指原始遊標。
int loopCount = 0;
@Override
public boolean hasNext() {
return loopCount < 10;
}
@Override
public String next() {
int i = cursor;// 記錄即將返回的遊標位置
if (cursor == 0) {
cursor = 9;
} else {
cursor--;
}
loopCount++;
return records[i];
}
}
;
}
這裏我們加入內部類(第18行)來定義迭代器實現,爲的是能輕鬆訪問到記錄儀私有數據集。內部類實現了兩個標配方法hasNext與next,內部邏輯看起來簡單多了,大家可以自行理解,這裏就不做講解了。最後重點來了,用戶可以進行如下操作了。
package com.liuxd;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Liuxd on 2018/11/7.
*/
public class Client2 {
public static void main(String[] args) {
DrivingRecorder2 dr = new DrivingRecorder2();
// 假設記錄了12條視頻
for (int i = 0; i < 12; i++) {
dr.append("視頻_" + i);
}
//用戶要獲取交通事故視頻,定義事故列表。
List<String> accidents = new ArrayList<>();
//用戶拿到迭代器
Iterator<String> it = dr.iterator();
while (it.hasNext()) {//如果還有下一條則繼續迭代
String video = it.next();
System.out.println(video);
//用戶翻看視頻發現10和8可作爲證據。
if ("視頻_10".equals(video) || "視頻_8".equals(video)) {
accidents.add(video);
}
}
//拿到兩個視頻集accidents交給交警查看。
System.out.println("事故證據:" + accidents);
/*
視頻_11
視頻_10
視頻_9
視頻_8
視頻_7
視頻_6
視頻_5
視頻_4
視頻_3
視頻_2
事故證據:[視頻_10, 視頻_8]
*/
}
}
輸出結果:
視頻_11
視頻_10
視頻_9
視頻_8
視頻_7
視頻_6
視頻_5
視頻_4
視頻_3
視頻_2
事故證據:[視頻_10, 視頻_8]
用戶拿到迭代器進行遍歷查看,注意第18行,用戶將這12條視頻中的10和8拿出來拷貝U盤並交給交警作爲呈堂證供判對方碰瓷,以證明自己的清白。
當然,我們這裏只是保持極簡說明問題,讀者可以自行重構代碼,尤其是實現迭代器的remove方法非常重要(注意遊標的調整),這樣用戶便可以刪除數據了。
總之,對於任何的集合類,既要保證內部數據表示不暴露給外部以防搞亂內部機制,還要提供給用戶遍歷並訪問到每個數據的權限,迭代器模式則成就了魚與熊掌兼得的可能,它提供了所有集合對外開放的統一標準接口,內政容不得干涉,但是經濟依舊要開放。