1. 程式人生 > >jdk 1.5 新特性 (ZZ)

jdk 1.5 新特性 (ZZ)

JDK 1.5
Java 5.0釋出了,許多人都將開始使用這個JDK版本的一些新增特性。從增強的for迴圈到諸如泛型(generic)之類更復雜的特性,都將很快出現在您所編寫的程式碼中。我們剛剛完成了一個基於Java 5.0的大型任務,而本文就是要介紹我們使用這些新特性的體驗。本文不是一篇入門性的文章,而是對這些特性以及它們所產生的影響的深入介紹,同時還給出了一些在專案中更有效地使用這些特性的技巧。

簡介
在JDK 1.5的beta階段,我們為BEA的Java IDE開發了一個Java 5編譯器。因為我們實現了許多新特性,所以人們開始以新的方式利用它們;有些用法很聰明,而有些用法明顯應該被列入禁用清單。編譯器本身使用了新的語言特性,所以我們也獲得了使用這些特性維護程式碼的直接體驗。本文將介紹其中的許多特性和使用它們的體驗。

我們假定您已經熟悉了這些新特性,所以不再全面介紹每個特性,而是談論一些有趣的、但很可能不太明顯的內容和用法。這些技巧出自我們的實際體驗,並大致按照語言特性進行了分類。
我們將從最簡單的特性開始,逐步過渡到高階特性。泛型所包含的內容特別豐富,因此佔了本文一半的篇幅。

增強的for迴圈
為了迭代集合和陣列,增強的for迴圈提供了一個簡單、相容的語法。有兩點值得一提:

Init表示式
在迴圈中,初始化表示式只計算一次。這意味著您通常可以移除一個變數宣告。在這個例子中,我們必須建立一個整型陣列來儲存computeNumbers() 的結果,以防止每一次迴圈都重新計算該方法。您可以看到,下面的程式碼要比上面的程式碼整潔一些,並且沒有洩露變數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;

侷限性
有時需要在迭代期間訪問迭代器或下標,看起來增強的for迴圈應該允許該操作,但事實上不是這樣,請看下面的例子:

for (int i=0; i < numbers.length ; i++) {
 if (i != 0) System.out.print(",");

 System.out.print(numbers[i]);
}

我們希望將陣列中的值列印為一個用逗號分隔的清單。我們需要知道目前是否是第一項,以便確定是否應該列印逗號。使用增強的for迴圈是無法獲知這種資訊的。我們需要自己保留一個下標或一個布林值來指示是否經過了第一項。   這是另一個例子:

for (Iterator<integer> it = n.iterator() ; it.hasNext() ; )
 if (it.next() < 0)
 it.remove();

在此例中,我們想從整數集合中刪除負數項。為此,需要對迭代器呼叫一個方法,但是當使用增強的for 迴圈時,迭代器對我們來說是看不到的。因此,我們只能使用Java 5之前版本的迭代方法。  順便說一下,這裡需要注意的是,由於Iterator是泛型,所以其宣告是Iterator<Integer>。許多人都忘記了這一點而使用了Iterator的原始格式。

註釋
註釋處理是一個很大的話題。因為本文只關注核心的語言特性,所以我們不打算涵蓋它所有的可能形式和陷阱。  我們將討論內建的註釋(SuppressWarnings,Deprecated和Override)以及一般註釋處理的侷限性。

Suppress Warnings
該註釋關閉了類或方法級別的編譯器警告。有時候您比編譯器更清楚地知道,程式碼必須使用一個被否決的方法或執行一些無法靜態確定是否型別安全的動作,而使用:

@SuppressWarnings("deprecation")
public static void selfDestruct() {
 Thread.currentThread().stop();
}

這可能是內建註釋最有用的地方。遺憾的是,1.5.0_04的javac不支援它。但是1.6支援它,並且Sun正在努力將其向後移植到1.5中。
Eclipse 3.1中支援該註釋,其他IDE也可能支援它。這允許您把程式碼徹底地從警告中解脫出來。如果在編譯時出現警告,可以確定是您剛剛把它新增進來——以幫助檢視那些可能不安全的程式碼。隨著泛型的新增,它使用起來將更趁手。

Deprecated
遺憾的是,Deprecated沒那麼有用。它本來旨在替換 @deprecated javadoc標籤,但是由於它不包含任何欄位,所以也就沒有方法來建議deprecated類或方法的使用者應該使用什麼做為替代品。大多數用法都同時需要javadoc標籤和這個註釋。

Override
Override表示,它所註釋的方法應該重寫超類中具有相同簽名的方法:

@Override
public int hashCode() {
 ...
}

看上面的例子,如果沒有在hashCode中將“C”大寫,在編譯時不會出現錯誤,但是在執行時將無法像期望的那樣呼叫該方法。通過新增Override標籤,編譯器會提示它是否真正地執行了重寫。
在超類發生改變的情況中,這也很有幫助。如果向該方法中新增一個新引數,而且方法本身也被重新命名了,那麼子類將突然不能編譯,因為它不再重寫超類的任何東西。

其它註釋
註釋在其他場景中非常有用。當不是直接修改行為而是增強行為時,特別是在新增樣板程式碼的情況下,註釋在諸如EJB和Web services這樣的框架中執行得非常好。
註釋不能用做前處理器。Sun的設計特別預防了完全因為註釋而修改類的位元組碼。這樣可以正確地理解該語言的成果,而且IDE之類的工具也可以執行深入的程式碼分析和重構之類的功能。
註釋不是銀彈。第一次遇到的時候,人們試圖嘗試各種技巧。請看下面這個從別人那裡獲得的建議:

public class Foo {

 @Property
 private int bar;

}

其思想是為私有欄位bar自動建立getter和setter方法。遺憾的是,這個想法有兩個失敗之處:1)它不能執行,2)它使程式碼難以閱讀和處理。   它是無法實現的,因為前面已經提到了,Sun特別阻止了對出現註釋的類進行修改。
即使是可能的,它也不是一個好主意,因為它使程式碼可讀性差。第一次看到這段程式碼的人會不知道該註釋建立了方法。此外,如果將來您需要在這些方法內部執行一些操作,註釋也是沒用的。   總之,不要試圖用註釋去做那些常規程式碼可以完成的事情。

列舉
enum 非常像public static final int宣告,後者作為列舉值已經使用了很多年。對int所做的最大也是最明顯的改進是型別安全——您不能錯誤地用列舉的一種型別代替另一種型別,這一點和 int不同,所有的int對編譯器來說都是一樣的。除去極少數例外的情況,通常都應該用enum例項替換全部的列舉風格的int結構。
列舉提供了一些附加的特性。EnumMap和EnumSet這兩個實用類是專門為列舉優化的標準集合實現。如果知道集合只包含列舉型別,那麼應該使用這些專門的集合來代替HashMap或HashSet。
大部分情況下,可以使用enum對程式碼中的所有public static final int做插入替換。它們是可比的,並且可以靜態匯入,所以對它們的引用看起來是等同的,即使是對於內部類(或內部列舉型別)。注意,比較列舉型別的時候,宣告它們的指令表明了它們的順序值。

“隱藏的”靜態方法
兩個靜態方法出現在所有列舉型別宣告中。因為它們是列舉子類上的靜態方法,而不是Enum本身的方法,所以它們在java.lang.Enum的javadoc中沒有出現。
第一個是values(),返回一個列舉型別所有可能值的陣列。
第二個是valueOf(),為提供的字串返回一個列舉型別,該列舉型別必須精確地匹配原始碼宣告。
方法
關於列舉型別,我們最喜歡的一個方面是它可以有方法。過去您可能需要編寫一些程式碼,對public static final int進行轉換,把它從資料庫型別轉換為JDBC URL。而現在則可以讓列舉型別本身帶一個整理程式碼的方法。下面就是一個例子,包括DatabaseType列舉型別的抽象方法以及每個列舉例項中提供的實現:

 public enum  DatabaseType {
 ORACLE {
 public String getJdbcUrl() {...}
 },
 MYSQL {
 public String getJdbcUrl() {...}
 };
 public abstract String getJdbcUrl();
 }

