程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 關於Java Collections API您不知道的5件事,第2部分

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

編輯:關於JAVA

注意可變對象

java.util 中的 Collections 類旨在通過取代數組提高 Java 性能。如您在 第 1 部分 中了解到的,它們也是多變的,能夠以各種方 式定制和擴展,幫助實現優質、簡潔的代碼。

Collections 非常強大,但是很多變:使用它們要小心,濫用它們會帶來風險。

1. List 不同於數組

Java 開發人員常常錯誤地認為 ArrayList 就是 Java 數組的替代品。Collections 由數組支持,在集合內隨機查找內容時性能較好。 與數組一樣,集合使用整序數獲取特定項。但集合不是數組的簡單替代。

要明白數組與集合的區別需要弄清楚順序 和位置 的不同。例如,List 是一個接口,它保存各個項被放入集合中的順序,如清單 1 所 示:

清單 1. 可變鍵值

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);
   }
}

當第三個元素從上面的 List 中被移除時,其 “後面” 的各項會上升填補空位。很顯然,此集合行為與數組的行為不同(事實上,從 數組中移除項與從 List 中移除它也不完全是一回事兒 — 從數組中 “移除” 項意味著要用新引用或 null 覆蓋其索引槽)。

2. 令人驚訝的 Iterator!

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

但是進行深入研究後,您會發現 Iterator 實際上有兩個十分有用的功能。

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

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

雙向滾動特別有用,尤其是在無處不在的 “滑動結果集” 操作中,因為結果集中只能顯示從數據庫或其他集合中獲取的眾多結果中的 10 個。它還可以用於 “反向遍歷” 集合或列表,而無需每次都從前向後遍歷。插入 ListIterator 比使用向下計數整數參數 List.get () “反向” 遍歷 List 容易得多。

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

Ruby 和 Groovy 開發人員喜歡炫耀他們如何能迭代整個文本文件並通過一行代碼將其內容輸出到控制台。通常,他們會說在 Java 編 程中完成同樣的操作需要很多行代碼:打開 FileReader,然後打開 BufferedReader,接著創建 while() 循環來調用 getLine(),直到它 返回 null。當然,在 try/catch/finally 塊中必須要完成這些操作,它要處理異常並在結束時關閉文件句柄。

這看起來像是一個沒有意義的學術上的爭論,但是它也有其自身的價值。

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

清單 2. 迭代文件

// 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);
   }
}

此方法的優勢是不會在內存中保留整個內容,但是有一個警告就是,它不能 close() 底層文件句柄(每當 readLine() 返回 null 時 就關閉文件句柄,可以修正這一問題,但是在 Iterator 沒有結束時不能解決這個問題)。

4. 注意可變的 hashCode()

Map 是很好的集合,為我們帶來了在其他語言(比如 Perl)中經常可見的好用的鍵/值對集合。JDK 以 HashMap 的形式為 我們提供了方便的 Map 實現,它在內部使用哈希表實現了對鍵的對應值的快速查找。但是這裡也有一個小問題:支持哈希碼的鍵依賴於可 變字段的內容,這樣容易產生 bug,即使最耐心的 Java 開發人員也會被這些 bug 逼瘋。

假設清單 3 中的 Person 對象有一個常見的 hashCode() (它使用 firstName、lastName 和 age 字段 — 所有字段都不是 final 字 段 — 計算 hashCode()),對 Map 的 get() 調用會失敗並返回 null:

清單 3. 可變 hashCode() 容易出現 bug

// 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));
   }
}

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

5. equals() 與 Comparable

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

清單 4. SortedSet,我很高興找到了它!

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);
   }
}

使用上述代碼一段時間後,可能會發現這個 Set 的核心特性之一:它不允許重復。該特性在 Set Javadoc 中進行了介紹。Set 是不包 含重復元素的集合。更准確地說,set 不包含成對的 e1 和 e2 元素,因此如果 e1.equals(e2),那麼最多包含一個 null 元素。

但實際上似乎並非如此 — 盡管 清單 4 中沒有相等的 Person 對象(根據 Person 的 equals() 實現),但在輸出時只有三個對象出 現在 TreeSet 中。

與 set 的有狀態本質相反,TreeSet 要求對象直接實現 Comparable 或者在構造時傳入 Comparator,它不使用 equals() 比較對象; 它使用 Comparator/Comparable 的 compare 或 compareTo 方法。

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

更糟的是,簡單的聲明兩者相等還不夠,因為以排序為目的的比較不同於以相等性為目的的比較:可以想象一下按姓排序時兩個 Person 相等,但是其內容卻並不相同。

一定要明白 equals() 和 Comparable.compareTo() 兩者之間的不同 — 實現 Set 時會返回 0。甚至在文檔中也要明確兩者的區別。

結束語

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

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

原文地址:http://www.ibm.com/developerworks/cn/java/j-5things3.html

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved