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 (Iteratori = 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、在JDK當前版本下的For循環語法如下:
void printAll(Collection c) {
for (Iteratori = 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有基本數據類型,在這些基本數據類型周圍又有包裝類。通常,編程人員需要將一種類型轉換成另一種。看看Listing C.中的代碼片斷。
Listing 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的內循環代碼看上去是多麼雜亂。現在,在Listing D.中看看相同的程序使用autoboxing重寫後的樣子。
Listing 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)
元數據特徵志於使開發者們藉助廠商提供的工具可以進行更簡易的開發。看一看Listing E.中的代碼。
Listing E
import org.yyy.hr;
public interface EmployeeI extends Java.rmi.Remote {
public String getName() throwsJava.rmi.RemoteException;
public String getLocation () throwsJava.rmi.RemoteException;
}
public class EmployeeImpl implements EmployeeI {
public String getName(){
}
public String getLocation (){
}
}
通過元數據的支持,你可以改寫Listing 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,這個類提供大量創建連接池的靜態方法,是必須掌握的。
一、固定大小的線程池
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
/**
* Java線程:線程池
*/
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
二、單任務線程池
//創建一個使用單個 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
對於以上兩種連接池,大小都是固定的,當要加入的池的線程(或者任務)超過池最大尺寸時候,則入此線程池需要排隊等待。
一旦池中有線程完畢,則排隊等待的某個線程會入池執行。
三、可變尺寸的線程池
與上面的類似,只是改動下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
四、延遲連接池
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* Java線程:線程池
*/
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
五、單任務延遲連接池
在四代碼基礎上,做改動
//創建一個單線程執行程序,它可安排在給定延遲後運行命令或者定期地執行。
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
六、自定義線程池
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Java線程:線程池-自定義線程池
*/
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
在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後的代碼更具可讀性和健壯性。
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> {
……;
}
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編譯器對此行產生警告
}
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>的子類。
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類的數組。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實例。
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新特性
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新特性
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語句本身申請更多資源,
這些資源作用於try代碼塊,並自動關閉。
這個:
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
} finally {
br.close();
}
變成了這個:
try (BufferedReader br = new BufferedReader(new FileReader(path)) {
return br.readLine();
}
你可以定義關閉多個資源:
try (
InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dest))
{
// code
}
爲了支持這個行爲,所有可關閉的類將被修改爲可以實現一個Closable(可關閉的)接口。
3 增強的對通用實例創建(diamond)的類型推斷
類型推斷是一個特殊的煩惱,下面的代碼:
Map<String, List<String>> anagrams = new HashMap<String, List<String>>();
通過類型推斷後變成:
Map<String, List<String>> anagrams = new HashMap<>();
這個<>被叫做diamond(鑽石)運算符,這個運算符從引用的聲明中推斷類型。
4 數字字面量下劃線支持
很長的數字可讀性不好,在Java 7中可以使用下劃線分隔長int以及long了,如:
int one_million = 1_000_000;
運算時先去除下劃線,如:1_1 * 10 = 110,120 – 1_0 = 110
5 switch中使用string
以前你在switch中只能使用number或enum。現在你可以使用string了:
String s = ...
switch(s) {
case "quux":
processQuux(s);
// fall-through
case "foo":
case "bar":
processFooOrBar(s); break;
case "baz":
processBaz(s);
// fall-through
default:
processDefault(s); break;
}
6 二進制字面量
由於繼承C語言,Java代碼在傳統上迫使程序員只能使用十進制,八進制或十六進制來表示數(numbers)。
由於很少的域是以bit導向的,這種限制可能導致錯誤。你現在可以使用0b前綴創建二進制字面量:
int binary = 0b1001_1001;
現在,你可以使用二進制字面量這種表示方式,並且使用非常簡短的代碼,可將二進制字符轉換爲數據類型,如在byte或short。
byte aByte = (byte)0b001;
short aShort = (short)0b010;
7 簡化的可變參數調用
當程序員試圖使用一個不可具體化的可變參數並調用一個*varargs* (可變)方法時,編輯器會生成一個“非安全操作”的警告。
JDK 7將警告從call轉移到了方法聲明(methord declaration)的過程中。這樣API設計者就可以使用vararg,因爲警告的數量大大減少了。