Java之JDK 1.5新特性

---------------------- ASP.Net+Android+IOS開發.Net培訓、期待與您交流! ----------------------

下面簡略的說說jdk1.5的新特性。

泛型(Generics--爲集合(collections)提供編譯時類型安全,無需每刻從Collections取得一個對象就進行強制轉換(cast

增強的“for”循環(Enhanced For loop--減少迭代器(iterator)的潛在錯誤(error-proneness

自動置入/自動取出(Autoboxing/unboxing--無需在基本類型(primitive types)(例如double)和包裝類型(wrapper types)(例如Double)之間人工地進行轉換。

類型安全的枚舉(Typesafeenums--提供類型安全枚舉模式的各項好處。

靜態導入(Static import--無需在使用其他類的靜態成員變量前綴其類名.這將使得代碼更爲簡潔。

元數據(Metadata--使編程人員避免編寫樣板化代碼(boiler plate code),並提供機會進行宣告式程式設計(declarative programming)


  讓我們詳細討論每個新特性,並看一些例子。

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

  staticbooleancheckName(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

  staticbooleancheckName(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語句實現相同方法:

  voidprintAll(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;

  }

  }

  有一件事值得注意的:在先前,如果你取出(unboxNull值,它將變爲0。在次代碼中,編譯器將自動地轉換Integerint然後加上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),像這樣:

  importorg.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

  importorg.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中的代碼爲:

  importorg.yyy.hr;

  public class Employee {

  @Remote public String getName() {

  ...

  }

  @Remote public public String getLocation() {

  ...

  }

  }


JDK1.5新特性之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.5Collection類庫的大部分類都被改進爲Generic類。以下是從JDK1.5源碼中

截取的關於ListIterator接口定義的代碼片斷:

public interface List<E> {
void add(E x);
Iterator<E> iterator;
}

public interface Iterator<E> {
E next();
boolean hasNext();
}

List爲例,“public interface List<E>”中的EList的類型參數,用戶在使用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類的靜態字段和靜態方法的定義中不能使用TT只能出現在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>”是不是有點出人意料?直觀上看,就像StringObject的子類,因此‘Object o = String”’合法一樣,存放StringList是存放ObjectList的子類,因此第二行應該是合法的。反過來分析,如果第二行是合法的,那麼如下會導致運行時異常的代碼也是合法的:

lo.add(new Object); //會導致在ls中添加了非String對象

String s = ls.get(0); //ls.get(0)返回的實際上只是一個Object實例,會導致ClassCastException

編譯器顯然不允許此種情形發生,因此不允許“List<Object> lo = ls”編譯通過。

因此,直觀上的“存放StringList是存放ObjectList的子類”是錯誤的。更一般的說,設FooBar的子類,GGeneric類型聲明,G<Foo>不是G<Bar>的子類。

參數化的Generic類和數組
我們知道,如果TS的子類,則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>代表類型參數值爲BaseClassBaseClass某個子類的任一參數化Collection。對於Collection<? extends BaseClass>的實例c,因爲c中要存放的對象具體類型不確定,不能往其加入非null對象,但從c中取出的對象都統一作爲BaseClass實例。事實上,你可以把Collection<?>看作Collection<? extends Object>的簡潔形式。

另一種情形:如果SubClass代表任一個類的類名,那麼Collection<? super SubClass>代表類型參數值爲SubClassSubClass某個祖先類的任一參數化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方法,還是採用類型參數通配符方式呢?一般而言,如果參數類型間或參數類型與返回值類型間存在某種依賴關係,則採取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) {

…….

}

}

  

 

我的總結:如果有人能說出更多的新特徵,那就更好了。我相信裏面有更多的很酷的新東西。新技術的產生往往會給我們的開發帶來很多方便,不要害怕使用新技術。

---------------------- ASP.Net+Android+IOS開發.Net培訓、期待與您交流! ----------------------

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章