久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

您不知道的 5 件事……: Java Collections API,,第 2 部分

 阿青哥Joe 2018-06-30


您覺得自己懂 Java 編程,?事實(shí)上,,大多數(shù)程序員對于 Java 平臺(tái)都是淺嘗輒止,只學(xué)習(xí)了足以完成手頭上任務(wù)的知識而已,。在本 系列 中,Ted Neward 深入挖掘 Java 平臺(tái)的核心功能,,揭示一些鮮為人知的事實(shí),幫助您解決最棘手的編程困難,。

java.util 中的 Collections 類旨在通過取代數(shù)組提高 Java 性能,。如您在 第 1 部分 中了解到的,它們也是多變的,,能夠以各種方式定制和擴(kuò)展,,幫助實(shí)現(xiàn)優(yōu)質(zhì)、簡潔的代碼,。

Collections 非常強(qiáng)大,,但是很多變:使用它們要小心,濫用它們會(huì)帶來風(fēng)險(xiǎn),。

1. List 不同于數(shù)組

Java 開發(fā)人員常常錯(cuò)誤地認(rèn)為 ArrayList 就是 Java 數(shù)組的替代品,。Collections 由數(shù)組支持,在集合內(nèi)隨機(jī)查找內(nèi)容時(shí)性能較好,。與數(shù)組一樣,,集合使用整序數(shù)獲取特定項(xiàng)。但集合不是數(shù)組的簡單替代,。

要明白數(shù)組與集合的區(qū)別需要弄清楚順序位置 的不同,。例如,List 是一個(gè)接口,,它保存各個(gè)項(xiàng)被放入集合中的順序,,如清單 1 所示:

清單 1. 可變鍵值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.util.*;
public class OrderAndPosition
{
    public static <T> void dumpArray(T[] array)
    {
        System.out.println("=============");
        for (int i=0; i<array.length; i++)
            System.out.println("Position " + i + ": " + array[i]);
    }
    public static <T> void dumpList(List<T> list)
    {
        System.out.println("=============");
        for (int i=0; i<list.size(); i++)
            System.out.println("Ordinal " + i + ": " + list.get(i));
    }
     
    public static void main(String[] args)
    {
        List<String> argList = new ArrayList<String>(Arrays.asList(args));
        dumpArray(args);
        args[1] = null;
        dumpArray(args);
         
        dumpList(argList);
        argList.remove(1);
        dumpList(argList);
    }
}

當(dāng)?shù)谌齻€(gè)元素從上面的 List 中被移除時(shí),其 “后面” 的各項(xiàng)會(huì)上升填補(bǔ)空位,。很顯然,,此集合行為與數(shù)組的行為不同(事實(shí)上,從數(shù)組中移除項(xiàng)與從 List 中移除它也不完全是一回事兒 — 從數(shù)組中 “移除” 項(xiàng)意味著要用新引用或 null 覆蓋其索引槽),。

2. 令人驚訝的 Iterator,!

無疑 Java 開發(fā)人員很喜愛 Java 集合 Iterator,但是您最后一次使用 Iterator 接口是什么時(shí)候的事情了,?可以這么說,,大部分時(shí)間我們只是將 Iterator 隨意放到 for() 循環(huán)或加強(qiáng) for() 循環(huán)中,然后就繼續(xù)其他操作了,。

但是進(jìn)行深入研究后,,您會(huì)發(fā)現(xiàn) Iterator 實(shí)際上有兩個(gè)十分有用的功能。

第一,,Iterator 支持從源集合中安全地刪除對象,,只需在 Iterator 上調(diào)用 remove() 即可。這樣做的好處是可以避免 ConcurrentModifiedException,,這個(gè)異常顧名思意:當(dāng)打開 Iterator 迭代集合時(shí),,同時(shí)又在對集合進(jìn)行修改。有些集合不允許在迭代時(shí)刪除或添加元素,,但是調(diào)用 Iteratorremove() 方法是個(gè)安全的做法,。

第二,Iterator 支持派生的(并且可能是更強(qiáng)大的)兄弟成員,。ListIterator,,只存在于 List 中,支持在迭代期間向 List 中添加或刪除元素,,并且可以在 List 中雙向滾動(dòng),。

