JDK1.5,1.6,1.7,1.8,1.9的新特性整理
目錄
jdk1.5新特性(2004年10月釋出)
1.泛型2.foreach
3.自動拆箱裝箱
4.列舉
5.靜態匯入(Static import)
6.元資料(Metadata)
7.執行緒池
8.Java Generics
1、泛型(Generics)
泛型是JDK1.5中一個最“酷”的特徵。通過引入泛型,我們將獲得編譯時型別的安全和執行時更小地丟擲 ClassCastExceptions的可能。在JDK1.5中,你可以宣告一個集合將接收/返回的物件的型別。在JDK1.4中,建立僱員名字的清單 (List)需要一個集合物件,像下面的語句:
List listOfEmployeeName = new ArrayList();
在JDK1.5中,你將使用下面語句
List<String> listOfEmployeeName = new ArrayList<String>();
最“酷”的是,如果你試圖插入非string型別的值,你將在編譯時發現並且修正這類問題。沒有泛型,你會發現這樣一個bug,當你的客戶呼叫後會告訴你,你所編寫的程式丟擲ClassCastException異常而崩潰。
另外,當你從集合中得到一個元素時你無需進行強制轉換。故原先為:
String employeeName = ((String) listOfEmployee.get(i));
而下面的語句將比上面的更加簡單:
String employeeName = listOfEmployee.get(i);
不清楚物件的型別而強制轉換物件是不合理的,並且更重要的是,它將在執行時失敗。假使使用者無意間傳入一個包含string buffers型別而非string型別的集合,那結果會怎樣呢。在Listing A中,客戶被要求傳入一個編譯器無法強制的strings型別集合。Listing B中顯示了同樣的方法使用泛型是如何實現的。
Listing A
static boolean checkName(Collection employeeNameList, String name) { for (Iterator i = employeeNamList.iterator(); i.hasNext();) { String s = (String) i.next(); if(s.equals(name)){ return true; //print employee name here ...... } } return false; }
Listing B
static boolean checkName(Collection<String> employeeNameList, String name) {
for (Iteratori = employeeNamList.iterator(); i.hasNext(); ) {
if(i.next().equals(name)){
return true;
//print employee name here ......
}
}
return false;
}
現在,通過方法簽名可以清楚知道輸入集合必須只能包含strings。如果客戶試圖傳入一個包含string buffers的集合,程式將不會編譯。同時注意,該方法不包含任何強制轉換。它只需要短短一行,一旦你習慣泛型後,它也更加清晰。
2、增強for迴圈
在JDK之前版本下的For迴圈語法如下:
void printAll(Collection c) {
for (Iterator i = c.iterator(); i.hasNext(); ) {
Employee emp = (Employee)i.next();
System.out.println(emp.getName());
}
}
現在,用增強的For語句實現相同方法:
void printAll(Collection c) {
for (Object o : c)
System.out.println((TimerTask)o).getName());
}
在這類For迴圈中,你應該將":"看成"in",所以,在該例中可以看成"for Object o in c"。你可以發現這種For迴圈更具可讀性。
3、自動拆裝箱(Autoboxing/unboxing)
Java有基本資料型別,在這些基本資料型別周圍又有包裝類。通常,程式設計人員需要將一種型別轉換成另一種。看看例項 C.中的程式碼片斷。
例項 C :
public class Employee {
private static final Integer CHILD = new Integer(0);
public static void main(String args[]) {
//code for adding n to an Integer
int n = 10;
Integer age = new Integer(30);
Integer ageAfterTenYear = new Integer(age.intValue +10);
}
}
請注意,用於計算ageAfterTenYear的內迴圈程式碼看上去是多麼雜亂。現在,在例項 D.中看看相同的程式使用autoboxing重寫後的樣子。
例項 D :
public class Employee {
public static void main(String args[]) {
int n =10;
Integer age = new Integer(30);
Integer ageAfterTenYear = age +10;
}
}
有一件事值得注意的:在先前,如果你取出(unbox)Null值,它將變為0。在這次程式碼中,編譯器將自動地轉換Integer為int然後加上10,接著將其轉換回Integer.。
4、型別安全的列舉(Typesafeenums)
型別安全列舉提供下列特性:
他們提供編譯時型別安全。
他們都是物件,因此你不需要將他們放入集合中。
他們作為一種類的實現,因此你可以新增一些方法。
他們為列舉型別提供了合適的名稱空間。
他們列印的值具有情報性(informative)― 如果你列印一個整數列舉(intenum),你只是看見一個數字,它可能並不具有情報性。
例一:
enum Season { winter, spring, summer, fall }
例二:
public enum Coin {
penny(1), nickel(5), dime(10), quarter(25);
Coin(int value) { this.value = value; }
private final int value;
public int value() { return value; }
}
5、靜態匯入(Static import)
靜態匯入使程式碼更易讀。通常,你要使用定義在另一個類中的常量(constants),像這樣:
import org.yyy.pkg.Increment;
class Employee {
public Double calculateSalary(Double salary{
return salary + Increment.INCREMENT * salary;
}
}
當時使用靜態匯入,我們無需為常量名字首類名就能使用這些常量,像這樣:
import static org.yyy.pkg.Increment;
class Employee {
public Double calculateSalary(Double salary{
return salary + INCREMENT * salary;
}
}
注意,我們可以呼叫INCREMENT這一常量而不要使用類名Increment.。
6、元資料(Metadata)
元資料特徵志於使開發者們藉助廠商提供的工具可以進行更簡易的開發。看一看例項 E.中的程式碼。 (Remote是用於遠端服務呼叫的介面)
例項 E
import org.yyy.hr;
public interface EmployeeI extends Java.rmi.Remote {
public String getName() throws Java.rmi.RemoteException;
public String getLocation () throws Java.rmi.RemoteException;
}
public class EmployeeImpl implements EmployeeI {
public String getName() {
}
public String getLocation () {
}
}
通過元資料的支援,你可以改寫例項 E中的程式碼為:
import org.yyy.hr;
public class Employee {
@Remote public String getName() {
...
}
@Remote public public String getLocation() {
...
}
}
7、執行緒池
Java5中,對Java執行緒的類庫做了大量的擴充套件,其中執行緒池就是Java5的新特徵之一,除了執行緒池之外,還有很多多執行緒相關的內容,為多執行緒的程式設計帶來了極大便利。為了編寫高效穩定可靠的多執行緒程式,執行緒部分的新增內容顯得尤為重要。
有關Java5執行緒新特徵的內容全部在java.util.concurrent下面,裡面包含數目眾多的介面和類,熟悉這部分API特徵是一項艱難的學習過程。目前有關這方面的資料和書籍都少之又少,大所屬介紹執行緒方面書籍還停留在java5之前的知識層面上。
當然新特徵對做多執行緒程式沒有必須的關係,在java5之前通用可以寫出很優秀的多執行緒程式。只是代價不一樣而已。
執行緒池的基本思想還是一種物件池的思想,開闢一塊記憶體空間,裡面存放了眾多(未死亡)的執行緒,池中執行緒執行排程由池管理器來處理。當有執行緒任務時,從池中取一個,執行完成後執行緒物件歸池,這樣可以避免反覆建立執行緒物件所帶來的效能開銷,節省了系統的資源。
在Java5之前,要實現一個執行緒池是相當有難度的,現在Java5為我們做好了一切,我們只需要按照提供的API來使用,即可享受執行緒池帶來的極大便利。
Java5的執行緒池分好多種:固定尺寸的執行緒池、可變尺寸連線池、。
在使用執行緒池之前,必須知道如何去建立一個執行緒池,在Java5中,需要了解的是java.util.concurrent.Executors類的API,這個類提供大量建立連線池的靜態方法,是必須掌握的。
7.1、固定大小的執行緒池
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
/**
* Java執行緒:執行緒池-
*
* @author Administrator 2009-11-4 23:30:44
*/
public class Test {
public static void main(String[] args) {
//建立一個可重用固定執行緒數的執行緒池
ExecutorService pool = Executors.newFixedThreadPool(2);
//建立實現了Runnable介面物件,Thread物件當然也實現了Runnable介面
Thread t1 = new MyThread();
Thread t2 = new MyThread();
Thread t3 = new MyThread();
Thread t4 = new MyThread();
Thread t5 = new MyThread();
//將執行緒放入池中進行執行
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.execute(t4);
pool.execute(t5);
//關閉執行緒池
pool.shutdown();
}
}
class MyThread extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"正在執行。。。");
}
}
pool-1-thread-1正在執行。。。
pool-1-thread-1正在執行。。。
pool-1-thread-1正在執行。。。
pool-1-thread-1正在執行。。。
pool-1-thread-2正在執行。。。
Process finished with exit code 0
7.2、單任務執行緒池
在上例的基礎上改一行建立pool物件的程式碼為:
//建立一個使用單個 worker 執行緒的 Executor,以無界佇列方式來執行該執行緒。
ExecutorService pool = Executors.newSingleThreadExecutor();
輸出結果為:
pool-1-thread-1正在執行。。。
pool-1-thread-1正在執行。。。
pool-1-thread-1正在執行。。。
pool-1-thread-1正在執行。。。
pool-1-thread-1正在執行。。。
Process finished with exit code 0
對於以上兩種連線池,大小都是固定的,當要加入的池的執行緒(或者任務)超過池最大尺寸時候,則入此執行緒池需要排隊等待。
一旦池中有執行緒完畢,則排隊等待的某個執行緒會入池執行。
7.3、可變尺寸的執行緒池
與上面的類似,只是改動下pool的建立方式:
//建立一個可根據需要建立新執行緒的執行緒池,但是在以前構造的執行緒可用時將重用它們。
ExecutorService pool = Executors.newCachedThreadPool();
pool-1-thread-5正在執行。。。
pool-1-thread-1正在執行。。。
pool-1-thread-4正在執行。。。
pool-1-thread-3正在執行。。。
pool-1-thread-2正在執行。。。
Process finished with exit code 0
7.4、延遲連線池
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* Java執行緒:執行緒池-
*
* @author Administrator 2009-11-4 23:30:44
*/
public class Test {
public static void main(String[] args) {
//建立一個執行緒池,它可安排在給定延遲後執行命令或者定期地執行。
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
//建立實現了Runnable介面物件,Thread物件當然也實現了Runnable介面
Thread t1 = new MyThread();
Thread t2 = new MyThread();
Thread t3 = new MyThread();
Thread t4 = new MyThread();
Thread t5 = new MyThread();
//將執行緒放入池中進行執行
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
//使用延遲執行風格的方法
pool.schedule(t4, 10, TimeUnit.MILLISECONDS);
pool.schedule(t5, 10, TimeUnit.MILLISECONDS);
//關閉執行緒池
pool.shutdown();
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "正在執行。。。");
}
}
pool-1-thread-1正在執行。。。
pool-1-thread-2正在執行。。。
pool-1-thread-1正在執行。。。
pool-1-thread-1正在執行。。。
pool-1-thread-2正在執行。。。
Process finished with exit code 0
7.5、單任務延遲連線池
在四程式碼基礎上,做改動
//建立一個單執行緒執行程式,它可安排在給定延遲後執行命令或者定期地執行。
ScheduledExecutorService pool = Executors.newSingleThreadScheduledExecutor();
pool-1-thread-1正在執行。。。
pool-1-thread-1正在執行。。。
pool-1-thread-1正在執行。。。
pool-1-thread-1正在執行。。。
pool-1-thread-1正在執行。。。
Process finished with exit code 0
7.6、自定義執行緒池
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Java執行緒:執行緒池-自定義執行緒池
*
* @author Administrator 2009-11-4 23:30:44
*/
public class Test {
public static void main(String[] args) {
//建立等待佇列
BlockingQueue<Runnable> bqueue = new ArrayBlockingQueue<Runnable>(20);
//建立一個單執行緒執行程式,它可安排在給定延遲後執行命令或者定期地執行。
ThreadPoolExecutor pool = new ThreadPoolExecutor(2,3,2,TimeUnit.MILLISECONDS,bqueue);
//建立實現了Runnable介面物件,Thread物件當然也實現了Runnable介面
Thread t1 = new MyThread();
Thread t2 = new MyThread();
Thread t3 = new MyThread();
Thread t4 = new MyThread();
Thread t5 = new MyThread();
Thread t6 = new MyThread();
Thread t7 = new MyThread();
//將執行緒放入池中進行執行
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.execute(t4);
pool.execute(t5);
pool.execute(t6);
pool.execute(t7);
//關閉執行緒池
pool.shutdown();
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "正在執行。。。");
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
pool-1-thread-1正在執行。。。
pool-1-thread-2正在執行。。。
pool-1-thread-2正在執行。。。
pool-1-thread-1正在執行。。。
pool-1-thread-2正在執行。。。
pool-1-thread-1正在執行。。。
pool-1-thread-2正在執行。。。
Process finished with exit code 0
建立自定義執行緒池的構造方法很多,本例中引數的含義如下:
ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
用給定的初始引數和預設的執行緒工廠及處理程式建立新的 ThreadPoolExecutor。使用 Executors 工廠方法之一比使用此通用構造方法方便得多。
引數:
corePoolSize - 池中所儲存的執行緒數,包括空閒執行緒。
maximumPoolSize - 池中允許的最大執行緒數。
keepAliveTime - 當執行緒數大於核心時,此為終止前多餘的空閒執行緒等待新任務的最長時間。
unit - keepAliveTime 引數的時間單位。
workQueue - 執行前用於保持任務的佇列。此佇列僅保持由 execute 方法提交的 Runnable 任務。
丟擲:
IllegalArgumentException - 如果 corePoolSize 或 keepAliveTime 小於零,或者 maximumPoolSize 小於或等於零,或者 corePoolSize 大於 maximumPoolSize。
NullPointerException - 如果 workQueue 為 null
自定義連線池稍微麻煩些,不過通過建立的ThreadPoolExecutor執行緒池物件,可以獲取到當前執行緒池的尺寸、正在執行任務的執行緒數、工作佇列等等。
8.Java Generics
8.1
在JDK1.5之前的版本中,對於一個Collection類庫中的容器類例項,可將任意型別物件加入其中(都被當作Object例項看待);從容器中取出的物件也只是一個Object例項,需要將其強制轉型為期待的型別,這種強制轉型的執行時正確性由程式設計師自行保證。
例如以下程式碼片斷:
List intList = new ArrayList(); //建立一個List,準備存放一些Integer例項
intList.add(new Integer(0));
intList.add(“1”); //不小心加入了一個字串;但在編譯和執行時都不報錯,只有仔細的程式碼走
//才能揪出
Integer i0 = (Integer)intList.get(0);
Integer i1 = (Integer)intList.get(1); //編譯通過,直到執行時才拋ClassCastException
而在JDK1.5中,可以建立一個明確只能存放某種特定型別物件的容器類例項,例如如下程式碼:
List<Integer> intList = new ArrayList<Integer>(); //intList為存放Integer例項的List
intList.add(new Integer(0));
Integer i0 = intList.get(0); //無需(Integer)強制轉型;List<Integer>的get()返回的就是Integer類
//型物件
intList.add(“1”); //編譯不通過,因為List<Integer>的add()方法只接受Integer型別的引數
“List<Integer> intList = new ArrayList<Integer>();”就是最簡單且最常用的Generic應用;顯然,運用Generic後的程式碼更具可讀性和健壯性。
8.2 Generic類
JDK1.5中Collection類庫的大部分類都被改進為Generic類。以下是從JDK1.5原始碼中
擷取的關於List和Iterator介面定義的程式碼片斷:
public interface List<E> {
void add(E x);
Iterator<E> iterator;
}
public interface Iterator<E> {
E next();
boolean hasNext();
}
以List為例,“public interface List<E>”中的E是List的型別引數,使用者在使用List
時可為型別引數指定一個確定型別值(如List<Integer>)。型別值為Java編譯器所用,確保使用者程式碼型別安全。例如,編 譯器知道List<Integer>的add()方法只接受Integer型別的引數,因此如果你在程式碼中將一個字串傳入add()將導致 編譯錯誤;編譯器知道Iterator<Integer>的next()方法返回一個Integer的例項,你在程式碼中也就無需對返回結果進 行(Integer)強制轉型。程式碼檢驗通過(語法正確且不會導致執行時型別安全問題)後,編譯器對現有程式碼有一個轉換工作。簡單的說,就是去除程式碼中的 型別值資訊,在必要處新增轉型程式碼。例如如下程式碼:
public String demo() {
List<String> strList = new ArrayList<String>();
strList.add(“Hello!”);
return strList.get(0);
}
編譯器在檢驗通過後,將其轉換為:
public String demo() {
List strList = new ArrayList(); //去除型別值<String>
strList.add(“Hello!”);
return (String)strList.get(0); //新增轉型動作程式碼(String)
}
可見,型別值資訊只為Java編譯器在編譯時所用,確保程式碼無型別安全問題;驗證通過之後,即被去除。對於JVM而言,只有如JDK1.5之前版本一樣的 List,並無List<Integer>和List<String>之分。這也就是Java Generics實現中關鍵技術Erasure的基本思想。以下程式碼在控制檯輸出的就是“true”。
List<String> strList = new ArrayList<String>();
List<Integer> intList = new ArrayList<Integer>();
System.out.println(strList.getClass() == intList.getClass());
可以將Generic理解為:為提高Java程式碼型別安全性(在編譯時確保,而非等到執行時才暴露),Java程式碼與Java編譯器之間新增的一種約定規 範。Java編譯器在編譯結果*.class檔案中供JVM讀取的部分裡沒有保留Generic的任何資訊;JVM看不到Generic的存在。
對於Generic類(設為GenericClass)的型別引數(設為T):
1) 由於對於JVM而言,只有一個GenericClass類,所以GenericClass類的靜態欄位和靜態方法的定義中不能使用T。T只能出現在 GenericClass的非靜態欄位或非靜態方法中。也即T是與GenericClass的例項相關的資訊;
2) T只在編譯時被編譯器理解,因此也就不能與執行時被JVM理解並執行其代表的操作的操作符(如instanceof 和new)聯用。
class GenericClass<T> {
T t1;
public void method1(T t){
t1 = new T(); //編譯錯誤,T不能與new聯用
if (t1 instanceof T) {}; //編譯錯誤,T不能與instanceof聯用
};
static T t2; //編譯錯誤,靜態欄位不能使用T
public static void method2(T t){};//編譯錯誤,靜態方法不能使用T
}
Generic類可以有多個型別引數,且型別引數命名一般為大寫單字元。例如Collection類庫中的Map宣告為:
public interface Map<K,V> {
……;
}
8.3 Generic類和原(Raw)類
對每一個Generic類,使用者在使用時可以不指定型別引數。例如,對於List<E>,使用者
可以以“List<String> list;”方式使用,也可以以“List list;”方式使用。“List<String>”被稱為引數化的Generic類(型別引數被賦值),而“List”稱為原類。原類 List的使用方式和效果與JDK1.5之前版本List的一樣;使用原類也就失去了Generic帶來的可讀性和健壯性的增強。
允許原類使用方式的存在顯然是為了程式碼的向前相容:即JDK1.5之前的程式碼在JDK1.5下仍然編譯通過且正常執行。
當你在JDK1.5中使用原類並向原類例項中新增物件時,編譯器會產生警告,因為它無法保證待新增物件型別的正確性。編譯通過是為了保證程式碼向前相容,產生警告是提醒潛在的風險。
public void test () {
List list = new ArrayList();
list.add("tt");//JDK1.5編譯器對此行產生警告
}
8.4 Generic類和子類
List<String> ls = new ArrayList<String>();
List<Object> lo = ls; //編譯錯誤:Type mismatch: cannot convert from List<Dummy> to
//List<Object>
以上第二行程式碼導致的編譯錯誤“Type mismatch: cannot convert from List<Dummy> to
List<Object>”是不是有點出人意料?直觀上看,就像String是Object的子類,因此‘Object o = “String”’合法一樣,存放String的List是存放Object的List的子類,因此第二行應該是合法的。反過來分析,如果第二行是合法 的,那麼如下會導致執行時異常的程式碼也是合法的:
lo.add(new Object); //會導致在ls中添加了非String物件
String s = ls.get(0); //ls.get(0)返回的實際上只是一個Object例項,會導致ClassCastException
編譯器顯然不允許此種情形發生,因此不允許“List<Object> lo = ls”編譯通過。
因此,直觀上的“存放String的List是存放Object的List的子類”是錯誤的。更一般的說,設Foo是Bar的子類,G是Generic型別宣告,G<Foo>不是G<Bar>的子類。
8.5 引數化的Generic類和陣列
我們知道,如果T是S的子類,則T[]也是S[]的子類。因此,如下程式碼編譯通過,只
在執行時於第三行程式碼處拋ArrayStoreException。
String[] words = new String[10];
Object[] objects = words;
Objects[0] = new Object(); //編譯通過,但執行時會拋ArrayStoreException
再分析如下程式碼:
List<String>[] wordLists = new ArrayList<String>[10];
ArrayList<Integer> integerList = new ArrayList<Integer>();
integerList.add(123);
Object[] objects = wordLists;
objects[0] = integerList;//執行時不出錯,因為執行時ArrayList<String>和ArrayList<Integer>都
//為ArrayList
String s = wordlists[0].get(0); //編譯通過,執行時拋ClassCastException
就出現了“正確使用了Generic,但在執行時仍然出現ClassCastException”的情形。顯然Java編譯器不允許此種情形的發生。事實 上,以上程式碼的第一行“List<String>[] wordLists = new ArrayList<String>[10];”就是編譯不通過的,也就不存在接下來的程式碼。
更一般地說,不能建立引數化的Generic類的陣列。
8.6 型別引數萬用字元?
由“Generic類和子類”節知,Collection<Object>不是存放其它型別物件的Collection(例
如Collection<String>)的基類(抽象),那麼如何表示任一種引數化的Collection的呢?使用 Collection<?>。?即代表任一型別引數值。例如,我們可以很容易寫出下面的通用函式printCollection():
public static void printCollection(Collection<?> c) {
//如此遍歷Collection的簡潔方式也是JDK1.5新引入的
for (Object o : c) {
System.out.println(o);
}
}
這樣,既可以將Collection<String>的例項,也可以將Collection<Integer>的例項作為引數呼叫printCollection()方法。
然而,要注意一點,你不能往Collection<?>容器例項中加入任何非null元素,例如如下程式碼的第三行編譯不通過:
public static void testAdd(Collection<?> c) {
c.add(null); //編譯通過
c.add(“test”); //編譯錯誤
}
很好理解:c中要存放的物件的具體型別不確定,編譯器無法驗證待新增物件型別的正確性,因此不能加入物件例項;而null可以看作是任一個類的例項,因而允許加入。
另外,儘管c中要存放的物件的型別不確定,但我們知道任何類都是Object子類,因此從c中取出的物件都統一作為Object例項。
更進一步,如果BaseClass代表某個可被繼承的類的類名,那麼Collection<? extends BaseClass>代表型別引數值為BaseClass或BaseClass某個子類的任一引數化Collection。對於 Collection<? extends BaseClass>的例項c,因為c中要存放的物件具體型別不確定,不能往其加入非null物件,但從c中取出的物件都統一作為 BaseClass例項。事實上,你可以把Collection<?>看作Collection<? extends Object>的簡潔形式。
另一種情形:如果SubClass代表任一個類的類名,那麼Collection<? super SubClass>代表型別引數值為SubClass或SubClass某個祖先類的任一引數化Collection。對於 Collection<? super SubClass>的例項c,你可以將SubClass例項加入其中,但從中取出的物件都是Object例項。
8.7 Generic方法
我們可以定義Generic類,同樣可以定義Generic方法,即將方法的一個或多個引數的型別引數化,如程式碼:
public static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
for (T o : a) {
c.add(o); //合法。注意與Collection<?>的區別
}
}
我們可以以如下方式呼叫fromArrayToCollection():
Object[] oa = new Object[100];
Collection<Object> co = new ArrayList<Object>();
fromArrayToCollection(oa, co); //此時,T即為Object
String[] sa = new String[100];
Collection<String> cs = new ArrayList<String>();
fromArrayToCollection(sa, cs); //此時,T即為String
fromArrayToCollection(sa, co); //此時,T即為Object
Integer[] ia = new Integer[100];
Float[] fa = new Float[100];
Number[] na = new Number[100];
Collection<Number> cn = new ArrayList<Number>();
fromArrayToCollection(ia, cn); //此時,T即為Number
fromArrayToCollection(fa, cn); //此時,T即為Number
fromArrayToCollection(na, cn); //此時,T即為Number
fromArrayToCollection(na, co); //此時,T即為Object
fromArrayToCollection(na, cs); //編譯錯誤
通過以上程式碼可以看出,我們在呼叫fromArrayToCollection()時,無需明確指定T為何種型別(與Generic類的使用方式不同), 而是像呼叫一般method一樣,直接提供引數值,編譯器會根據提供的引數值自動為T賦型別值或提示編譯錯誤(引數值不當)。
考慮如下函式sum()
public static long sum(Collection<? extends Number> numbers) {
long sum = 0;
for (Number n : numbers) {
sum += n.longValue();
}
return sum;
}
我們也可以將其以Generic方法實現:
public static <T extends Number> long sum(Collection<T> numbers) {
long sum = 0;
for (Number n : numbers) {
sum += n.longValue();
}
return sum;
}
那麼對於一個方法,當要求引數型別可變時,是採用Generic方法,還是採用型別引數萬用字元方式呢?一般而言,如果引數型別間或引數型別與返回值型別間存在某種依賴關係,則採取Generic方法,否則採取型別引數萬用字元方式。
這一原則在Collection類庫的原始碼中得到了很好的體現,例如Collection介面的containsAll()、addAll()和toArray()方法:
interface Collection<E> {
public boolean containsAll(Collecion<?> c); //引數間型別以及引數與返回
//值間型別無依賴
<T> T[] toArray(T[] a); //引數a與返回值都是相同類的陣列,有依賴
}
當然,根據需要,二者也可以結合使用,例如Collections中的copy()方法:
class Collections {
public static <T> void copy(List<T> dest, List<? extends T> src) {
…….
}
}
jdk1.6新特性(2006年4月釋出)
1.Desktop類和SystemTray類
2.使用JAXB2來實現物件與XML之間的對映
3.StAX
4.使用Compiler API
5.輕量級Http Server API
6.插入式註解處理API(Pluggable Annotation Processing API)
7.用Console開發控制檯程式
8.對指令碼語言的支援
9.Common Annotations
1.Desktop類和SystemTray類
在JDK1.6中,AWT新增加了兩個類:Desktop和SystemTray.
前者可以用來開啟系統預設瀏覽器瀏覽指定的URL,開啟系統預設郵件客戶端給指定的郵箱發郵件,用預設應用程式開啟或編輯檔案(比如,用記事本開啟以txt為字尾名的檔案),用系統預設的印表機列印文件;後者可以用來在系統托盤區建立一個托盤程式.
2.使用JAXB2來實現物件與XML之間的對映
JAXB是Java Architecture for XML Binding的縮寫,可以將一個Java物件轉變成為XML格式,反之亦然.
我們把物件與關係資料庫之間的對映稱為ORM,其實也可以把物件與XML之間的對映稱為OXM(Object XML Mapping).原來JAXB是Java EE的一部分,在JDK1.6中,SUN將其放到了Java SE中,這也是SUN的一貫做法.JDK1.6中自帶的這個JAXB版本是2.0,比起1.0(JSR 31)來,JAXB2(JSR 222)用JDK5的新特性Annotation來標識要作繫結的類和屬性等,這就極大簡化了開發的工作量.實際上,在Java EE 5.0中,EJB和Web Services也通過Annotation來簡化開發工作.另外,JAXB2在底層是用StAX(JSR 173)來處理XML文件.除了JAXB之外,我們還可以通過XMLBeans和Castor等來實現同樣的功能.
3.StAX
StAX(JSR 173)是JDK1.6.0中除了DOM和SAX之外的又一種處理XML文件的API.
StAX 的來歷:在JAXP1.3(JSR 206)有兩種處理XML文件的方法:DOM(Document Object Model)和SAX(Simple API for XML).
JDK1.6.0中的JAXB2(JSR 222)和JAX-WS 2.0(JSR 224)都會用到StAXSun決定把StAX加入到JAXP家族當中來,並將JAXP的版本升級到1.4(JAXP1.4是JAXP1.3的維護版 本).JDK1.6裡面JAXP的版本就是1.4.
StAX是The Streaming API for XML的縮寫,一種利用拉模式解析(pull-parsing)XML文件的API.StAX通過提供一種基於事件迭代器(Iterator)的API讓 程式設計師去控制xml文件解析過程,程式遍歷這個事件迭代器去處理每一個解析事件,解析事件可以看做是程式拉出來的,也就是程式促使解析器產生一個解析事件 然後處理該事件,之後又促使解析器產生下一個解析事件,如此迴圈直到碰到文件結束符;
SAX也是基於事件處理xml文件,但卻是用推模式解析,解析器解析完整個xml文件後,才產生解析事件,然後推給程式去處理這些事件;DOM採 用的方式是將整個xml文件對映到一顆記憶體樹,這樣就可以很容易地得到父節點和子結點以及兄弟節點的資料,但如果文件很大,將會嚴重影響效能.
4.使用Compiler API
現在我 們可以用JDK1.6 的Compiler API(JSR 199)去動態編譯Java原始檔,Compiler API結合反射功能就可以實現動態的產生Java程式碼並編譯執行這些程式碼,有點動態語言的特徵.
這個特性對於某些需要用到動態編譯的應用程式相當有用,比如JSP Web Server,當我們手動修改JSP後,是不希望需要重啟Web Server才可以看到效果的,這時候我們就可以用Compiler API來實現動態編譯JSP檔案,當然,現在的JSP Web Server也是支援JSP熱部署的,現在的JSP Web Server通過在執行期間通過Runtime.exec或ProcessBuilder來呼叫javac來編譯程式碼,這種方式需要我們產生另一個程序去 做編譯工作,不夠優雅容易使程式碼依賴與特定的作業系統;Compiler API通過一套易用的標準的API提供了更加豐富的方式去做動態編譯,是跨平臺的.
5.輕量級Http Server API
JDK1.6 提供了一個簡單的Http Server API,據此我們可以構建自己的嵌入式Http Server,它支援Http和Https協議,提供了HTTP1.1的部分實現,沒有被實現的那部分可以通過擴充套件已有的Http Server API來實現,程式設計師自己實現HttpHandler介面,HttpServer會呼叫HttpHandler實現類的回撥方法來處理客戶端請求,在這 裡,我們把一個Http請求和它的響應稱為一個交換,包裝成HttpExchange類,HttpServer負責將HttpExchange傳給 HttpHandler實現類的回撥方法.
6.插入式註解處理API(Pluggable Annotation Processing API)
插入式註解處理API(JSR 269)提供一套標準API來處理Annotations(JSR 175)
實際上JSR 269不僅僅用來處理Annotation,我覺得更強大的功能是它建立了Java 語言本身的一個模型,它把method,package,constructor,type,variable, enum,annotation等Java語言元素對映為Types和Elements(兩者有什麼區別?),從而將Java語言的語義對映成為物件,我 們可以在javax.lang.model包下面可以看到這些類. 我們可以利用JSR 269提供的API來構建一個功能豐富的超程式設計(metaprogramming)環境.
JSR 269用Annotation Processor在編譯期間而不是執行期間處理Annotation,Annotation Processor相當於編譯器的一個外掛,稱為插入式註解處理.如果Annotation Processor處理Annotation時(執行process方法)產生了新的Java程式碼,編譯器會再呼叫一次Annotation Processor,如果第二次處理還有新程式碼產生,就會接著呼叫Annotation Processor,直到沒有新程式碼產生為止.每執行一次process()方法被稱為一個"round",這樣整個Annotation processing過程可以看作是一個round的序列.
JSR 269主要被設計成為針對Tools或者容器的API. 舉個例子,我們想建立一套基於Annotation的單元測試框架(如TestNG),在測試類裡面用Annotation來標識測試期間需要執行的測試方法.
7.用Console開發控制檯程式
JDK1.6中提供了java.io.Console 類專用來訪問基於字元的控制檯裝置.你的程式如果要與Windows下的cmd或者Linux下的Terminal互動,就可以用Console類代勞. 但我們不總是能得到可用的Console,一個JVM是否有可用的Console依賴於底層平臺和JVM如何被呼叫.如果JVM是在互動式命令列(比如 Windows的cmd)中啟動的,並且輸入輸出沒有重定向到另外的地方,那麼就可以得到一個可用的Console例項.
8.對指令碼語言的支援
如: ruby,groovy,javascript
9.Common Annotations
Common annotations原本是Java EE 5.0(JSR 244)規範的一部分,現在SUN把它的一部分放到了Java SE 6.0中.
隨著Annotation元資料功能(JSR 175)加入到Java SE 5.0裡面,很多Java 技術(比如EJB,Web Services)都會用Annotation部分代替XML檔案來配置執行引數(或者說是支援宣告式程式設計,如EJB的宣告式事務),如果這些技術為通用 目的都單獨定義了自己的Annotations,顯然有點重複建設,,為其他相關的Java技術定義一套公共的Annotation是有價值的,可以避免 重複建設的同時,也保證Java SE和Java EE 各種技術的一致性.
下面列舉出Common Annotations 1.0裡面的10個Annotations Common Annotations Annotation Retention Target Description Generated Source ANNOTATION_TYPE,CONSTRUCTOR,FIELD,LOCAL_VARIABLE,METHOD,PACKAGE,PARAMETER,TYPE 用於標註生成的原始碼Resource Runtime TYPE,METHOD,FIELD用於標註所依賴的資源,容器據此注入外部資源依賴,有基於欄位的注入和基於setter方法的注入兩種方式 Resources Runtime TYPE同時標註多個外部依賴,容器會把所有這些外部依賴注入PostConstruct Runtime METHOD標註當容器注入所有依賴之後執行的方法,用來進行依賴注入後的初始化工作,只有一個方法可以標註為PostConstruct PreDestroy Runtime METHOD當物件例項將要被從容器當中刪掉之前,要執行的回撥方法要標註為PreDestroy RunAs Runtime TYPE用於標註用什麼安全形色來執行被標註類的方法,這個安全形色和Container的Security角色一致的.RolesAllowed Runtime TYPE,METHOD用於標註允許執行被標註類或方法的安全形色,這個安全形色和Container的Security角色一致的PermitAll Runtime TYPE,METHOD允許所有角色執行被標註的類或方法DenyAll Runtime TYPE,METHOD不允許任何角色執行被標註的類或方法,表明該類或方法不能在Java EE容器裡面執行DeclareRoles Runtime TYPE用來定義可以被應用程式檢驗的安全形色,通常用isUserInRole來檢驗安全形色.
jdk1.7新特性(2011年7月28號釋出)
1 對集合類的語言支援;
2 自動資源管理;
3 改進的通用例項建立型別推斷;
4 數字字面量下劃線支援;
5 switch中使用string;
6 二進位制字面量;
7 簡化可變引數方法呼叫。
下面我們來仔細看一下這7大新功能:
1 對集合類的語言支援
Java將包含對建立集合類的第一類語言支援。這意味著集合類的建立可以像Ruby和Perl那樣了。
原本需要這樣:
List<String> list = new ArrayList<String>();
list.add("item");
String item = list.get(0);
Set<String> set = new HashSet<String>();
set.add("item");
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("key", 1);
int value = map.get("key");
現在你可以這樣:
List<String> list = ["item"];
String item = list[0];
Set<String> set = {"item"};
Map<String, Integer> map = {"key" : 1};
int value = map["key"];
這些集合是不可變的。
2 自動資源管理
Java中某些資源是需要手動關閉的,如InputStream,Writes,Sockets,Sql classes等。這個新的語言特性允許try語句本身申請更多的資源,