現在列舉型別可以直接提供它的實用方法。例如:

DatabaseType dbType = ...;
String jdbcURL = dbType.getJdbcUrl();

要獲取URL,必須預先知道該實用方法在哪裡。


可變引數(Vararg)
正確地使用可變引數確實可以清理一些垃圾程式碼。典型的例子是一個帶有可變的String引數個數的log方法:

 Log.log(String code)
 Log.log(String code,  String arg)
 Log.log(String code,  String arg1, String arg2)
 Log.log(String code,  String[] args)

當討論可變引數時,比較有趣的是,如果用新的可變引數替換前四個例子,將是相容的:
Log.log(String code, String... args)
所有的可變引數都是源相容的——那就是說,如果重新編譯log()方法的所有呼叫程式,可以直接替換全部的四個方法。然而,如果需要向後的二進位制相容性,那麼就需要捨去前三個方法。只有最後那個帶一個字串陣列引數的方法等效於可變引數版本,因此可以被可變引數版本替換。

型別強制轉換
如果希望呼叫程式瞭解應該使用哪種型別的引數,那麼應該避免用可變引數進行型別強制轉換。看下面這個例子,第一項希望是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
 }
 }

方法簽名應該如下所示,相應的可變引數分別使用String和Exception宣告:

Log.log(String message, Exception e, Object... objects) {...}

不要使用可變引數破壞型別系統。需要強型別化時才可以使用它。對於這個規則,PrintStream.printf()是一個有趣的例外:它提供型別資訊作為自己的第一個引數,以便稍後可以接受那些型別。

協變返回
協變返回的基本用法是用於在已知一個實現的返回型別比API更具體的時候避免進行型別強制轉換。在下面這個例子中,有一個返回Animal物件的Zoo 介面。我們的實現返回一個AnimalImpl物件,但是在JDK 1.5之前,要返回一個Animal物件就必須宣告。:

 public interface Zoo  {
 public Animal getAnimal();
 }
 public class ZooImpl  implements Zoo {
 public Animal getAnimal(){
 return new AnimalImpl();
 }
 }

協變返回的使用替換了三個反模式:

 * 直接欄位訪問。為了規避API限制,一些實現把子類直接暴露為欄位:

ZooImpl._animal

 * 另一種形式是,在知道實現的實際上是特定的子類的情況下,在呼叫程式中執行向下轉換:

((AnimalImpl)ZooImpl.getAnimal()).implMethod();

 * 我看到的最後一種形式是一個具體的方法,該方法用來避免由一個完全不同的簽名所引發的問題:

ZooImpl._getAnimal();

這三種模式都有它們的問題和侷限性。要麼是不夠整潔,要麼就是暴露了不必要的實現細節。

協變
協變返回模式就比較整潔、安全並且易於維護,它也不需要型別強制轉換或特定的方法或欄位:
public AnimalImpl getAnimal(){
return new AnimalImpl();
}
使用結果:
ZooImpl.getAnimal().implMethod();

使用泛型
我們將從兩個角度來了解泛型:使用泛型和構造泛型。我們不討論List、Set和Map的顯而易見的用法。知道泛型集合是強大的並且應該經常使用就足夠了。
我們將討論泛型方法的使用以及編譯器推斷型別的方法。通常這些都不會出問題,但是當出問題時,錯誤資訊會非常令人費解,所以需要了解如何修復這些問題。

泛型方法
除了泛型型別,Java 5還引入了泛型方法。在這個來自java.util.Collections的例子中,構造了一個單元素列表。新的List的元素型別是根據傳入方法的物件的型別來推斷的:

static <T> List<T> Collections.singletonList(T o)
示例用法:
public List<Integer> getListOfOne() {
 return Collections.singletonList(1);
}

在示例用法中,我們傳入了一個int。所以方法的返回型別就是List<Integer>。編譯器把T推斷為Integer。這和泛型型別是不同的,因為您通常不需要顯式地指定型別引數。
這也顯示了自動裝箱和泛型的相互作用。型別引數必須是引用型別:這就是為什麼我們得到的是List<Integer>而不是List<int>。