雙向滾動(dòng)特別有用,尤其是在無處不在的 “滑動(dòng)結(jié)果集” 操作中,,因?yàn)榻Y(jié)果集中只能顯示從數(shù)據(jù)庫或其他集合中獲取的眾多結(jié)果中的 10 個(gè),。它還可以用于 “反向遍歷” 集合或列表,而無需每次都從前向后遍歷,。插入 ListIterator 比使用向下計(jì)數(shù)整數(shù)參數(shù) List.get() “反向” 遍歷 List 容易得多,。

3. 并非所有 Iterable 都來自集合

Ruby 和 Groovy 開發(fā)人員喜歡炫耀他們?nèi)绾文艿麄€(gè)文本文件并通過一行代碼將其內(nèi)容輸出到控制臺(tái)。通常,,他們會(huì)說在 Java 編程中完成同樣的操作需要很多行代碼:打開 FileReader,,然后打開 BufferedReader,接著創(chuàng)建 while() 循環(huán)來調(diào)用 getLine(),,直到它返回 null,。當(dāng)然,在 try/catch/finally 塊中必須要完成這些操作,,它要處理異常并在結(jié)束時(shí)關(guān)閉文件句柄,。

這看起來像是一個(gè)沒有意義的學(xué)術(shù)上的爭論,但是它也有其自身的價(jià)值,。

他們(包括相當(dāng)一部分 Java 開發(fā)人員)不知道并不是所有 Iterable 都來自集合,。Iterable 可以創(chuàng)建 Iterator,該迭代器知道如何憑空制造下一個(gè)元素,,而不是從預(yù)先存在的 Collection 中盲目地處理:

清單 2. 迭代文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// FileUtils.java
import java.io.*;
import java.util.*;
public class FileUtils
{
    public static Iterable<String> readlines(String filename)
        throws IOException
    {
        final FileReader fr = new FileReader(filename);
        final BufferedReader br = new BufferedReader(fr);
         
        return new Iterable<String>() {
            public <code>Iterator</code><String> iterator() {
                return new <code>Iterator</code><String>() {
                    public boolean hasNext() {
                        return line != null;
                    }
                    public String next() {
                        String retval = line;
                        line = getLine();
                        return retval;
                    }
                    public void remove() {
                        throw new UnsupportedOperationException();
                    }
                    String getLine() {
                        String line = null;
                        try {
                            line = br.readLine();
                        }
                        catch (IOException ioEx) {
                            line = null;
                        }
                        return line;
                    }
                    String line = getLine();
                };
            }  
        };
    }
}
//DumpApp.java
import java.util.*;
public class DumpApp
{
    public static void main(String[] args)
        throws Exception
    {
        for (String line : FileUtils.readlines(args[0]))
            System.out.println(line);
    }
}

此方法的優(yōu)勢是不會(huì)在內(nèi)存中保留整個(gè)內(nèi)容,,但是有一個(gè)警告就是,它不能 close() 底層文件句柄(每當(dāng) readLine() 返回 null 時(shí)就關(guān)閉文件句柄,,可以修正這一問題,,但是在 Iterator 沒有結(jié)束時(shí)不能解決這個(gè)問題)。

4. 注意可變的 hashCode()

Map 是很好的集合,,為我們帶來了在其他語言(比如 Perl)中經(jīng)??梢姷暮糜玫逆I/值對集合。JDK 以 HashMap 的形式為我們提供了方便的 Map 實(shí)現(xiàn),,它在內(nèi)部使用哈希表實(shí)現(xiàn)了對鍵的對應(yīng)值的快速查找,。但是這里也有一個(gè)小問題:支持哈希碼的鍵依賴于可變字段的內(nèi)容,,這樣容易產(chǎn)生 bug,即使最耐心的 Java 開發(fā)人員也會(huì)被這些 bug 逼瘋,。

假設(shè)清單 3 中的 Person 對象有一個(gè)常見的 hashCode() (它使用 firstName,、lastNameage 字段 — 所有字段都不是 final 字段 — 計(jì)算 hashCode()),對 Mapget() 調(diào)用會(huì)失敗并返回 null

