日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

為什么阿里巴巴要求謹(jǐn)慎使用ArrayList中的subList方法

 印度阿三17 2019-06-25

GitHub 3.7k Star 的Java工程師成神之路 ,不來(lái)了解一下嗎?

GitHub 3.7k Star 的Java工程師成神之路 ,真的不來(lái)了解一下嗎?

GitHub 3.7k Star 的Java工程師成神之路 ,真的確定不來(lái)了解一下嗎?

集合是Java開(kāi)發(fā)日常開(kāi)發(fā)中經(jīng)常會(huì)使用到的。在之前的一些文章中,我們介紹過(guò)一些關(guān)于使用集合類(lèi)應(yīng)該注意的事項(xiàng),如《為什么阿里巴巴禁止在 foreach 循環(huán)里進(jìn)行元素的 remove/add 操作》、《為什么阿里巴巴建議集合初始化時(shí),指定集合容量大小》等。

關(guān)于集合類(lèi),《阿里巴巴Java開(kāi)發(fā)手冊(cè)》中其實(shí)還有另外一個(gè)規(guī)定:

-w1379?

本文就來(lái)分析一下為什么會(huì)有如此建議?其背后的原理是什么?

subList

subList是List接口中定義的一個(gè)方法,該方法主要用于返回一個(gè)集合中的一段、可以理解為截取一個(gè)集合中的部分元素,他的返回值也是一個(gè)List。

如以下代碼:

public static void main(String[] args) {
    List<String> names = new ArrayList<String>() {{
        add("Hollis");
        add("hollischuang");
        add("H");
    }};

    List subList = names.subList(0, 1);
    System.out.println(subList);
}

以上代碼輸出結(jié)果為:

[Hollis]

如果我們改動(dòng)下代碼,將subList的返回值強(qiáng)轉(zhuǎn)成ArrayList試一下:

public static void main(String[] args) {
    List<String> names = new ArrayList<String>() {{
        add("Hollis");
        add("hollischuang");
        add("H");
    }};

    ArrayList subList = names.subList(0, 1);
    System.out.println(subList);
}

以上代碼將拋出異常:

java.lang.ClassCastException: java.util.ArrayList$SubList cannot be cast to java.util.ArrayList

不只是強(qiáng)轉(zhuǎn)成ArrayList會(huì)報(bào)錯(cuò),強(qiáng)轉(zhuǎn)成LinkedList、Vector等List的實(shí)現(xiàn)類(lèi)同樣也都會(huì)報(bào)錯(cuò)。

那么,為什么會(huì)發(fā)生這樣的報(bào)錯(cuò)呢?我們接下來(lái)深入分析一下。

底層原理

首先,我們看下subList方法給我們返回的List到底是個(gè)什么東西,這一點(diǎn)在JDK源碼中注釋是這樣說(shuō)的:

Returns a view of the portion of this list between the specifiedfromIndex, inclusive, and toIndex, exclusive.

也就是說(shuō)subList 返回是一個(gè)視圖,那么什么叫做視圖呢?

我們看下subList的源碼:

public List<E> subList(int fromIndex, int toIndex) {
    subListRangeCheck(fromIndex, toIndex, size);
    return new SubList(this, 0, fromIndex, toIndex);
}

這個(gè)方法返回了一個(gè)SubList,這個(gè)類(lèi)是ArrayList中的一個(gè)內(nèi)部類(lèi)。

SubList這個(gè)類(lèi)中單獨(dú)定義了set、get、size、add、remove等方法。

當(dāng)我們調(diào)用subList方法的時(shí)候,會(huì)通過(guò)調(diào)用SubList的構(gòu)造函數(shù)創(chuàng)建一個(gè)SubList,那么看下這個(gè)構(gòu)造函數(shù)做了哪些事情:

SubList(AbstractList<E> parent,
            int offset, int fromIndex, int toIndex) {
    this.parent = parent;
    this.parentOffset = fromIndex;
    this.offset = offset   fromIndex;
    this.size = toIndex - fromIndex;
    this.modCount = ArrayList.this.modCount;
}

可以看到,這個(gè)構(gòu)造函數(shù)中把原來(lái)的List以及該List中的部分屬性直接賦值給自己的一些屬性了。

也就是說(shuō),SubList并沒(méi)有重新創(chuàng)建一個(gè)List,而是直接引用了原有的List(返回了父類(lèi)的視圖),只是指定了一下他要使用的元素的范圍而已(從fromIndex(包含),到toIndex(不包含))。

所以,為什么不能講subList方法得到的集合直接轉(zhuǎn)換成ArrayList呢?因?yàn)镾ubList只是ArrayList的內(nèi)部類(lèi),他們之間并沒(méi)有集成關(guān)系,故無(wú)法直接進(jìn)行強(qiáng)制類(lèi)型轉(zhuǎn)換。

視圖有什么問(wèn)題

前面通過(guò)查看源碼,我們知道,subList()方法并沒(méi)有重新創(chuàng)建一個(gè)ArrayList,而是返回了一個(gè)ArrayList的內(nèi)部類(lèi)——SubList。

這個(gè)SubList是ArrayList的一個(gè)視圖。

那么,這個(gè)視圖又會(huì)帶來(lái)什么問(wèn)題呢?我們需要簡(jiǎn)單寫(xiě)幾段代碼看一下。

1、非結(jié)構(gòu)性改變SubList

public static void main(String[] args) {
    List<String> sourceList = new ArrayList<String>() {{
        add("H");
        add("O");
        add("L");
        add("L");
        add("I");
        add("S");
    }};

    List subList = sourceList.subList(2, 5);

    System.out.println("sourceList : "   sourceList);
    System.out.println("sourceList.subList(2, 5) 得到List :");
    System.out.println("subList : "   subList);

    subList.set(1, "666");

    System.out.println("subList.set(3,666) 得到List :");
    System.out.println("subList : "   subList);
    System.out.println("sourceList : "   sourceList);

}

