|
Java 5.0發(fā)布了,許多人都將開始使用這個JDK版本的一些新增特性。從增強的for循環(huán)到諸如泛型(generic)之類更復(fù)雜的特性,都將很快出現(xiàn)在您所編寫的代碼中。我們剛剛完成了一個基于Java 5.0的大型任務(wù),而本文就是要介紹我們使用這些新特性的體驗。本文不是一篇入門性的文章,而是對這些特性以及它們所產(chǎn)生的影響的深入介紹,同時還給出了一些在項目中更有效地使用這些特性的技巧。 簡介 增強的for循環(huán) 在循環(huán)中,初始化表達式只計算一次。這意味著您通??梢砸瞥粋€變量聲明。在這個例子中,我們必須創(chuàng)建一個整型數(shù)組來保存computeNumbers()的結(jié)果,以防止每一次循環(huán)都重新計算該方法。您可以看到,下面的代碼要比上面的代碼整潔一些,并且沒有泄露變量numbers: 未增強的For: int sum = 0; Integer[] numbers = computeNumbers(); for (int i=0; i < numbers.length ; i++) sum += numbers[i]; 增強后的For: int sum = 0; for ( int number: computeNumbers() ) sum += number;局限性 有時需要在迭代期間訪問迭代器或下標(biāo),看起來增強的for循環(huán)應(yīng)該允許該操作,但事實上不是這樣,請看下面的例子: for (int i=0; i < numbers.length ; i++) {
if (i != 0) System.out.print(",");
System.out.print(numbers[i]);
}
我們希望將數(shù)組中的值打印為一個用逗號分隔的清單。我們需要知道目前是否是第一項,以便確定是否應(yīng)該打印逗號。使用增強的for循環(huán)是無法獲知這種信息的。我們需要自己保留一個下標(biāo)或一個布爾值來指示是否經(jīng)過了第一項。 這是另一個例子: for (Iterator<integer> it = n.iterator() ; it.hasNext() ; ) if (it.next() < 0) it.remove(); 在此例中,我們想從整數(shù)集合中刪除負(fù)數(shù)項。為此,需要對迭代器調(diào)用一個方法,但是當(dāng)使用增強的for 循環(huán)時,迭代器對我們來說是看不到的。因此,我們只能使用Java 5之前版本的迭代方法。 順便說一下,這里需要注意的是,由于Iterator是泛型,所以其聲明是Iterator<Integer>。許多人都忘記了這一點而使用了Iterator的原始格式。 注釋注釋處理是一個很大的話題。因為本文只關(guān)注核心的語言特性,所以我們不打算涵蓋它所有的可能形式和陷阱?! ∥覀儗⒂懻搩?nèi)置的注釋(SuppressWarnings,Deprecated和Override)以及一般注釋處理的局限性。 Suppress Warnings @SuppressWarnings("deprecation")
public static void selfDestruct() {
Thread.currentThread().stop();
}
這可能是內(nèi)置注釋最有用的地方。遺憾的是,1.5.0_04的javac不支持它。但是1.6支持它,并且Sun正在努力將其向后移植到1.5中。 Deprecated Override @Override
public int hashCode() {
...
}
看上面的例子,如果沒有在hashCode中將“C”大寫,在編譯時不會出現(xiàn)錯誤,但是在運行時將無法像期望的那樣調(diào)用該方法。通過添加Override標(biāo)簽,編譯器會提示它是否真正地執(zhí)行了重寫。 其它注釋 public class Foo {
@Property
private int bar;
}
其思想是為私有字段bar自動創(chuàng)建getter和setter方法。遺憾的是,這個想法有兩個失敗之處:1)它不能運行,2)它使代碼難以閱讀和處理。 它是無法實現(xiàn)的,因為前面已經(jīng)提到了,Sun特別阻止了對出現(xiàn)注釋的類進行修改。 枚舉 public enum DatabaseType {
ORACLE {
public String getJdbcUrl() {...}
},
MYSQL {
public String getJdbcUrl() {...}
};
public abstract String getJdbcUrl();
}
現(xiàn)在枚舉類型可以直接提供它的實用方法。例如:DatabaseType dbType = ...; String jdbcURL = dbType.getJdbcUrl(); 要獲取URL,必須預(yù)先知道該實用方法在哪里。
Log.log(String code) Log.log(String code, String arg) Log.log(String code, String arg1, String arg2) Log.log(String code, String[] args)當(dāng)討論可變參數(shù)時,比較有趣的是,如果用新的可變參數(shù)替換前四個例子,將是兼容的: Log.log(String code, String... args) 所有的可變參數(shù)都是源兼容的——那就是說,如果重新編譯log()方法的所有調(diào)用程序,可以直接替換全部的四個方法。然而,如果需要向后的二進制兼容性,那么就需要舍去前三個方法。只有最后那個帶一個字符串?dāng)?shù)組參數(shù)的方法等效于可變參數(shù)版本,因此可以被可變參數(shù)版本替換。 類型強制轉(zhuǎn)換 如果希望調(diào)用程序了解應(yīng)該使用哪種類型的參數(shù),那么應(yīng)該避免用可變參數(shù)進行類型強制轉(zhuǎn)換??聪旅孢@個例子,第一項希望是String,第二項希望是Exception: Log.log(Object... objects) {
String message = (String)objects[0];
if (objects.length > 1) {
Exception e = (Exception)objects[1];
// Do something with the exception
}
}
方法簽名應(yīng)該如下所示,相應(yīng)的可變參數(shù)分別使用String和Exception聲明: Log.log(String message, Exception e, Object... objects) {...} 不要使用可變參數(shù)破壞類型系統(tǒng)。需要強類型化時才可以使用它。對于這個規(guī)則,PrintStream.printf()是一個有趣的例外:它提供類型信息作為自己的第一個參數(shù),以便稍后可以接受那些類型。 協(xié)變返回 協(xié)變返回的基本用法是用于在已知一個實現(xiàn)的返回類型比API更具體的時候避免進行類型強制轉(zhuǎn)換。在下面這個例子中,有一個返回Animal對象的Zoo接口。我們的實現(xiàn)返回一個AnimalImpl對象,但是在JDK 1.5之前,要返回一個Animal對象就必須聲明。: public interface Zoo {
public Animal getAnimal();
}
public class ZooImpl implements Zoo {
public Animal getAnimal(){
return new AnimalImpl();
}
}
協(xié)變返回的使用替換了三個反模式:
ZooImpl._animal
((AnimalImpl)ZooImpl.getAnimal()).implMethod();
ZooImpl._getAnimal(); 除了泛型類型,Java 5還引入了泛型方法。在這個來自java.util.Collections的例子中,構(gòu)造了一個單元素列表。新的List的元素類型是根據(jù)傳入方法的對象的類型來推斷的: static <T> List<T> Collections.singletonList(T o)
示例用法:
public List<Integer> getListOfOne() {
return Collections.singletonList(1);
}
在示例用法中,我們傳入了一個int。所以方法的返回類型就是List<Integer>。編譯器把T推斷為Integer。這和泛型類型是不同的,因為您通常不需要顯式地指定類型參數(shù)。 emptyList()方法與泛型一起引入,作為java.util.Collections中EMPTY_LIST字段的類型安全置換: static <T> List<T> Collections.emptyList()
示例用法:
public List<Integer> getNoIntegers() {
return Collections.emptyList();
}
與先前的例子不同,這個方法沒有參數(shù),那么編譯器如何推斷T的類型呢?基本上,它將嘗試使用一次參數(shù)。如果沒有起作用,它再次嘗試使用返回或賦值類型。在本例中,返回的是List<Integer>,所以T被推斷為Integer。 public List<Integer> getNoIntegers() {
return x ? Collections.emptyList() : null;
}
因為編譯器看不到返回上下文,也不能推斷T,所以它放棄并采用Object。您將看到一個錯誤消息,比如:“無法將List<Object>轉(zhuǎn)換為List<Integer>?!? return x ? Collections.<Integer>emptyList() : null; 這種情況經(jīng)常發(fā)生的另一個地方是在方法調(diào)用中。如果一個方法帶一個List<String>參數(shù),并且需要為那個參數(shù)調(diào)用這個傳遞的emptyList(),那么也需要使用這個語法。 集合之外這里有三個泛型類型的例子,它們不是集合,而是以一種新穎的方式使用泛型。這三個例子都來自標(biāo)準(zhǔn)的Java庫:
泛型最復(fù)雜的部分是對通配符的理解。我們將討論三種類型的通配符以及它們的用途。 首先讓我們了解一下數(shù)組是如何工作的。可以從一個Integer[]為一個Number[]賦值。如果嘗試把一個Float寫到Number[]中,那么可以編譯,但在運行時會失敗,出現(xiàn)一個ArrayStoreException: Integer[] ia = new Integer[5]; Number[] na = ia; na[0] = 0.5; // compiles, but fails at runtime 如果試圖把該例直接轉(zhuǎn)換成泛型,那么會在編譯時失敗,因為賦值是不被允許的: List<Integer> iList = new ArrayList<Integer>(); List<Number> nList = iList; // not allowed nList.add(0.5); 如果使用泛型,只要代碼在編譯時沒有出現(xiàn)警告,就不會遇到運行時ClassCastException。 上限通配符 上限 List<Integer> iList = new ArrayList<Integer>(); List<? extends Number> nList = iList; Number n = nList.get(0); nList.add(0.5); // Not allowed 我們可以從列表中得到Number,因為無論列表的確切元素類型是什么(Float、Integer或Number),我們都可以把它賦值給Number。 在下面這個例子中,通配符用于向API的用戶隱藏類型信息。在內(nèi)部,Set被存儲為CustomerImpl。而API的用戶只知道他們正在獲取一個Set,從中可以讀取Customer。 此處通配符是必需的,因為無法從Set<CustomerImpl>向Set<Customer>賦值: public class CustomerFactory {
private Set<CustomerImpl> _customers;
public Set<? extends Customer> getCustomers() {
return _customers;
}
}
通配符和協(xié)變返回 public interface NumberGenerator {
public List<? extends Number> generate();
}
public class FibonacciGenerator extends NumberGenerator {
public List<Integer> generate() {
...
}
}
如果要使用數(shù)組,接口可以返回Number[],而實現(xiàn)可以返回Integer[]。 下限 List<? extends Number> readList = new ArrayList<Integer>(); Number n = readList.get(0); List<? super Number> writeList = new ArrayList<Object>(); writeList.add(new Integer(5)); 第一個是可以從中讀數(shù)的列表。 無界通配符 公共API中的通配符 void removeNegatives(List<? extends Number> list); 構(gòu)造泛型類型 集合風(fēng)格(Collection-like)的函數(shù) public final class Pair<A,B> {
public final A first;
public final B second;
public Pair(A first, B second) {
this.first = first;
this.second = second;
}
}
這使從方法返回兩個項而無需為每個兩種類型的組合編寫專用的類成為可能。另一種方法是返回Object[],而這樣是類型不安全或者不整潔的。 public Pair<File,Boolean> getFileAndWriteStatus(String path){
// create file and status
return new Pair<File,Boolean>(file, status);
}
Pair<File,Boolean> result = getFileAndWriteStatus("...");
File f = result.first;
boolean writeable = result.second;
集合之外 public abstract class DBFactory<T extends DBPeer> {
protected abstract T createEmptyPeer();
public List<T> get(String constraint) {
List<T> peers = new ArrayList<T>();
// database magic
return peers;
}
}
通過實現(xiàn)DBFactory<Customer>,CustomerFactory必須從createEmptyPeer()返回一個Customer:
public class CustomerFactory extends DBFactory<Customer>{
public Customer createEmptyPeer() {
return new Customer();
}
}
泛型方法 <T> List<T> reverse(List<T> list) 具體化 按照泛型教程的慣例,解決方案使用的是“類型令牌”,通過向構(gòu)造函數(shù)添加一個Class<T>參數(shù),可以強制客戶端為類的類型參數(shù)提供正確的類對象: public class ArrayExample<T> {
private Class<T> clazz;
public ArrayExample(Class<T> clazz) {
this.clazz = clazz;
}
public T[] getArray(int size) {
return (T[])Array.newInstance(clazz, size);
}
}
為了構(gòu)造ArrayExample<String>,客戶端必須把String.class傳遞給構(gòu)造函數(shù),因為String.class的類型是Class<String>。 結(jié)束語 |
|
|