清單 3. 可變 hashCode() 容易出現(xiàn) bug
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// Person.java
import java.util.*;
public class Person
    implements Iterable<Person>
{
    public Person(String fn, String ln, int a, Person... kids)
    {
        this.firstName = fn; this.lastName = ln; this.age = a;
        for (Person kid : kids)
            children.add(kid);
    }
     
    // ...
     
    public void setFirstName(String value) { this.firstName = value; }
    public void setLastName(String value) { this.lastName = value; }
    public void setAge(int value) { this.age = value; }
     
    public int hashCode() {
        return firstName.hashCode() & lastName.hashCode() & age;
    }
    // ...
    private String firstName;
    private String lastName;
    private int age;
    private List<Person> children = new ArrayList<Person>();
}
// MissingHash.java
import java.util.*;
public class MissingHash
{
    public static void main(String[] args)
    {
        Person p1 = new Person("Ted", "Neward", 39);
        Person p2 = new Person("Charlotte", "Neward", 38);
        System.out.println(p1.hashCode());
         
        Map<Person, Person> map = new HashMap<Person, Person>();
        map.put(p1, p2);
         
        p1.setLastName("Finkelstein");
        System.out.println(p1.hashCode());
         
        System.out.println(map.get(p1));
    }
}

很顯然,,這種方法很糟糕,,但是解決方法也很簡單:永遠(yuǎn)不要將可變對象類型用作 HashMap 中的鍵。

5. equals()Comparable

在瀏覽 Javadoc 時(shí),,Java 開發(fā)人員常常會(huì)遇到 SortedSet 類型(它在 JDK 中唯一的實(shí)現(xiàn)是 TreeSet),。因?yàn)?SortedSetjava.util 包中唯一提供某種排序行為的 Collection,所以開發(fā)人員通常直接使用它而不會(huì)仔細(xì)地研究它,。清單 4 展示了:

清單 4. SortedSet,,我很高興找到了它!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.util.*;
public class UsingSortedSet
{
    public static void main(String[] args)
    {
        List<Person> persons = Arrays.asList(
            new Person("Ted", "Neward", 39),
            new Person("Ron", "Reynolds", 39),
            new Person("Charlotte", "Neward", 38),
            new Person("Matthew", "McCullough", 18)
        );
        SortedSet ss = new TreeSet(new Comparator<Person>() {
            public int compare(Person lhs, Person rhs) {
                return lhs.getLastName().compareTo(rhs.getLastName());
            }
        });
        ss.addAll(perons);
        System.out.println(ss);
    }
}

使用上述代碼一段時(shí)間后,,可能會(huì)發(fā)現(xiàn)這個(gè) Set 的核心特性之一:它不允許重復(fù),。該特性在 Set Javadoc 中進(jìn)行了介紹。Set 是不包含重復(fù)元素的集合,。更準(zhǔn)確地說,,set 不包含成對的 e1 和 e2 元素,因此如果 e1.equals(e2),,那么最多包含一個(gè) null 元素,。

但實(shí)際上似乎并非如此 — 盡管 清單 4 中沒有相等的 Person 對象(根據(jù) Personequals() 實(shí)現(xiàn)),但在輸出時(shí)只有三個(gè)對象出現(xiàn)在 TreeSet 中,。

與 set 的有狀態(tài)本質(zhì)相反,,TreeSet 要求對象直接實(shí)現(xiàn) Comparable 或者在構(gòu)造時(shí)傳入 Comparator,它不使用 equals() 比較對象,;它使用 Comparator/ComparablecomparecompareTo 方法,。

因此存儲(chǔ)在 Set 中的對象有兩種方式確定相等性:大家常用的 equals() 方法和 Comparable/Comparator 方法,采用哪種方法取決于上下文,。

更糟的是,,簡單的聲明兩者相等還不夠,因?yàn)橐耘判驗(yàn)槟康牡谋容^不同于以相等性為目的的比較:可以想象一下按姓排序時(shí)兩個(gè) Person 相等,,但是其內(nèi)容卻并不相同,。

一定要明白 equals()Comparable.compareTo() 兩者之間的不同 — 實(shí)現(xiàn) Set 時(shí)會(huì)返回 0。甚至在文檔中也要明確兩者的區(qū)別,。

結(jié)束語

Java Collections 庫中有很多有用之物,,如果您能加以利用,它們可以讓您的工作更輕松、更高效,。但是發(fā)掘這些有用之物可能有點(diǎn)復(fù)雜,,比如只要您不將可變對象類型作為鍵,您就可以用自己的方式使用 HashMap,。

至此我們挖掘了 Collections 的一些有用特性,,但我們還沒有挖到金礦:Concurrent Collections,它在 Java 5 中引入,。本 系列 的后 5 個(gè)竅門將關(guān)注 java.util.concurrent

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

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多