不帶引數的泛型方法
emptyList()方法與泛型一起引入,作為java.util.Collections中EMPTY_LIST欄位的型別安全置換:

static <T> List<T> Collections.emptyList()
示例用法:
public List<Integer> getNoIntegers() {
 return Collections.emptyList();
}

與先前的例子不同,這個方法沒有引數,那麼編譯器如何推斷T的型別呢?基本上,它將嘗試使用一次引數。如果沒有起作用,它再次嘗試使用返回或賦值型別。在本例中,返回的是List<Integer>,所以T被推斷為Integer。
如果在返回語句或賦值語句之外的位置呼叫泛型方法會怎麼樣呢?那麼編譯器將無法執行型別推斷的第二次傳送。在下面這個例子中,emptyList()是從條件運算子內部呼叫的:

public List<Integer> getNoIntegers() {
 return x ? Collections.emptyList() : null;
}

因為編譯器看不到返回上下文,也不能推斷T,所以它放棄並採用Object。您將看到一個錯誤訊息,比如:“無法將List<Object>轉換為List<Integer>。”
為了修復這個錯誤,應顯式地向方法呼叫傳遞型別引數。這樣,編譯器就不會試圖推斷型別引數,就可以獲得正確的結果:

return x ? Collections.<Integer>emptyList() : null;

這種情況經常發生的另一個地方是在方法呼叫中。如果一個方法帶一個List<String>引數,並且需要為那個引數呼叫這個傳遞的emptyList(),那麼也需要使用這個語法。

集合之外
這裡有三個泛型型別的例子,它們不是集合,而是以一種新穎的方式使用泛型。這三個例子都來自標準的Java庫:

 * Class<T>
 Class在類的型別上被引數化了。這就使無需型別強制轉換而構造一個newInstance成為可能。
 * Comparable<T>
 Comparable被實際的比較型別引數化。這就在compareTo()呼叫時提供了更強的型別化。例如,String實現 Comparable<String>。對除String之外的任何東西呼叫compareTo(),都會在編譯時失敗。
 * Enum<E extends Enum<E>>
 Enum被列舉型別引數化。一個名為Color的列舉型別將擴充套件Enum<Color>。getDeclaringClass()方法返回列舉型別的類物件,在這個例子中就是一個Color物件。它與getClass()不同,後者可能返回一個無名類。

萬用字元
泛型最複雜的部分是對萬用字元的理解。我們將討論三種類型的萬用字元以及它們的用途。
首先讓我們瞭解一下陣列是如何工作的。可以從一個Integer[]為一個Number[]賦值。如果嘗試把一個Float寫到Number[]中,那麼可以編譯,但在執行時會失敗,出現一個ArrayStoreException:

Integer[] ia = new Integer[5];
Number[] na = ia;
na[0] = 0.5; // compiles, but fails at runtime
如果試圖把該例直接轉換成泛型,那麼會在編譯時失敗,因為賦值是不被允許的:
List<Integer> iList = new ArrayList<Integer>();
List<Number> nList = iList; // not allowed
nList.add(0.5);

如果使用泛型,只要程式碼在編譯時沒有出現警告,就不會遇到執行時ClassCastException。

上限萬用字元
我們想要的是一個確切元素型別未知的列表,這一點與陣列是不同的。
List<Number>是一個列表,其元素型別是具體型別Number。
List<? extends Number>是一個確切元素型別未知的列表。它是Number或其子型別。

上限
如果我們更新初始的例子,並賦值給List<? extends Number>,那麼現在賦值就會成功了:

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。
我們仍然不能把浮點型別插入列表中。這會在編譯時失敗,因為我們不能證明這是安全的。如果我們想要向列表中新增浮點型別,它將破壞iList的初始型別安全——它只儲存Integer。
萬用字元給了我們比陣列更多的表達能力。

為什麼使用萬用字元
在下面這個例子中,萬用字元用於向API的使用者隱藏型別資訊。在內部,Set被儲存為CustomerImpl。而API的使用者只知道他們正在獲取一個Set,從中可以讀取Customer。
此處萬用字元是必需的,因為無法從Set<CustomerImpl>向Set<Customer>賦值:

