<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    John Jiang

    a cup of Java, cheers!
    https://github.com/johnshajiang/blog

       :: 首頁(yè) ::  :: 聯(lián)系 :: 聚合  :: 管理 ::
      131 隨筆 :: 1 文章 :: 530 評(píng)論 :: 0 Trackbacks
    你所不知道的五件事情--Java集合框架API(第二部分)
                                              -- 小心可變性

    這是Ted NewardIBM developerWorks5 things系列文章中的一篇,仍然講述了關(guān)于Java集合框架的一些應(yīng)用竅門(mén),值得大家學(xué)習(xí)。(2010.05.08最后更新)

        概要:你可以在任何地方使用Java集合框架,但不要想當(dāng)然地使用它們。集合框架有神秘之處,如果你不能正確地對(duì)待它,它就會(huì)為你惹麻煩。Ted Neward探索了Java集合框架API中復(fù)雜且可變的部分,還給出了一些幫助你更好地利用Iterable,HashMap和SortedSet的竅門(mén),這些竅門(mén)將會(huì)使你的代碼不會(huì)產(chǎn)生Bug。

        設(shè)計(jì)java.util包中集合框架類(lèi)的目的就是幫助,也即替代數(shù)組,這也就提高了Java的能力。如你在上一篇文章中所學(xué)習(xí)到的,它們?nèi)跃呖伤苄裕鼈兿M圆煌耐緩剑玫姆绞剑麧嵉拇a去進(jìn)行定制和擴(kuò)展。
        集合框架仍然強(qiáng)大,但它是可變的:要小心使用之,若濫用之則會(huì)使你陷入危機(jī)中。

    1. List不同于數(shù)組
        Java開(kāi)發(fā)者經(jīng)常錯(cuò)誤地猜想ArrayList只是Java數(shù)組的替代品。集合框架的背后就是數(shù)組,這就使得在集合對(duì)象中隨機(jī)地查找元素時(shí)能有好的性能。另外,如同數(shù)組那樣,集合對(duì)象使用整數(shù)序數(shù)去獲取特定元素。即便如此,集合對(duì)象仍不是數(shù)組的簡(jiǎn)易替代品。
    將集合對(duì)象與數(shù)組區(qū)分開(kāi)來(lái)的技巧就是要知道順序與位置之間的區(qū)別。例如,List是一個(gè)接口,它為置入集合中的元素維護(hù)了順序,如清單1所示:

    清單1. Mutable keys
    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)刪除上面List中的第三個(gè)元素時(shí),該元素"下面"的其它元素會(huì)向上移動(dòng)以填補(bǔ)空位。很清楚,集合對(duì)象的行為不同于數(shù)組。(事實(shí)上,從數(shù)組中刪除一個(gè)元素與從List中刪除一個(gè)元素大為不同--從數(shù)組中"刪除"一個(gè)元素就是用一個(gè)新的引用變量或null去覆蓋該元素所處的位置。)

    2. 迭代器,令我大為吃驚!
        毫無(wú)疑問(wèn),Java開(kāi)發(fā)者喜歡Java集合框架中的Iterator,但你最后一次看到Iterator接口是在什么時(shí)候呢?可以這么說(shuō),多數(shù)時(shí)候,我們只是將Iterator置入for循環(huán)或改進(jìn)的for循環(huán)中。
        但對(duì)于那些善于挖掘的人,Iterator內(nèi)藏兩大驚人之處:
        第一,通過(guò)調(diào)用Iterator本身的remove()方法,Iterator擁有了從來(lái)源集合對(duì)象中安全地刪除元素的能力。此處的關(guān)鍵點(diǎn)在于避免了 ConcurrentModifiedException,顧名思意:當(dāng)?shù)髡诒闅v集合對(duì)象時(shí),又正在修改該集合。一些集合對(duì)象不會(huì)讓你向正在被遍歷的集合中刪除或添加元素,但調(diào)用Iterator的remove()方法是一個(gè)安全的實(shí)踐方式。
        第二,Iterator支持派生出的(且功能更強(qiáng)大的)兄弟。ListIterator,它只存在于List實(shí)例中,支持在遍歷過(guò)程中向List中添加和刪除元素,并且能雙向滾動(dòng)(bidirectional scrolling)List對(duì)象。
    雙向滾動(dòng)(bidirectional scrolling)在某些場(chǎng)景下有特別強(qiáng)大的功能,例如無(wú)處不在的"結(jié)果集滑動(dòng)",即,從數(shù)據(jù)庫(kù)或其它集合對(duì)象的眾多結(jié)果中展示其中的10個(gè)。它還可以被用于"向后遍歷"一個(gè)集合或列表,而不用試圖從前向后地訪(fǎng)問(wèn)每個(gè)元素。使用ListIterator要比利用向下計(jì)數(shù)的整數(shù)參數(shù)的List.get() 方法去"向后遍歷"一個(gè)List容易得多。

    3. 并不是所有的Iterable實(shí)例都來(lái)自于集合對(duì)象

        Ruby和Groovy開(kāi)發(fā)者喜歡炫耀他們?cè)鯓邮褂靡恍写a就遍歷了整篇文本,并將其中的內(nèi)容打印到控制臺(tái)上。多數(shù)時(shí)候,他們會(huì)說(shuō),使用Java來(lái)做同樣的事情需要編寫(xiě)許多代碼:打開(kāi)一個(gè)FileReader,再創(chuàng)建一個(gè)BufferedReader,然后創(chuàng)建一個(gè)while()循環(huán)去調(diào)用 getLine()方法,直到返回null為止。當(dāng)然,你還必須得在一個(gè)try/catch/finally語(yǔ)句塊中做上述事情,這個(gè)語(yǔ)句塊用于處理異常且在結(jié)束時(shí)關(guān)閉文件句柄。
        看起來(lái)這像是一個(gè)微不足道,學(xué)究式的爭(zhēng)論,但它還是有些意義的。
        他們(包括一些Java開(kāi)發(fā)者)不知道并不是所有Iterable實(shí)例都要來(lái)自于集合對(duì)象。相反地,一個(gè)Iterable實(shí)例可以創(chuàng)建一個(gè) Iterator實(shí)例,這個(gè)Iterator知道如何去憑空地造出下一個(gè)元素,而不是在一個(gè)預(yù)先已存在集合對(duì)象的內(nèi)部默默地進(jìn)行處理。

    清單2 Iterating a file
    // 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)點(diǎn)在于不需要在內(nèi)存中處理整個(gè)文件的內(nèi)容,但有一個(gè)告誡,如上面所編寫(xiě)的代碼,它不能關(guān)閉下層的文件句柄。(當(dāng)readLing()方法返回 null時(shí)就關(guān)閉文件句柄,通過(guò)該方法可以修正這一問(wèn)題,但當(dāng)Iterator未能遍歷完整個(gè)文件時(shí),該方法也解決不了這個(gè)問(wèn)題。)

    4. 意識(shí)到可變的hashCode()方法
        Map是很好的集合對(duì)象,它帶給我們只有在其它編程語(yǔ)言,如Perl,中才能體會(huì)到的鍵-值對(duì)集合的樂(lè)趣。并且JDK為我們提供了一個(gè)很棒的Map實(shí)現(xiàn),HashMap,該實(shí)現(xiàn)在內(nèi)部使用散列表,這使得快速地通過(guò)鍵來(lái)查找對(duì)應(yīng)的值。但在那兒就會(huì)出現(xiàn)一個(gè)細(xì)微的問(wèn)題:支持散列碼的鍵會(huì)依賴(lài)內(nèi)容可變的字段,這很容易就產(chǎn)生Bug。即使對(duì)那些最有耐心的Java開(kāi)發(fā)者,這樣的Bug也會(huì)使他們發(fā)瘋。
        想像清單3中的Person對(duì)象,它有一個(gè)典型的hashCode()方法(該方法使用firstName,lastName和age字段--所有的字段都不是final的--去計(jì)算散列碼),調(diào)用Map的get()方法將可能失敗并返回null。

    清單3 可變的hashCode()使人犯錯(cuò)
    // 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));
        }
    }

    更明確地說(shuō),上述方法令人痛楚,但解決方法卻很簡(jiǎn)單:HashMap的鍵永遠(yuǎn)不要使用可變對(duì)象。

    5. equals() vs Comparable
        瀏覽Javadoc時(shí),Java開(kāi)發(fā)者們常會(huì)遇到SortedSet類(lèi)型(在JDK中,它的唯一實(shí)現(xiàn)是TreeSet)。因?yàn)镾ortedSet是 java.util包中唯一提供了某種指定排序行為的集合類(lèi),所以開(kāi)發(fā)者們?cè)谝婚_(kāi)始使用它時(shí)并沒(méi)有仔細(xì)地考究其中的細(xì)節(jié)。清單4證明了這一點(diǎn):

    清單4 SortedSet,很高興發(fā)現(xiàn)你
    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)Set的核心特性之一:它不允許重復(fù)。這一特性在Set的Javadoc中有明確的描述。Set是"不包含重復(fù)元素的集合"。更準(zhǔn)確地說(shuō),對(duì)于元素e1和e2,如果有e1.eqauls(e2),那么Set就不能同時(shí)包含它們,并且最多只能包含一個(gè)null元素。
        但這似乎不是實(shí)際情況--雖然清單4沒(méi)有Person對(duì)象是相等的(根據(jù)Person所實(shí)現(xiàn)的equals()方法),但當(dāng)打印該TreeSet時(shí),只展示了三個(gè)Person對(duì)象。
        與Set的天然狀態(tài)相反,TreeSet要求對(duì)象要么實(shí)現(xiàn)Comparable接口,要么向構(gòu)造器中直接傳入一個(gè)Comparator實(shí)現(xiàn),不用 equals()方法相比較對(duì)象;而是使用Comparator/Comparable中的compare/comparaTo方法。
        存儲(chǔ)在Set中的對(duì)象有兩種潛在的方法來(lái)判定相等性:期望中的equals()方法;Comparable/Comparator方法,這依賴(lài)于調(diào)用這些方法的上下文。
    更糟的是,如此簡(jiǎn)單的描述還不足以表明這二者是不同的,因?yàn)橐耘判驗(yàn)槟康牡谋容^不同于以等價(jià)性為目的的比較:當(dāng)按姓氏進(jìn)行排序時(shí),某兩個(gè)Person對(duì)象是相等的,但它們的內(nèi)容卻是不等的。
        總是要明確equals()與Comparable.compareTo()方法的區(qū)別--當(dāng)實(shí)現(xiàn)Set時(shí),返回零必須是清晰的。甚至于,應(yīng)該在你的文檔中清晰地描述這一區(qū)別。

    結(jié)論
        Java集合框架遍布有用之物,只要知道它們,就能使你的生活更簡(jiǎn)單也更富有成效。然而,挖掘出的這些有用之物經(jīng)常伴隨著一定的復(fù)雜度,例如,你會(huì)發(fā)現(xiàn)只要不在鍵中使用可變對(duì)象,就可以按你自己的方式去使用HashMap。
        到目前為止,我們已經(jīng)對(duì)集合框架進(jìn)行了深入挖掘,但我們還未觸及這其中的"金礦":由Java 5引入的并發(fā)集合。本系列的后5個(gè)竅門(mén)將關(guān)注包java.util.concurrent。

    請(qǐng)關(guān)注你所不知道的五件事情--Java集合框架API(第一部分)


    posted on 2010-05-08 09:36 John Jiang 閱讀(3092) 評(píng)論(1)  編輯  收藏 所屬分類(lèi): Java翻譯

    評(píng)論

    # re: 你所不知道的五件事情--Java集合框架API(第二部分)(譯) 2010-05-10 12:32 萬(wàn)其
    好  回復(fù)  更多評(píng)論
      

    主站蜘蛛池模板: 免费国产在线视频| 114一级毛片免费| 久久精品国产精品亚洲艾| 亚洲精品中文字幕麻豆| 亚洲精品乱码久久久久久V| 精品一区二区三区高清免费观看| 久久99热精品免费观看牛牛| 精品少妇人妻AV免费久久洗澡| 亚洲裸男gv网站| 亚洲欧洲日本国产| 香蕉免费看一区二区三区| 亚洲中文字幕不卡无码| 亚洲AV日韩AV一区二区三曲| 91大神在线免费观看| 久久久久无码专区亚洲av| 亚洲字幕AV一区二区三区四区| 97在线免费视频| 国产免费av片在线播放| 亚洲第一成年人网站| 久久久久久国产精品免费免费| 国产亚洲精品成人AA片新蒲金| 99热在线日韩精品免费| 亚洲无删减国产精品一区| a免费毛片在线播放| 国产精品免费看香蕉| 亚洲日本VA午夜在线电影| 蜜桃AV无码免费看永久| 亚洲A∨无码一区二区三区| 国产无遮挡色视频免费观看性色| 亚洲色欲久久久综合网东京热| 一级毛片a免费播放王色电影| 成人免费看黄20分钟| 亚洲一区二区三区无码国产| 久久久久成人片免费观看蜜芽| 亚洲人成电影青青在线播放| 91精品成人免费国产片| 亚洲av片劲爆在线观看| 啦啦啦中文在线观看电视剧免费版 | 国产亚洲精aa成人网站| 免费国产污网站在线观看15| 亚洲av中文无码乱人伦在线播放|