JDK1.5新特性
目錄
1.之前已經學習過的JDK1.5新特性
2.靜態導入 StaticImport
3.可變參數 ...
4.高級for循環
5.枚舉
6.泛型 Generic
7.注解
注:本章全部為重點內容。
####################################################################################
####################################################################################
1.之前已經學習過的JDK1.5新特性
(1)StringBuilder
(2)Lock
(3)Condition
(4)自動裝箱與拆箱
2.靜態導入 StaticImport
import 語句可以導入一個類或某個包中的所有類
import static 語句導入一個類中的某個靜態成員或所有靜態成員
注意:
a.當類中方法都是靜態方法(如工具類)可以使用靜態導入
b.當類名重名時,需要指定具體的包名。當方法重名時,指定具備所屬的對象或者類。
?? ?
代碼示例
1 import java.util.*;//導入了Util包中所有的類 2 import static java.util.Arrays.*;//導入leArrays這個類中的所有靜態成員。 3 import static java.util.Collections.sort;//導入了Collections類中的sort方法。 4 import static java.lang.System.out;//導入了System類中的out。 5 class StaticImport 6 { 7 public static void main(String[] args) 8 { 9 out.println("haha");//省略了System. 10 int[] arr = {3,1,5}; 11 sort(arr); 12 out.println(Arrays.toString(arr)); 13 //這里的Arrays不能省略,因為該類默認繼承了Object類,而Object類也有toString方法,當方法重名時,指定具備所屬的類 14 ArrayList al = new ArrayList(); 15 al.add(1); 16 al.add(3); 17 al.add(2); 18 out.println(al); 19 sort(al); 20 out.println(al); 21 } 22 }
?
3.可變參數 ...
函數的另一種表現形式
?? ?返回值類型?? 函數名(參數類型 ... 形式參數)
?? ??? ?{
?? ??? ??? ?執行語句;
?? ??? ?}
其實接收的是一個數組,可以指定實際參數個數。
注意:
a.可變參數只能出現在參數列表的最后;
b. ...位于變量類型和變量名之間,前后有無空格都可以;
c.調用可變參數的方法時,編譯器為該可變參數隱含創建一個數組,在方法體中以數組的形式訪問可變參數。
當方法接收得參數個數不固定時,如何實現:
//方法一 重載
1 class ParamMethodDemo 2 { 3 public static void main(String[] args) 4 { 5 //show(3,4); 6 7 public static void show(int a,int b) 8 { 9 //執行語句 10 } 11 public static void show(int a,int b,int c) 12 { 13 //執行語句 14 } 15 }
?
//方法二 數組 雖然少定義了多個方法。但是每次都要定義一個數組。作為實際參數。
1 class ParamMethodDemo 2 { 3 public static void main(String[] args) 4 { 5 int[] arr = {3,4}; 6 show(arr); 7 8 int[] arr1 = {2,3,4,5}; 9 show(arr1); 10 11 public static void show(int[] arr) 12 { 13 //執行語句 14 } 15 }
?
//方法三 可變參數...
//數組參數的簡寫形式。不用每一次都手動的建立數組對象。只要將要操作的元素作為參數傳遞即可。隱式的將這些參數封裝成了數組。
1 class ParamMethodDemo 2 { 3 public static void main(String[] args) 4 { 5 show("haha",2,3,4,5,6); 6 //show();//傳了一個0長度的數組 7 8 } 9 //public static void show(int... arr,String str)//編譯失敗,可變參數要在參數列表最后面 10 public static void show(String str,int... arr) 11 { 12 //執行語句 13 } 14 }
4.高級for循環
(1)格式:
?? ?for(數據類型 變量名 : 被遍歷的集合(Collection)或者數組)
?? ?{
?? ?}
(2)特點:
凡是支持迭代器的集合也都支持高級 for 循環,它是迭代器的簡寫格式.有泛型時可以指定數據類型,沒有泛型時要用 Object 來接受任意類型的數據。
注意事項:
迭代變量必須在( )中定義!
集合變量可以是數組或實現了Iterable接口的集合類
(3)高級for循環和迭代器的區別:
對集合進行遍歷。只能獲取集合元素。但是不能對集合進行操作。迭代器除了遍歷,還可以進行remove集合中元素的動作。如果是用ListIterator,還可以在遍歷過程中對集合進行增刪改查的動作。
(4)傳統for和高級for的區別
高級for有一個局限性。必須有被遍歷的目標。建議在遍歷數組的時候,還是希望是用傳統for。因為傳統for可以定義角標。
(5)代碼示例
對于數組
?? ??? ?int[] arr = {3,5,1};
?? ??? ?for(int i : arr)
?? ??? ?{
?? ??? ??? ?System.out.println("i:"+i);
?? ??? ?}
對于Collection集合
?? ??? ?ArrayList<String> al = new ArrayList<String>();
?? ??? ?al.add("abc1");
?? ??? ?al.add("abc2");
?? ??? ?al.add("abc3");
?? ??? ?for(String s : al)
?? ??? ?{
?? ??? ??? ?System.out.println(s);
?? ??? ?}
問題?? ?判斷打印結果
?? ?ArrayList<String> al = new ArrayList<String>();
?? ?al.add("abc1");
?? ?al.add("abc2");
?? ?al.add("abc3");
?? ?for(String s : al)
?? ?{
?? ??? ?s = "kk";
?? ?}
?? ?System.out.println(al);
?? ?s只是指向了集合中的元素,在遍歷過程中被打印。當s指向其它對象時,集合中的元素沒有變化,所以打印集合結果也不會變化,為原集合。
?? ?
對于Mpa集合
?? ?keySet方法
?? ??? ?Set<Integer> keySet = hm.keySet();
?? ??? ?for(Integer i : keySet)
?? ?簡寫格式
?? ??? ?for(Integer i : hm.keySet())
?? ?entrySet方法
?? ??? ?Set<Map.Entry<Integer,String>> entrySet = hm.entrySet();
?? ??? ?for(Map.Entry<Integer,String> me : entrySet)
?? ?簡寫格式
?? ??? ?for(Map.Entry<Integer,String> me : hm.entrySet())
5.枚舉
(1)為什么要有枚舉
假設用1-7分別表示星期一到星期日,但有人可能會寫成int weekday = 0;或即使使用常量方式也無法阻止意外。枚舉就是要讓某個類型的變量的取值只能為若干個固定值中的一個,否則,編譯器就會報錯。枚舉可以讓編譯器在編譯時就可以控制源程序中填寫的非法值,普通變量的方式在開發階段無法實現這一目標。
(2)普通類與枚舉
定義一個Weekday的普通類來模擬枚舉功能。
1 public abstract class WeekDay { 2 private WeekDay(){} 3 public static final WeekDay SUN=new WeekDay(){ 4 public WeekDay nextDay(){ 5 return MON; 6 } 7 }; 8 public static final WeekDay MON=new WeekDay(){ 9 public WeekDay nextDay(){ 10 return SUN; 11 } 12 }; 13 public abstract WeekDay nextDay(); 14 public String toString(){ 15 return this==SUN?"SUN":"MON"; 16 } 17 }
?
枚舉的做法
1 enum WeekDay{ 2 SUN{ 3 public WeekDay nextDay(){ 4 return MON; 5 } 6 }, 7 MON{ 8 public WeekDay nextDay(){ 9 return SUN; 10 } 11 }; 12 13 public abstract WeekDay nextDay(); 14 }
?
注意:
a.枚舉是一種特殊的類,其中的每個元素都是該類的一個實例對象
b.枚舉也可以定義構造方法、成員變量、普通方法和抽象方法。但構造方法必須定義成私有的,
c.枚舉元素必須位于枚舉體中的最開始部分,枚舉元素列表的后要有分號與其他成員分隔。把枚舉中的成員方法或變量等放在枚舉元素的前面,編譯器會報告錯誤。
d.枚舉類的實例對象個數是有限的,就是那些成員,當枚舉只有一個成員時,就可以作為一種單例的實現方式。
6.泛型 Generic
用于解決安全問題,是一個類型安全機制。
(1)泛型概述
泛型作用:
沒有使用泛型時,只要是對象,不管是什么類型的對象,都可以存儲進同一個集合中。使用泛型集合,可以將一個集合中的元素限定為一個特定類型,集合中只能存儲同一個類型的對象,這樣更安全;并且當從集合獲取一個對象時,編譯器也可以知道這個對象的類型,不需要對對象進行強制類型轉換,這樣更方便。
泛型的好處
1)提高了程序的安全性
2)將運行期遇到的問題轉移到了編譯期
3)省去了類型強轉的麻煩
4)泛型類的出現優化了程序設計
泛型術語:
ArrayList<E>類定義和ArrayList<Integer>類引用中涉及如下術語:
整個ArrayList<E>稱為泛型類型
ArrayList<E>中的E稱為類型變量或類型參數
整個ArrayList<Integer>稱為參數化的類型
ArrayList<Integer>中的Integer稱為類型參數的實例或實際類型參數
ArrayList<Integer>中的<>念typeof
ArrayList稱為原始類型
參數化類型與原始類型的兼容性:
參數化類型可以引用一個原始類型的對象,編譯報告警告,
原始類型可以引用一個參數化類型的對象,編譯報告警告,
注意:
編譯器不允許創建泛型變量的數組。即在創建數組實例時,數組的元素不能使用參數化的類型,
(2)泛型的定義
泛型既可以定義在類上,也可以定義在方法上,包括靜態方法和非靜態方法,還可以定義在接口上和異常中。
1)泛型定義在類上
如果類的實例對象中的多處都要用到同一個泛型參數,即這些地方引用的泛型類型要保持同一個實際類型時,這時候就要采用泛型類型的方式進行定義,也就是類級別的泛型,泛型類定義的泛型,在整個類中有效。如果被方法使用,那么泛型類的對象明確要操作的具體類型后,所有要操作的類型就已經固定了。語法格式如下:
?? ?public class GenericDao<T> {
?? ??? ?private T field1;
?? ??? ?public void save(T obj){}
?? ??? ?public T getById(int id){}
?? ?}
代碼示例
???
1 //泛型被定義在類上 2 class Demo<T> 3 { 4 public void show(T t) 5 { 6 System.out.println("show:"+t); 7 } 8 public void print(T t) 9 { 10 System.out.println("print:"+t); 11 } 12 } 13 class GenericDemo 14 { 15 public static void main(String[] args) 16 { 17 Demo<Integer> d = new Demo<Integer>(); 18 19 d.show(new Integer(4)); 20 d.print(3); 21 //編譯通過,泛型定義在類上,明確操作類型后,所有方法都只能操作該類型 22 23 Demo<String> d1 = new Demo<String>(); 24 d1.print("haha"); 25 d1.show(5); 26 //編譯失敗,已經明確數據為String,show方法卻操作了Intager 27 28 } 29 }
?
2)泛型定義在方法上
為了讓不同方法可以操作不同類型,而且類型還不確定。那么可以將泛型定義在方法上。用于放置泛型的類型參數的尖括號應出現在方法的其他所有修飾符之后和在方法的返回類型之前,也就是緊鄰返回值之前。按照慣例,類型參數通常用單個大寫字母表示。如交換數組中的兩個元素的位置的泛型方法語法定義如下:
static <E> void swap(E[] a, int i, int j) {
?? ?E t = a[i];
?? ?a[i] = a[j];
?? ?a[j] = t;
}
注意:
只有引用類型才能作為泛型的實際參數,swap(new int[3],3.5);語句會報告編譯錯誤,這是因為new int[3]本身已經是對象了,編譯器不會對new int[3]中的int自動拆箱和裝箱了。
代碼示例
???
1 class Demo 2 { 3 public <T> void show(T t) 4 { 5 System.out.println("show:"+t); 6 } 7 public <T> void print(T t)//兩個方法中的T沒有關系。因為泛型只在方法內有效 8 { 9 System.out.println("show:"+t); 10 } 11 } 12 class GenericDemo 13 { 14 public static void main(String[] args) 15 { 16 Demo d = new Demo(); 17 d.show("haha"); 18 d.show(new Integer(4)); 19 d.print("heihei"); 20 //編譯通過,泛型定義在方法上,只在方法中有效,彼此不影響 21 22 } 23 }
3)泛型既定義在類上,又定義在方法上
1 class Demo<T> 2 { 3 public void show(T t) 4 { 5 System.out.println("show:"+t); 6 } 7 public <Q> void print(Q q) 8 { 9 System.out.println("print:"+q); 10 } 11 } 12 class GenericDemo 13 { 14 public static void main(String[] args) 15 { 16 Demo <String> d = new Demo<String>(); 17 d.show("haha"); 18 //d.show(4);//編譯失敗,show方法只能調用已經明確的操作類型 19 d.print(5); 20 d.print("hehe"); 21 //編譯通過,泛型定義在print方法上,可以操作不同類型。 22 } 23 }
?
4)泛型定義在靜態方法上
靜態方法不可以訪問類上定義的泛型。因為泛型定義在類上時,操作類型是在對象建立時明確的,只有對象帶著類型在運行。而靜態方法方法存在時對象還不存在,所以不能訪問類上定義的泛型。如果靜態方法操作的應用數據類型不確定,可以將泛型定義在方法上。
代碼示例
?? ?class Demo<T>
?? ?{
?? ??? ?public? static <W> void method(W t)
?? ??? ?{
?? ??? ??? ?System.out.println("method:"+t);
?? ??? ?}
?? ?}
?? ?class GenericDemo
?? ?{
?? ??? ?public static void main(String[] args)
?? ??? ?{
?? ??? ??? ?Demo.method("hahahahha");
?? ??? ?}
?? ?}
5)泛型定義在接口上
?? ?interface Inter<T>
?? ?{
?? ??? ?void show(T t);
?? ?}
?? ?//當實現時已經確定操作類型
?? ?class InterImpl implements Inter<String>
?? ?{
?? ??? ?public void show(String t)
?? ??? ?{
?? ??? ??? ?System.out.println("show :"+t);
?? ??? ?}
?? ?}
?? ?class GenericDemo
?? ?{
?? ??? ?public static void main(String[] args)
?? ??? ?{
?? ??? ??? ?InterImpl i = new InterImpl();
?? ??? ??? ?i.show("haha");
?? ??? ?}
?? ?}
?? ?//當實現時不確定操作類型
?? ?class InterImpl<T> implements Inter<T>
?? ?{
?? ??? ?public void show(T t)
?? ??? ?{
?? ??? ??? ?System.out.println("show :"+t);
?? ??? ?}
?? ?}
?? ?class GenericDemo5
?? ?{
?? ??? ?public static void main(String[] args)
?? ??? ?{
?? ??? ??? ?InterImpl<Integer> i = new InterImpl<Integer>();
?? ??? ??? ?i.show(4);
?? ??? ?}
?? ?}
6)泛型定義在異常內
也可以用類型變量表示異常,稱為參數化的異常,可以用于方法的throws列表中,但是不能用于catch子句中。用下面的代碼說明對異常如何采用泛型:
private static <T extends Exception> sayHello() throws T
{
?? ?try{
?? ??? ?//被檢測的語句
?? ?}catch(Exception e){
?? ??? ?throw (T)e;
?? ?}
}
注意:
a.在對泛型類型進行參數化時,類型參數的實例必須是引用類型,不能是基本類型。
b.當一個變量被聲明為泛型時,只能被實例變量、方法和內部類調用,而不能被靜態變量和靜態方法調用。因為靜態成員是被所有參數化的類所共享的,所以靜態成員不應該有類級別的類型參數。
(3)泛型限定
? 通配符
當對象類型不確定時,可以用一個通配符來表示。也可以理解為占位符。使用?通配符可以引用其他各種參數化的類型,?主要用作引用,不能用它去給其他變量賦值;可以調用與參數化無關的方法,不能調用與參數化有關的方法。
? extends E: 可以接收E類型或者E的子類型。上限。
? super E: 可以接收E類型或者E的父類型。下限
注意:
限定通配符總是包括自己。
4)類型參數的類型推斷
編譯器判斷范型方法的實際類型參數的過程稱為類型推斷,類型推斷是相對于知覺推斷的,其實現方法是一種非常復雜的過程。根據調用泛型方法時實際傳遞的參數類型或返回值的類型來推斷,具體規則如下:
當某個類型變量只在整個參數列表中的所有參數和返回值中的一處被應用了,那么根據調用方法時該處的實際應用類型來確定,這很容易憑著感覺推斷出來,即直接根據調用方法時傳遞的參數類型或返回值來決定泛型參數的類型,例如:
?? ? swap(new String[5],3,4)????? static <E> void swap(E[] a, int i, int j)
當某個類型變量在整個參數列表中的所有參數和返回值中的多處被應用了,如果調用方法時這多處的實際應用類型都對應同一種類型來確定,這很容易憑著感覺推斷出來,例如:
?? ? add(3,5)??? static <T> T add(T a, T b)
當某個類型變量在整個參數列表中的所有參數和返回值中的多處被應用了,如果調用方法時這多處的實際應用類型對應到了不同的類型,且沒有使用返回值,這時候取多個參數中的最大交集類型,例如,下面語句實際對應的類型就是Number了,編譯沒問題,只是運行時出問題:
?? ? fill(new Integer[3],3.5f)??? static <T> void fill(T[] a, T v)
當某個類型變量在整個參數列表中的所有參數和返回值中的多處被應用了,如果調用方法時這多處的實際應用類型對應到了不同的類型, 并且使用返回值,這時候優先考慮返回值的類型,例如,下面語句實際對應的類型就是Integer了,編譯將報告錯誤,將變量x的類型改為float,對比eclipse報告的錯誤提示,接著再將變量x類型改為Number,則沒有了錯誤:
?? ? int x =(3,3.5f)??? static <T> T add(T a, T b)
參數類型的類型推斷具有傳遞性,下面第一種情況推斷實際參數類型為Object,編譯沒有問題,而第二種情況則根據參數化的Vector類實例將類型變量直接確定為String類型,編譯將出現問題:
?? ?copy(new Integer[5],new String[5])? static <T> void copy(T[] a,T[]? b);
?? ?copy(new Vector<String>(), new Integer[5])? static <T> void copy(Collection<T> a , T[] b);
5)擴展閱讀:
泛型是提供給javac編譯器使用的,可以限定集合中的輸入類型,讓編譯器擋住源程序中的非法輸入,編譯器編譯帶類型說明的集合時會去除掉“類型”信息,使程序運行效率不受影響,對于參數化的泛型類型,getClass()方法的返回值和原始類型完全一樣。由于編譯生成的字節碼會去掉泛型的類型信息,只要能跳過編譯器,就可以往某個泛型集合中加入其它類型的數據,例如,用反射得到集合,再調用其add方法即可。
Java中的泛型類型(或者泛型)類似于 C++ 中的模板。但是這種相似性僅限于表面,Java 語言中的泛型基本上完全是在編譯器中實現,用于編譯器執行類型檢查和類型推斷,然后生成普通的非泛型的字節碼,這種實現技術稱為擦除(erasure)(編譯器使用泛型類型信息保證類型安全,然后在生成字節碼之前將其清除)。這是因為擴展虛擬機指令集來支持泛型被認為是無法接受的,這會為 Java 廠商升級其 JVM 造成難以逾越的障礙。所以,java的泛型采用了可以完全在編譯器中實現的擦除方法。
7.注解 Annotation
(1)概述
1)什么是注解
對于過時的語句,java會提示過時了,通過@SuppressWarnings("Deprecation")可以在DOS命令行中取消提示,但Eclipse無法取消。這就是注解,相當于標記。編譯器、開發工具、javac通過反射獲得注解里的內容,進而明確應該做什么、不應該做什么。注解可以加在包、類、屬性、方法、參數及局部變量之上。一個注解就是一個類。
2)java.lang包中的注解
@SuppressWarnings 取消警告,保留到SOURCE階段
@Deprecated 標識已過時,保留到RUNTIME階段
@Override 覆蓋父類方法,保留到SOURCE階段
(2)元注解
元注解有2個:Rentention和Target。對注解類的注解,可以理解為注解類的屬性。
1)Rentention 注解類
注解的生命周期由Rentention的3種取值決定,Rentention的值是枚舉RententionPolicy的值,只有3個:SOURCE、CLASS、RUNTIME。
RententionPolicy.SOURCE對應Java源文件,RententionPolicy.CLASS(默認值)對應class文件、RententionPolicy.RUNTIME對應內存中的字節碼。
2)Target注解類
性質和Rentention一樣,都是注解類的屬性,表示注解類應該在什么位置,對那一塊的數據有效。例如,@Target(ElementType.METHOD)。Target內部的值使用枚舉ElementType表示,表示的主要位置有:注解、構造方法、屬性、局部變量、函數、包、參數和類(默認值)。多個位置使用數組,例如,@Target({ElementType.METHOD,ElementType.TYPE})。
?
(3)注解屬性
屬性,給注解提供更加詳細的信息。注解相當于接口,屬性相當于方法。例如,@ItcastAnnotation(color="black"),給屬性賦值,取值時類似調用方法,例如System.out.println(annotation.color()); 所有的屬性必須全部出現,除非有缺省值。
1)為屬性指定缺省值:
String color() default "yellow";
2)value屬性:
String value() default "bbb";
當只設置value屬性時,入股其他屬性都采用默認值或者只有一個value屬性,那么可以省略value=部分,例如:@MyAnnotation("aaa")。
3)數組類型的屬性
int[] arr() default {3,7,5};,MyAnnotation(arr={3,7,6}) 如果數組只有1個元素,可以不加{}。@Target({ElementType.METHOD,ElementType.TYPE}) 也是數組類型的屬性。
4)枚舉類型的屬性
EnumTest.TrafficLamp lamp() ;
@MyAnnotation(lamp=EnumTest.TrafficLamp.GREEN)
5)注解類型的屬性
將一個注解類作為屬性加入到另一個注解類中。
MetaAnnotation annotationAttr() default @MetaAnnotation("xxxx");
@MyAnnotation(annotationAttr=@MetaAnnotation(“yyy”) )