public class CustomerFactory {
 private Set<CustomerImpl> _customers;
 public Set<? extends Customer> getCustomers() {
 return _customers;
 }
}

萬用字元和協變返回
萬用字元的另一種常見用法是和協變返回一起使用。與賦值相同的規則可以應用到協變返回上。如果希望在重寫的方法中返回一個更具體的泛型型別,宣告的方法必須使用萬用字元:

public interface NumberGenerator {
 public List<? extends Number> generate();
}
public class FibonacciGenerator extends NumberGenerator {
 public List<Integer> generate() {
 ...
 }
}

如果要使用陣列,介面可以返回Number[],而實現可以返回Integer[]。

下限
我們所談的主要是關於上限萬用字元的。還有一個下限萬用字元。List<? super Number>是一個確切“元素型別”未知的列表,但是可能是Mnumber,或者Number的超型別。所以它可能是一個 List<Number>或一個List<Object>。
下限萬用字元遠沒有上限萬用字元那樣常見,但是當需要它們的時候,它們就是必需的。

下限與上限

List<? extends Number> readList = new ArrayList<Integer>();
Number n = readList.get(0);

List<? super Number> writeList = new ArrayList<Object>();
writeList.add(new Integer(5));

第一個是可以從中讀數的列表。
第二個是可以向其寫數的列表。

無界萬用字元
最後,List<?>列表的內容可以是任何型別,而且它與List<? extends Object>幾乎相同。可以隨時讀取Object,但是不能向列表中寫入內容。

公共API中的萬用字元
總之,正如前面所說,萬用字元在向呼叫程式隱藏實現細節方面是非常重要的,但即使下限萬用字元看起來是提供只讀訪問,由於remove(int position)之類的非泛型方法,它們也並非如此。如果您想要一個真正不變的集合,可以使用java.util.Collection上的方法,比如 unmodifiableList()。
編寫API的時候要記得萬用字元。通常,在傳遞泛型型別時,應該嘗試使用萬用字元。它使更多的呼叫程式可以訪問API。
通過接收List<? extends Number>而不是List<Number>,下面的方法可以由許多不同型別的列表呼叫:

相關推薦

jdk 1.5 特性 ZZ

JDK 1.5Java 5.0釋出了,許多人都將開始使用這個JDK版本的一些新增特性。從增強的for迴圈到諸如泛型(generic)之類更復雜的特性,都將很快出現在您所編寫的程式碼中。我們剛剛完成了一個基於Java 5.0的大型任務,而本文就是要介紹我們使用這些新特性的體驗。本文不是一篇入門性的文章,而是對這

JDK 1.5 特性之列舉,舉例說明