得到結(jié)果:

sourceList : [H, O, L, L, I, S]
sourceList.subList(2, 5) 得到List :
subList : [L, L, I]
subList.set(3,666) 得到List :
subList : [L, 666, I]
sourceList : [H, O, L, 666, I, S]

當(dāng)我們嘗試通過(guò)set方法,改變subList中某個(gè)元素的值得時(shí)候,我們發(fā)現(xiàn),原來(lái)的那個(gè)List中對(duì)應(yīng)元素的值也發(fā)生了改變。

同理,如果我們使用同樣的方法,對(duì)sourceList中的某個(gè)元素進(jìn)行修改,那么subList中對(duì)應(yīng)的值也會(huì)發(fā)生改變。讀者可以自行嘗試一下。

1、結(jié)構(gòu)性改變SubList

public static void main(String[] args) {
    List<String> sourceList = new ArrayList<String>() {{
        add("H");
        add("O");
        add("L");
        add("L");
        add("I");
        add("S");
    }};

    List subList = sourceList.subList(2, 5);

    System.out.println("sourceList : "   sourceList);
    System.out.println("sourceList.subList(2, 5) 得到List :");
    System.out.println("subList : "   subList);

    subList.add("666");

    System.out.println("subList.add(666) 得到List :");
    System.out.println("subList : "   subList);
    System.out.println("sourceList : "   sourceList);

}

得到結(jié)果:

sourceList : [H, O, L, L, I, S]
sourceList.subList(2, 5) 得到List :
subList : [L, L, I]
subList.add(666) 得到List :
subList : [L, L, I, 666]
sourceList : [H, O, L, L, I, 666, S]

我們嘗試對(duì)subList的結(jié)構(gòu)進(jìn)行改變,即向其追加元素,那么得到的結(jié)果是sourceList的結(jié)構(gòu)也同樣發(fā)生了改變。

1、結(jié)構(gòu)性改變?cè)璍ist

public static void main(String[] args) {
    List<String> sourceList = new ArrayList<String>() {{
        add("H");
        add("O");
        add("L");
        add("L");
        add("I");
        add("S");
    }};

    List subList = sourceList.subList(2, 5);

    System.out.println("sourceList : "   sourceList);
    System.out.println("sourceList.subList(2, 5) 得到List :");
    System.out.println("subList : "   subList);

    sourceList.add("666");

    System.out.println("sourceList.add(666) 得到List :");
    System.out.println("sourceList : "   sourceList);
    System.out.println("subList : "   subList);

}

得到結(jié)果:

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1239)
    at java.util.ArrayList$SubList.listIterator(ArrayList.java:1099)
    at java.util.AbstractList.listIterator(AbstractList.java:299)
    at java.util.ArrayList$SubList.iterator(ArrayList.java:1095)
    at java.util.AbstractCollection.toString(AbstractCollection.java:454)
    at java.lang.String.valueOf(String.java:2994)
    at java.lang.StringBuilder.append(StringBuilder.java:131)
    at com.hollis.SubListTest.main(SubListTest.java:28)

我們嘗試對(duì)sourceList的結(jié)構(gòu)進(jìn)行改變,即向其追加元素,結(jié)果發(fā)現(xiàn)拋出了ConcurrentModificationException。關(guān)于這個(gè)異常,我們?cè)凇?a href="https://www./archives/3542">一不小心就踩坑的fail-fast是個(gè)什么鬼?》中分析過(guò),這里原理相同,就不再贅述了。

小結(jié)

我們簡(jiǎn)單總結(jié)一下,List的subList方法并沒(méi)有創(chuàng)建一個(gè)新的List,而是使用了原List的視圖,這個(gè)視圖使用內(nèi)部類(lèi)SubList表示。

所以,我們不能把subList方法返回的List強(qiáng)制轉(zhuǎn)換成ArrayList等類(lèi),因?yàn)樗麄冎g沒(méi)有繼承關(guān)系。

另外,視圖和原List的修改還需要注意幾點(diǎn),尤其是他們之間的相互影響:

1、對(duì)父(sourceList)子(subList)List做的非結(jié)構(gòu)性修改(non-structural changes),都會(huì)影響到彼此。

2、對(duì)子List做結(jié)構(gòu)性修改,操作同樣會(huì)反映到父List上。

3、對(duì)父List做結(jié)構(gòu)性修改,會(huì)拋出異常ConcurrentModificationException。

所以,阿里巴巴Java開(kāi)發(fā)手冊(cè)中有另外一條規(guī)定:

?

如何創(chuàng)建新的List

如果需要對(duì)subList作出修改,又不想動(dòng)原list。那么可以創(chuàng)建subList的一個(gè)拷貝:

subList = Lists.newArrayList(subList);
list.stream().skip(strart).limit(end).collect(Collectors.toList());

PS:最近,《阿里巴巴Java開(kāi)發(fā)手冊(cè)》已經(jīng)正式更名為《Java開(kāi)發(fā)手冊(cè)》,并發(fā)布了新版本,增加了21條新規(guī)約,修改描述112處。

關(guān)注公眾號(hào)后臺(tái)回復(fù):手冊(cè),即可獲取最新版Java開(kāi)發(fā)手冊(cè)。

參考資料: https://www.jianshu.com/p/5854851240df https://www.cnblogs.com/ljdblog/p/6251387.html

來(lái)源:https://www./content-4-266401.html

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶(hù)發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶(hù) 評(píng)論公約

    類(lèi)似文章 更多