對於比較穩定的值集合,Java 提供了列舉來定義,簡單舉例如下: package com.jalor; public class HomeWork { enum Weeks { MON() { @Override publ

JDK 1.5 特性-中文版

“JDK1.5”(開發代號猛虎)的一個重要主題就是通過新增一些特性來簡化開發,這些特性包括泛型,for-each 迴圈,自動裝包/拆包,列舉,可變引數, 靜態匯入 。使用這些特性有助於我們編寫更加清晰,精悍,安全的程式碼。  下面我們簡單介紹一下這些新特性。  1.泛型(Ge

jdk 1.5特性說明

JDK1.5”的一個重要主題就是通過新增一些特性來簡化開發,這些特性包括泛型,for-each 迴圈,自動裝包/拆包,列舉,可變引數, 靜態匯入 。使用這些特性有助於我們編寫更加清晰,精悍,安全的程式碼。 一. 首先簡單介紹一下各種特性及其使用 1.泛型(Generi

Java之JDK 1.5特性

---------------------- ASP.Net+Android+IOS開發、.Net培訓、期待與您交流! ---------------------- 下面簡略的說說jdk1.5的新特性。 泛型(Generics)--為集合(collections)提供編譯

jdk 1.5特性--泛型

我們還是國際慣例,不廢話直奔主題。 網上的資料這樣定義泛型:Java的泛型就是建立一個用型別作為引數的類。感覺這個總結不夠全面。按我的理解,泛型相當於Java型別的佔位 符。 搞程式設計的人在概念上較真是幼稚的,概念意會就可以了。我們來看看使用泛型的好處: packa

jdk 1.5特性——泛型

---------------------- ASP.Net+Android+IOS開發、.Net培訓、期待與您交流! ---------------------- 15,泛型 15.1泛型 泛型的符號:<泛型識別符號,泛型識別符號……> 泛型的作用:泛型就是

JDK 1.5 特性

前言:為什麼會出現新特性呢?      新的技術的出現就是為了解決老的問題,Java語言隨著自身的不斷髮展,對那些不利於提高開發率的技術進行了改進。 1.靜態匯入   靜態匯入可以匯入靜態方法,這樣就不必寫類名而可以直接省略類名呼叫靜態方法了。   語法:import

Kubernetes1.5特性:Kubelet API增加認證和授權能力

背景介紹 在Kubernetes1.5中,對於kubelet新增加了幾個同認證/授權相關的幾個啟動引數,分別是: 認證相關引數

kubernetes1.5特性:支援Photon卷外掛

在Kubernetes中卷的作用在於提供給POD持久化儲存,這些持久化儲存可以掛載到POD中的容器上,進而

Java基礎-----jdk1.5特性靜態匯入,增強for迴圈,列舉

package cn.itcast.jdk15; /*      Jdk1.5新特性之-----靜態匯入    靜態匯入的作用:簡化書寫。  靜態匯入可以作用於一個類的所有成員  靜態沒匯入的格式:

迄今最安全的MySQL?細數5.7那些驚豔與雞肋的特性

STRICT_TRANS_TABLES 意思是說要儲存的欄位的長度大於欄位定義的大小,直接報錯而非像5.6版本以及之前,截斷資料進行儲存,同時丟擲一個warning。注意同一個會話調整 sql_mode必須退出之後在進入sql_mode才會生效。詳細瞭解SQL_MODE請移步《sql_mode官方文件》(h

35-多執行緒--多執行緒JDK1.5特性Lock+Condition+使用JDK1.5特性解決多生產者多消費者問題+總結+範例Lock+Condition-多生產者多消費者問題實際開發程式碼

java.util.concurrent.locks包中提供了幾個介面:Lock、Condition...... 一、java.util.concurrent.locks.Lock 1、interface Lock:Lock實現提供了比使用synchronized方法(同

SpringMVC 4.1 特性JSONP的支援

為了啟用@ResponseBody和ResponseEntity方法的JSONP支援,需宣告一個@ControllerAdvice的bean,它擴充套件了AbstractJsonpResponseBod

MQTT 5.0 特性— 有效載荷標識與內容型別

有效載荷標識(Payload Format Indicator)與內容型別(Content Type)是 MQTT 5.0 新引入

MQTT 5.0 特性Clean Start 與 Session Expiry Interval

前言 MQTT v5.0 中的 Clean Start 與 Session Expiry Interval,對於有 MQTT v3

MySQL 8.0.2復制特性翻譯

ogl 防止 將不 地址 arc -- 等待 download 日誌 譯者:知數堂星耀隊 MySQL 8.0.2復制新特性 MySQL 8 正在變得原來越好,而且這也在我們MySQL復制研發團隊引起了一陣熱潮。我們一直致力於全面提升MySQL復制,通過引入新的和一些有趣

Java 接口 特性Java8

java7 void @override www jdk font default style static   Java8新特性之接口增強   在Java7以及以前的版本中,接口裏的方法都是抽象的,並且不存在靜態方法,屬性默認修飾符是public static final

Java8特性---Lambda表示式

Java8新特性之Lambda表示式 Lambda的語法格式 語法格式一:無引數,無返回值 語法格式二:有一個引數,並且無返回值 語法格式三:若只有一個引

php7特性:面向物件部分

1)、PHP 7 支援new class 來例項化一個匿名類這可以用來替代一些"用後即焚"的完整類定義。 2)、Closure::call():將一個閉包函式動態繫結到一個新的物件例項並呼叫執行該函式 3)、use:可以使用一個 use 從同一個 namespace 中匯入類、函