------- android教育訓練、java教育訓練、期待與您交流! ----------
Java 泛型(Generircs)
1.介紹
泛型是JDK 1.5的新特性,泛型的本質是參數化類型,也就是說所操作的資料類型被指定為一個參數。這種參數類型可以用在類、接口和方法的建立中,分别稱為泛型類、泛型接口、泛型方法。
引入泛型的好處是安全簡單。在JDK 1.5之前,沒有泛型的情況的下,通過對類型Object的引用來實作參數的“任意化”,“任意化”帶來的缺點是要做顯式的強制類型轉換,而這種轉換是要求開發者對實際參數類型可以預知的情況下進行的。對于強制類型轉換錯誤的情況,編譯器可能不提示錯誤,在運作的時候才出現異常,這是一個安全隐患。泛型的好處是在編譯的時候檢查類型安全,并且所有的強制轉換都是自動和隐式的,提高代碼的重用率。
1.1 定義簡單的泛型
首先來看看List和Iterator中定義的泛型,
public interface Iterator<E> {
boolean hasNext();
E next();
void remove();
}
public interface List<E> extends Collection<E> {
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
boolean add(E e);
E set(int index, E element);
}
首先定義一個不使用泛型的Box類,
//不使用泛型
public class Box {
private Object object;
public void add(Object object) {
this.object = object;
}
public Object get() {
return object;
}
}
public class BoxDemo1 {
public static void main(String[] args) {
// ONLY place Integer objects into this box!
Box integerBox = new Box();
integerBox.add(new Integer(10));
Integer someInteger = (Integer)integerBox.get();
System.out.println(someInteger);
}
}
如果我們在add方法中添加一個字元串類型的參數會出現怎樣的結果?
public class BoxDemo1 {
public static void main(String[] args) {
// ONLY place Integer objects into this box!
Box integerBox = new Box();
//integerBox.add(new Integer(10));
/*
* Imagine this is one part of a large application modified by one programmer.
* Note how the type is now String.
*/
integerBox.add("10");//String
Integer someInteger = (Integer)integerBox.get();
System.out.println(someInteger);
}
}
輸出運作結果:
---------- java ----------
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
at Demo.main(Demo.java:21)
可以看出此時的結果會出現類型轉換常,雖然add方法的參數是object類型,把一個數字10作為一個字元串傳進去是可以的,但是結果傳回時把Object轉換成了Integer類型。
使用泛型定義Box類,如下:
/**
* Generic version of the Box class.
* @param <T> the type of value being boxed
*/
public class Box<T> {
// T stands for "Type"
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
}
現在把T換成Integer類型,Box<Integer>
public class BoxDemo3 {
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
integerBox.add(new Integer(10));
// 不用強制轉換
Integer someInteger = integerBox.get();
System.out.println(someInteger);
}
}
這裡指定了參數類型是Integer,那麼我們在建立這個Box對象的時候也指定了一個類型參數,這樣就取代了類型之間的強制轉換。這裡有個很大的不同是,編譯器現在能夠在編譯時檢查程式的正确性。當我們說integerBox被聲明為Box<Integer>類型,這就告訴我們無論何時何地使用integerBox變量,編譯器保證其中的元素的正确的類型。實際上,這樣增加了不少可讀性和穩定性。
2.深入泛型
泛型的主要好處就是讓編譯器保留參數的類型資訊,執行類型檢查,執行類型轉換(casting)操作,編譯器保證了這些類型轉換(casting)的絕對無誤。如下面代碼:
/******* 不使用泛型類型 *******/
List list1 = new ArrayList();
list1.add(100); //編譯器不檢查值
String str1 = (String)list1.get(0); //需手動強制轉換,如轉換類型與原資料類型不一緻将抛出ClassCastException異常
/******* 使用泛型類型 *******/
List<String> list2 = new ArrayList<String>();
list2.add("value"); //[類型安全的寫入資料] 編譯器檢查該值,該值必須是String類型才能通過編譯
String str2 = list2.get(0); //[類型安全的讀取資料] 不需要手動轉換
2.1 類型擦除
Java中的泛型隻存在于編譯期,在将Java源檔案編譯完成Java位元組代碼中是不包含泛型中的類型資訊的。使用泛型的時候加上的類型參數,會被編譯器在編譯的時候去掉。這個過程就稱為類型擦除(type erasure)。
List<String> list1 = new ArrayList<String>();
List<Integer> list2 = new ArrayList<Integer>();
System.out.println(list1.getClass() == list2.getClass()); // 輸出結果: true
System.out.println(list1.getClass().getName()); // 輸出結果: java.util.ArrayList
System.out.println(list2.getClass().getName()); // 輸出結果: java.util.ArrayList
在以上代碼中list1,list2分别定義為 List<String> 和 List<Integer> 等類型,在編譯之後都會變成 List,而由泛型附加的類型資訊對 JVM 來說是不可見的,是以list1,list2的class對象相同。class對象的名字都是java.util.ArrayList,這都說明 List<String> 和 List<Integer> 的對象使用的都是同一份位元組碼,運作期間并不存在泛型。
public class GenericsApp {
public void method(List<String> list){
//
}
/*
* 編譯出錯,這兩個方法不屬于重載,由于類型的擦除,使得這兩個方法的參數清單的參數均為List類型,
* 這就相當于同一個方法被聲明了兩次,編譯自然無法通過了
*
public void method(List<Integer> list){
}
*/
}
我可以用javap指令反編譯位元組碼來檢視method方法的參數清單的參數類型。
從圖中可以看出,經反編譯後的源碼中 method 方法的參數變成了 List 類型,說明泛型的類型被擦除了,位元組碼檔案中不存在泛型,也就是說,運作期間泛型并不存在,它在編譯完成之後就已經被擦除了。
2.2 泛型類型的繼承規則
在使用泛型類時,需要了解一些有關繼承和子類型的準則。泛型類型跟其是否是泛型類型的子類型沒有任何關系。
考慮一個類和一個子類,有如下兩個Employee類和Manager類,
class Employee
{
private String name;
private double salary;
private Date hireDay;//入職日期
/**
@param n the employee's name
@param s the salary
@param year the hire year
@param month the hire month
@param day the hire day
*/
public Employee(String n, double s, int year, int month, int day)
{
name = n;
salary = s;
GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day);
hireDay = calendar.getTime();
}
public String getName()
{
return name;
}
public double getSalary()
{
return salary;
}
public Date getHireDay()
{
return hireDay;
}
public void raiseSalary(double byPercent)
{
double raise = salary * byPercent / 100;
salary += raise;
}
}
class Manager extends Employee
{
private double bonus;
/**
@param n the employee's name
@param s the salary
@param year the hire year
@param month the hire month
@param day the hire day
*/
public Manager(String n, double s, int year, int month, int day)
{
super(n, s, year, month, day);
bonus = 0;
}
public double getSalary()
{
double baseSalary = super.getSalary();
return baseSalary + bonus;
}
public void setBonus(double b)
{
bonus = b;
}
public double getBonus()
{
return bonus;
}
}
Pair<Employee>是Pair<Manager>的一個子類嗎?NO!!或許我們會感到奇怪,如下面代碼将不能編譯成功:
class Pair<T>
{
private T first;
private T second;
public Pair(){
first = null;
second = null;
}
public Pair(T first, T second){
this.first = first;
this.second = second;
}
public void setFirst(T newValue){first = newValue;}
public void setSecond(T newValue){second = newValue;}
public T getFirst(){return first;}
public T getSecond(){return second ;}
}
class ArrayAlg
{
public static <T> Pair<T> makePair(Class<T> c1){
try{
// Pair<String> p = Pair.makePair(String.class);
return new Pair<T>(c1.newInstance(),c1.newInstance());
}catch(Exception ex){
return null;
}
}
/**
* 計算泛型數組的最大值和最小值
*/
public static <T extends Comparable> Pair<T> minmax(T[] a){
if(a ==null || a.length==0) return null;
T min =a[0];
T max =a[0];
for(int i=1;i<a.length;i++){
if(min.compareTo(a[i])>0) min =a[i];
if(max.compareTo(a[i])<0) max =a[i];
}
return new Pair<T>(min,max);
}
/**
* 變量類型的限定
* T 限制為實作了Comparable接口,(隻含有一個方法compareTo)的類。T extends Comparable
*/
public static <T extends Comparable> T min(T[] a){
if(a == null || a.length==0)
return null;
T smallest = a[0];
for(int i =1;i<a.length;i++){
if(smallest.compareTo(a[i])>0)
smallest = a[i];
}
return smallest;
}
}
// 不能編譯通過
Manager[] honchos = null;
Pair<Employee> result = ArrayAlg.minmax(honchos);// Error
minmax方法傳回Pair<Manager>,而不是Pair<Employee>,并且這樣的指派時不合法的。無論S和T有什麼關系,通常,Pair<S>和Pair<T>沒有聲明聯系。如下圖:
假設與許将Pair<Manager>轉換為Pair<Employee>,考慮一下代碼:
Pair<Manager> managerBuddies = new Pair<Manager>(ceo,cfo);
Pair<Employee> employeeBuddies = managerBuddies;
顯然, managerBuddies和employeeBuddies引用了相同對象,現在将CFO和一個普通員工組成一對,這對于Pair<Manager>來說應該是不可能的。
注意:必須要注意泛型和Java數組之間的重要差別,可以将Manager[]數組賦給一個類型Employee[]的變量:
Manager[] managerBuddies = {ceo,cfo};
Employee[] employeeBuddies = managerBuddies; //OK
然而,數組帶有特别的保護,如果試圖将一個低級别的雇員存儲到emplouyeeBuddies[0]中,虛拟機将會抛出ArrayStoreException異常。記住永遠可以将參數化類型轉換為一個原始類型。例如,Pair<Manager>是原始類型Pair的一個子類型。
Manager ceo = new Manager("Gus Greedy", 800000, 2003, 12, 15);
Manager cfo = new Manager("Sid Sneaky", 600000, 2003, 12, 15);
Pair<Manager> buddies = new Pair<Manager>(ceo, cfo);
Pair rawBuddies = buddies ; //轉換到原始類型
rawBuddies.setFirst(...);
轉換成原始類型之後會産生類型錯誤嗎?會!!,rawBuddies.setFirst(new File("...")),當使用getFisrt獲得外來對象并賦給Manager變量時,與通常一樣,會抛出ClassCastException異常。這裡失去的隻是泛型程式設計提供的附加安全性。
最後,泛型類可以擴充或實作其他的泛型類,就這一點而言,與普通的類沒有什麼差別。例如,ArrayList<T>類實作List<T>接口,這意味着,一個ArrayList<Manager>可以被轉換為一個List<Manager>。但是,如前面所見,一個ArrayList<Manager>不是一個ArrayList<Employee>或List<Employee>。如下圖所示:
2.3 泛型中的通配符(?)
在使用泛型類的時候,既可以指定一個具體的類型,如List<String>就聲明了具體的類型是String;也可以用通配符?來表示未知類型,如List<?>就聲明了List中包含的元素類型是未知的。通配符所代表的其實是一組類型,但具體的類型是未知的。List<?>所聲明的就是所有類型都是可以的。但是List<?>并不等同于List<Object>。List<Object>實際上确定了List中包含的是Object及其子類,在使用的時候都可以通過Object來進行引用。而List<?>則其中所包含的元素類型是不确定。其中可能包含的是String,也可能是Integer。如果它包含了String的話,往裡面添加Integer類型的元素就是錯誤的。正因為類型未知,就不能通過new ArrayList<?>()的方法來建立一個新的ArrayList對象。因為編譯器無法知道具體的類型是什麼。但是對于List<?>中的元素确總是可以用Object來引用的,因為雖然類型未知,但肯定是Object及其子類。考慮下面的代碼:
public void wildcard(List<?> list) {
list.add(1);//編譯錯誤
}
試圖對一個帶通配符的泛型類進行操作的時候,總是會出現編譯錯誤。其原因在于通配符所表示的類型是未知的。
通配符類型Pair<? extends Employee>,表示任何泛型Pair類型,它的參數是Employee的子類,如Pair<Manager>,但不是Pair<String>。下面有個列印雇員的方法,
public static void printBuddies(Pair<Employee> p) {
Employee first = p.getFirst();
Employee second = p.getSecond();
System.out.println(first.getName() + " and " + second.getName()+ " are buddies.");
}
這裡不能将Pair<Manager>傳遞給這個方法,這點很受限制,解決方法就是使用通配符類型:
public static void printBuddies(Pair<? extends Employee> p) {//}
類型Pair<Manager>是Pair<? extends Employee>的子類型。
使用通配符通過Pair<? extends Employee>的引用會破環Pair<Manager>嗎?
Pair<Manager> managerBuddies = new Pair<Manager>(ceo,cfo);
Pair<? extends Employee> wildcardBuddies = managerBuddies; // 引用完全OK,
//wildcardBuddies.setFirst(cko); //error
這個不會引起破壞。但是setFirst()方法的調用有一個類型錯誤,"The method setFirst(capture#1-of ? extends Employee) in the type Pair<capture#1-of ? extends Employee> is not applicable for the arguments (Manager)",在方法中無法捕獲到Manager可用的參數。
仔細分析類型Pair<? extends Employee>,其方法似乎是這樣的:
[? extends Employee] getFirst()
void setFirst(? extends Employee)
這樣将不可以調用setFirst方法。編譯器隻知道需要某個Employee的子類型,但是不知道具體是什麼類型。它拒絕傳遞任何特定的類型。使用getFirst就不存在這個問題:将getFirst的傳回值賦給一個Employee的引用完全可以的。這就是引入有限定的通配符的關鍵之處,現在可以有辦法差別安全的通路器方法和不安全的更改器方法了。
帶有通配符超類型限定的通配符的行為與前面的通配符限定的行為恰好相反,可以為方法提供參數,但是不能使用傳回值。例如,Pair<? super Manager>有方法:
void setFirst(? super Manager)
? super Manager getFirst()
編譯器不知道setFirst方法的确切類型,但是可以用任意Manager對象調用它,而不能用Empolyee對象調用。如果調用getFirst,傳回的對象類型就不會得到保證,隻能把它賦給一個Object。
有一個方法把一個經理的數組,并且想把獎金最高和最低的經理放在同一個Pair對象中,這裡最合适的就是Pair類型就是Pair<Employee>,也可以是Pair<Object>。
/**
* 通配符? 上限
* 經理數組,把獎金最高和最低的經理放在一個Pair對象中。在這裡Pair是Pair<Employee>類型的。Pair<Object>也可以
* @param a
* @param result
*/
public static void minmaxBonus(Manager[] a, Pair<? super Manager> result) {
if (a == null || a.length == 0)
return;
Manager min = a[0];
Manager max = a[0];
for (int i = 1; i < a.length; i++) {
if (min.getBonus() > a[i].getBonus())
min = a[i];
if (max.getBonus() < a[i].getBonus())
max = a[i];
}
result.setFirst(min);
result.setSecond(max);
}
直覺地講,帶有超類型限定的通配符可以向泛型對象寫入,帶有子類型限定的通配符可以從泛型對象讀取。
下面是超類型限定的另一種應用。Comparable接口本身就是一個泛型類型,聲明如下:
public interface Comparable<T> {
public int compareTo(T o);
}
在此,類型變量訓示了 o參數的類型。例如,String類實作Comparable<String>,它的compareTo方法被聲明為:
public int compareTo(String anotherString)
顯式的參數又一個正确的類型,在JDK5.0之前anotherString是一個Object,并且這個方法的實作需要強制類型轉換。由于Comparable是一個泛型類型,也許可以把ArrayAlg類中的mimax方法重構的更好一些。
public static <T extends Comparable<T>> Pair<T> minmax(T[] a)
{
if (a == null || a.length == 0) return null;
T min = a[0];
T max = a[0];
for (int i = 1; i < a.length; i++)
{
if (min.compareTo(a[i]) > 0) min = a[i];
if (max.compareTo(a[i]) < 0) max = a[i];
}
return new Pair<T>(min, max);
}
這樣寫比隻使用 <T extends Complarable>更徹底,對許多類來講工作的更好。比如說求一個String數組的最小值,T就是String類型的,而String是Complarable<String>的子類型。但是處理一個GregorianCalendar對象數組時,就會出現問題。GregorianCalendar是Calendar的子類,并且Calendar實作了Complarable<Calendar>。
public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar> {
///實作了 Comparable<Calendar>接口
}
public class GregorianCalendar extends Calendar {
///
}
是以GregorianCalendar也是實作的是Comparable<Calendar>,而不是Comparable<GregorianCalendar>。這時就可以使用超類型限制通配符:
public static <T extends Comparable<? super T>> Pair<T> minmax(T[] a){
if (a == null || a.length == 0)
return null;
T min = a[0];
T max = a[0];
for (int i = 1; i < a.length; i++) {
if (min.compareTo(a[i]) > 0)
min = a[i];
if (max.compareTo(a[i]) < 0)
max = a[i];
}
return new Pair<T>(min, max);
}
而compareTo方法就可以寫成:
public int compareTo(? super T)
有可能被聲明為使用類型T的對象,也有可能使用T的超類型(例如,當T是GregorianCalendar)。無論如何,傳遞一個T類型的對象給compareTo方法都是安全的。
2.4 限定泛型的上下界
對于List<?>中的元素隻能用Object來引用,在有些情況下不是很友善。在這些情況下,可以使用上下界來限制未知類型的範圍。如List<? extends Number>說明List中可能包含的元素類型是Number及其子類。而List<? super Number>則說明List中包含的是Number及其父類。當引入了上界之後,在使用類型的時候就可以使用上界類中定義的方法。比如通路List<? extends Number>的時候,就可以使用Number類的intValue()等方法。
有限制的通配符(Bounded Wildcards)考慮一個簡單的畫圖程式:
public abstract class Shape {
public abstract void draw(Canvas c);
}
//圓形
public class Circle extends Shape {
private int x, y, radius;
public void draw(Canvas c) {
// ...
}
}
//矩形
public class Rectangle extends Shape {
private int x, y, width, height;
public void draw(Canvas c) {
// ...
}
}
這些類可以在一個畫布(Canvas)上被畫出來:
public class Canvas {
public void draw(Shape s) {
s.draw(this);
}
}
所有的圖形通常都有很多個形狀。假定它們用一個list來表示,Canvas裡有一個方法來畫出所有的形狀會比較友善:
public void drawAll(List<Shape> shapes) {
for (Shape s : shapes) {
s.draw(this);
}
}
現在,drawAll()方法所作的隻是從這個list讀取shape,是以它應該也能對List<Circle>調用。我們真正要的是這個方法能夠接受一個任意種類的shape:
public void drawAll(List<? extends Shape> shapes) { //..}
把類型 List<Shape> 替換成了 List<? extends Shape>。現在drawAll()可以接受任何Shape的子類的List,是以我們可以對List<Circle>進行調用。
List<? extends Shape>是有限制通配符的一個例子。這裡 "?" 代表一個未知的類型,就像我們前面看到的通配符一樣。但是,在這裡,我們知道這個未知的類型實際上是Shape的一個子類(它可以是Shape本身或者Shape的子類而不必是extends自Shape)。我們說Shape是這個通配符的上限(upper bound)。像平常一樣,要得到使用通配符的靈活性有些代價。這個代價是,現在向shapes中寫入是非法的對象。比如下面的代碼:
public void addRectangle(List<? extends Shape> shapes) {
shapes.add(0, new Rectangle()); // compile error!
}
上面的代碼是不允許的。因為shapes.add的第二個參數類型是? extends Shape ——一個Shape未知的子類。是以我們不知道這個類型是什麼,我們不知道它是不是Rectangle的父類;它可能是也可能不是一個父類,是以這裡傳遞一個Rectangle不安全。
注意:使用<?>或是<? extends SomeClass>的聲明方式,意味著您隻能通過該名稱來取得所參考執行個體的資訊,或者是移除某些資訊,但不能增加它的資訊,因為隻知道當中放置的是SomeClass的子類,但不确定是什麼類的執行個體,編譯器不讓您加入資訊,理由是,如果可以加入資訊的話,那麼您就得記得取回的執行個體是什麼類型,然後轉換為原來的類型方可進行操作,這就失去了使用泛型的意義。
2.5 無限定通配符和捕獲通配符
在泛型中,有一種無限制的通配符類型,比如Pair<?>,這裡的“?”就代表了對這個Pair中元素沒有任何限制或者根本不關心。在提供了友善性的同時它也有個很大的限制,那就是使用了無限制通配符類型的泛型Pair<?>,是不能添加任何元素進去的,除了null外。在類型Pair<?>有方法如:
? getFirst()
void setFirst(?)
getFirst的傳回值隻能賦給一個Object。setFirst方法不能被調用,甚至不能用Object調用。Pair<?>和Pair本質的不同在于:可以用任意Object對象調用原始的Pair類的setObject方法。
有時候這樣的弱類型泛型對于有些簡單的操作非常有用,例如,下面這個方法将用來測試一個Pair是否包含了指定的對象,它不需要實際的類型。
public static boolean hasNulls(Pair<?> p){
return p.getFirst() == null || p.getSecond() ==null;
}
編寫一個交換一個pair元素的方法:
public static void swap(Pair<?> p) {}
通配符不是類型變量,是以,不能在編寫代碼中使用“?”作為一個類型。也就是說,下面代碼是非法的:
? t = p.getFirst(); // Error
p.setFirst(p.getSecond());
p.setSecond(t);
這個問題因為在交換的時候必須臨時儲存第一個元素。這個問題可以用輔助方法swapHelper解決,
public static <T> void swapHelper(Pair<T> p) {
T t = p.getFirst();
p.setFirst(p.getSecond());
p.setSecond(t);
}
注意,swapHelper方法是一個泛型方法,而swap方法不是,它具有固定的Pair<?>類型的參數。現在可以由swap調用swapHelper方法:
public static void swap(Pair<?> p) {
swapHelper(p);
}
這種情況下,swapHelper方法的參數T捕獲通配符。它不知道是那種類型的通配符,但是,這是一個明确的類型,并且<T>swapHelper的定義隻有在T指出類型時才有明确的含義。當然并不是一定要使用通配符。可以直接實作沒有通配符的泛型方法<T> void swap(Pair<T> p)。
public static void maxminBonus(Manager[] a, Pair<? super Manager> result) {
minmaxBonus(a, result);
PairAlg.swapHelper(result); // OK--swapHelper captures wildcard type
}
在這裡,通配符捕獲機制是不可避免的。通配符捕獲隻有在許多限制的情況下才是合法的。編譯器必須能夠确信通配符表達的是單個、确定的類型。例如,ArrayList<Pair<T>>中的T永遠不能捕獲ArrayList<Pair<?>>中的通配符。數組清單可以儲存兩個Pair<?>,分别針對?的不同類型。
2.6 泛型接口
在JDK 5.0之後,不僅可以聲明泛型類,還可以聲明泛型接口,聲明泛型接口和聲明泛型類相似,直接在接口名稱後面加上<T>即可。定義一個泛型接口:
interface Info<T>
{
public T getValue();
}
泛型接口定義完成之後,就要實作這個泛型接口,定義泛型接口的子類有兩種方式:
a).一種是直接在子類後面聲明泛型。
b).一種是直接在子類實作的接口中明确指定泛型類型。
方式1,
class InfoImpl<T> implements Info<T>
{
private T value;
public InfoImpl(T v){
this.setValue(v);
}
public void setValue(T v){
this.value = v;
}
public T getValue(){
return this.value;
}
}
此程式泛型接口的子類聲明了與接口中相同的泛型标志,使用以上子類的方法與前的類似,使用泛型接口的子類如下:
public class Demo
{
public static void main(String[] args){
Info<String> i = null;
i = new InfoImpl<String>("zhangsan");
System.out.println("value is "+i.getValue());
}
}
使用方式2,
class InfoImpl implements Info<String>//指定類型為String
{
private String value;
public InfoImpl(String v){
this.setValue(v);
}
public void setValue(String v){
this.value = v;
}
public String getValue(){
return this.value;
}
}
此方式在子類實作接口時,直接在實作的接口處指定了具體的泛型類型String,這樣在覆寫Info接口中的getValue()方法時直接指明類型為String即可。使用此泛型接口的子類:
public class Demo
{
public static void main(String[] args){
Info<String> i = null;
i = new InfoImpl("zhangsan");
System.out.println("value is "+i.getValue());
}
}
此時,在程式執行個體化子類對象時,不用再指定泛型,因為在聲明子類時已經明确地指定了具體類型。
2.7 泛型方法及泛型類執行個體
在前面所有的所有的泛型操作都是将整個類進行泛型化,但同樣也可以在類中定義泛型化的方法。泛型方法的定義與其所在的類是否是泛型類是沒有任何關系的,所在的類可以是泛型類,也可以不是泛型類。在泛型方法中可以定義泛型參數,此時,參數的類型就是傳入的資料類型。泛型方法定義方式如下:
調用泛型方法,
說明一下,定義泛型方法時,必須在傳回值前邊加一個<T>,來聲明這是一個泛型方法,持有一個泛型T,然後才可以用泛型T作為方法的傳回值。Class<T>的作用就是指明泛型的具體類型,而Class<T>類型的變量c,可以用來建立泛型類的對象。既然是泛型方法,就代表着我們不知道具體的類型是什麼,也不知道構造方法如何,是以沒有辦法去new一個對象,但可以利用變量c的newInstance方法去建立對象,也就是利用反射建立對象。
定義一個泛型方法:
class GenericsMethodDemo
{
public <T> T fun(T t){ //可以接收任意類型的資料
return t;
}
}
public class Demo
{
public static void main(String[] args){
GenericsMethodDemo gmd = new GenericsMethodDemo();
String str = gmd.fun("zhangsan"); //接收字元串
int i = gmd.fun(20); //接收整型
System.out.println(str);
System.out.println(i);
}
}
輸出運作結果:
---------- java ----------
zhangsan
20
以上程式中的fun()方法是将接收的參數直接傳回,而且在方法接收參數中使用了泛型操作,是以此方法可以接收任意類型的資料,而且此方法的傳回值類型将由泛型決定。
如果可以通過泛型方法傳回一個泛型類的執行個體化對象,則必須在方法的傳回類型聲明處明确地指出泛型标志。
通過泛型方法傳回傳回泛型類執行個體:
class Info<T extends Number>
{ //此處泛型 T 隻能是數字類型
private T var;
public T getVar(){
return var;
}
public void setVar(T var){
this.var=var;
}
public String toString(){
return this.var.toString();
}
}
public class Demo
{
/**
* 泛型方法
* @param <T extends Number> 聲明一個泛型 T,T隻能是Number類型或子類型
* @return Info<T>該方法的傳回值的類型
*/
public static <T extends Number> Info<T> fun(T params){
// 根據傳遞的資料類型執行個體化Info對象
Info<T> temp = new Info<T>();
temp.setVar(params);
//傳回執行個體化對象
return temp;
}
public static void main(String[] args){
//方法中傳入或傳回的泛型類型由調用方法時所設定的參數類型決定
Info<Integer> i = fun(20);
Info<Double> di = fun(22.22);
System.out.println(i);
System.out.println(di);
}
}
為什麼要使用泛型方法呢?因為泛型類要在執行個體化的時候就指明類型,如果想換一種類型,不得不重新new一次,可能不夠靈活;而泛型方法可以在調用的時候指明類型,更加靈活。
2.8 泛型建立數組
泛型數組 隻能作為參數類型或者方法參數。在Java中,Object[]數組可以是任何數組的父類,或者說,任何一個數組都可以向上轉型成父類的數組,這個時候如果我們往裡面放不同于原始資料類型 但是滿足後來使用的父類類型的話,編譯不會有問題,但是在運作時會檢查加入數組的對象的類型,于是會抛ArrayStoreException:
String[] strArray = new String[20];
Object[] objArray = strArray;
objArray[0] = new Integer(1); // throws ArrayStoreException at runtime
(a)、參數類型
1. 泛型list的數組,形如:ArrayList<T>[]
ArrayList<T>[] list = new ArrayList<T>[n];
for(int i = 0; i < n; i++){
list[i] = new ArrayList<T>;
}
(b)、泛型數組的集合,形如:ArrayList<T[n]>
import java.lang.reflect.Array;
//...
ArrayList<T[n]> lst = new ArrayList<T[n]>;
lst.add((T[])Array.newInstance(type,size));
Type類型為Class<T>,需要調用者指定,size為要開辟的數組長度;另外,具體建立數組中的參數,也需要用type來指定。
T[] t = lst.get(0);
for (int i = 0; i < size; i++)
t[] = type.newInstance();
在Java中,不能通過直接通過T[] tarr=new T[10]的方式來建立數組,最簡單的方式便是通過Array.newInstance(Class<t>type,int size)的方式來建立數組例如下面的程式
public class ArrayMarker<T> {
private Class<T> type;
public ArrayMarker(Class<T> type) {
this.type = type;
}
//數組
T[] createArray(int size){
return (T[]) Array.newInstance(type, size);
}
//集合
List<T> createList(){
return new ArrayList<T>();
}
public static void main(String[] args) {
ArrayMarker<Type> am2 = new ArrayMarker<Type>(Type.class);
System.out.println(Arrays.asList(am2.createArray(10)));
System.out.println(Arrays.asList(am2.createList()));
}
}
class Type{
public String toString(){
return "type";
}
}
程式輸出結果:
[null, null, null, null, null, null, null, null, null, null]
[[]]
根據數組類型或普通類型的class建立數組:
/**
* 根據普通類型的class建立數組
* @param <T> 目标類型
* @param clazz
* @param length 數組長度
* @return
*/
public static <T> T[] newArrayByClass(Class<T> clazz, int length) {
return (T[]) Array.newInstance(clazz, length);
}
/**
* 根據數組類型的class建立對應類型的數組
* @param <T> 目标類型
* @param clazz
* @param length 數組長度
* @return
*/
public static <T> T[] newArrayByArrayClass(Class<T[]> clazz, int length) {
return (T[]) Array.newInstance(clazz.getComponentType(), length);
}
上面的這個例子比較簡單,但是如果你有接觸過泛型數組,你便對他的複雜度有一定的了解,由于建立泛型數組比較複雜,是以在實際的應用過程中一般會選擇List的對泛型進行存儲,如果實在需要使用泛型數組,則需要注意數組的在運作時的類型,think in java這本書中,對泛型數組的處理通過四個小程式對其進行了比較完整的描述:
程式一:這個程式主要說明了,在使用泛型數組中容易出現的問題,由于書中對于程式的說明比較詳細,是以隻對程式做引用
public class ArrayofGenric {
public static void main(String[] args) {
Genric<Integer>[] genArr;
//genArr = (Genric<Integer>[]) new Object[]{};
//genArr = new Genric<Integer>[2];
genArr = (Genric<Integer>[]) new Genric[2];
System.out.println(genArr);
}
}
class Genric<T> {
}
程式二:這個程式主要是說明在程式的執行過程中,泛型數組的類型資訊會被擦除,且在運作的過程中數組的類型有且僅有Object[],如果我們強制轉換成T[]類型的話,雖然在編譯的時候不會有異常産生,但是運作時會有ClassCastException抛出
public class ArrayofGenric2<T> {
private T[] ts;
public ArrayofGenric2(int size) {
ts = (T[]) new Object[size];
}
public T[] rep() {
return ts;
}
public T get(int index) {
return ts[index];
}
public void set(int index, T t) {
ts[index] = t;
}
public static void main(String[] args) {
ArrayofGenric2<String> aog2 = new ArrayofGenric2<String>(10);
Object[] objs = aog2.rep();
System.out.println(objs);
// will throw ClassCastException
// String[] strs = aog2.rep();
// System.out.println(objs);
}
}
程式三:主要說明在對象中通過用Object[]來儲存資料,則生成對象是,可以對其持有的對象在T和object之間進行轉換,但是當設計到數組的轉換時,還是會報ClassCastException
public class ArrayofGenric3<T> {
Object[] ts;
public ArrayofGenric3(int size) {
ts = new Object[size];
}
public T[] rep() {
return (T[]) ts;
}
public T get(int index) {
return (T) ts[index];
}
public void set(int index, T t) {
ts[index] = t;
}
public static void main(String[] args) {
ArrayofGenric3<Integer> aog3 = new ArrayofGenric3<Integer>(10);
Object[] objs = aog3.rep();
for (int i = 0; i < 10; i++) {
aog3.set(i, i);
System.out.println(aog3.get(i));
}
//ClassCastException
//Integer[] strs = aog3.rep();
//System.out.println(strs);
}
}
程式四:是對泛型數組相對而言比較完美的解決方案
public class ArrayofGenric4<T> {
T[] ts;
public ArrayofGenric4(Class<T> type, int size) {
// TODO Auto-generated constructor stub
ts = (T[]) Array.newInstance(type, size);
}
public T[] rep() {
return (T[]) ts;
}
public T get(int index) {
return (T) ts[index];
}
public void set(int index, T t) {
ts[index] = t;
}
public static void main(String[] args) {
ArrayofGenric4<Integer> aog4 = new ArrayofGenric4<Integer>(Integer.class, 10);
Object[] objs = aog4.rep();
for (int i = 0; i < 10; i++) {
aog4.set(i, i);
System.out.println(aog4.get(i));
}
try {
Integer[] strs = aog4.rep();
System.out.println("User Array.newInstancer to create generic of array was successful!!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.9 反射與泛型
從JDK5.0起,Class類是泛型的。例如,String.class實際上是一個Class<String>類的對象(唯一的對象)。類型參數非常有用,它允許Class<T>方法的傳回類型更具有針對性。下面Class<T>中的方法就是使用了類型參數:
public T newInstance()
public T cast(java.lang.Object)
public T[] getEnumConstants()
public static java.lang.Class<?> forName(java.lang.String, boolean, java.lang.ClassLoader)
public static java.lang.Class<?> forName(java.lang.String)
public <U> java.lang.Class<? extends U> asSubclass(java.lang.Class<U>)
public java.lang.Class<?>[] getClasses()
public java.lang.reflect.Constructor<?>[] getConstructors()
public java.lang.Class<?>[] getDeclaredClasses()
public java.lang.reflect.Constructor<?>[] getDeclaredConstructors()
public java.lang.reflect.Type getGenericSuperclass()
public java.lang.String getSimpleName()
public java.lang.reflect.TypeVariable<java.lang.Class<T>>[] getTypeParameters()
有時比對方法中的Class<T>參數的類型變量是很有價值的。下面有個應用示例:
public static <T> Pair<T> makePair(Class<T> c) throws Exception{
return new Pair<T>(c.newInstance(),c.newInstance());
}
如果調用makePair(Employee.class),Employee.class就是類型Class<Employee>的一個對象。makePair方法的類型參數T同Employee比對,并且編譯器可以推斷出這個方法傳回一個Pair<Employee>。
為了表達泛型類型聲明,JDK5.0在java.lang.reflect包中提供了一個新的接口Type。這個接口包含下列子類型:
Class 類,描述具體類型。
TypeVariable 接口,描述類型變量(如T extends Comparable<? super T>)。
WildcardType 接口,描述通配符(如 ? super T)。
ParameterizedType 接口,描述泛型類或接口類型(如Complarable<? super T>)。
GenericArrayType 接口,描述泛型數組(如T[])。
java.lang.Class 類,提供了一個 getGenericSuperclass() 方法來擷取直接超類的泛型類型。
public class Base<T> {
private Class<T> entityClass;
//泛型的 實際類型參數 的類全名
public String getEntityName(){
return entityClass.getName();
}
//泛型的 實際類型參數 的類名
public String getEntitySimpleName(){
return entityClass.getSimpleName();
}
//泛型的 實際類型參數 的Class
public Class<T> getEntityClass(){
return entityClass;
}
{
//entityClass = (Class<T>) ((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];
try {
//獲得實際運作的類的Class
Class<?> clazz = getClass();
//獲得實際運作的類的直接超類的泛型類型
Type type = clazz.getGenericSuperclass();
//如果該泛型類型是參數化類型
if (type instanceof ParameterizedType) {
//擷取泛型類型的實際類型參數集
Type[] parameterizType = ((ParameterizedType)type).getActualTypeArguments();
//取出第一個(下标為0)參數的值
entityClass = (Class<T>) parameterizType[0];
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
(Class<T>) parameterizedType[0],怎麼就知道第一個參數(parameterizedType[0])就是該泛型的實際類型呢?很簡單,因為 Base<T> 的泛型的類型參數清單中隻有一個參數,是以,第一個元素就是泛型 T 的實際參數類型。
public static void main(String[] args) {
System.out.println("用getGenericSuperclass()方法來擷取直接超類的泛型類型.");
Base<String> base = new Base<String>();
System.out.println(base.getEntityClass());
//System.out.println(base.getEntityName());//NullPointerException
//System.out.println(base.getEntitySimpleName());//NullPointerException
}
從列印的結果來看,Base 類并不能直接來使用,為什麼會這樣?原因很簡單,由于 Base 類中的 clazz.getGenericSuperclass() 方法,傳回表示此 Class 所表示的實體(類、接口、基本類型或 void)的直接超類的 Type。如果超類是參數化類型,則傳回的 Type 對象必須準确反映源代碼中所使用的實際類型參數。如果此 Class 表示 Object 類、接口、基本類型或 void,則傳回 null。如果此對象表示一個數組類,則傳回表示 Object 類的 Class 對象。是以Base 類不能夠直接來使用,而是應該通過其子類來使用,Base 應該用來作為一個基類,我們要用的是它的具體的子類。
public class Children extends Base<Children>{
}
Children child = new Children();
System.out.println(child.getEntityClass());
System.out.println(child.getEntityName());
System.out.println(child.getEntitySimpleName());
程式輸出結果:
class Children
Children
Children
泛型反射的關鍵是擷取 ParameterizedType 接口,再調用 ParameterizedType 接口中的 getActualTypeArguments() 方法就可獲得實際綁定的類型。由于去參數化(擦拭法),也隻有在 超類(調用 getGenericSuperclass 方法) 或者成員變量(調用 getGenericType 方法)或者方法(調用 getGenericParameterTypes 方法)像這些有方法傳回 ParameterizedType 類型的時候才能反射成功。
下面将變量和方法的兩種反射獲得泛型的實際類型參數說下。因為泛型編譯後會去參數化(擦拭法),是以無法直接用反射擷取泛型的參數類型,可以把泛型用做一個方法的參數類型,方法可以保留參數的相關資訊,這樣就可以用反射先擷取方法的資訊,然後再進一步擷取泛型參數的相關資訊,這樣就得到了泛型的實際參數類型。通過方法,反射獲得泛型的實際類型參數:
public void applyCollection(Collection<Number> collection){
//聲明一個空方法,并将泛型用作方法的參數類型
}
public static void main(String[] args) {
System.out.print("通過方法,反射獲得泛型的實際類型參數:");
try {
Class<?> clazz = Test.class;
Method method = clazz.getDeclaredMethod("applyCollection", Collection.class);//獲得方法
Type[] type = method.getGenericParameterTypes();//獲得泛型類型參數集
ParameterizedType ptype = (ParameterizedType) type[0];//将其轉成參數化類型,因為在方法中泛型是參數,且Number是第一個類型參數
type = ptype.getActualTypeArguments();//獲得參數的實際類型
System.out.println(type[0]);//取出第一個元素
} catch (Exception e) {
e.printStackTrace();
}
}
程式輸出結果:
通過方法,反射獲得泛型的實際類型參數:
class java.lang.Number
通過字段變量,反射獲得泛型的實際類型參數:
private Map<String, Number> collection;
public static void main(String[] args) {
System.out.println("通過字段變量,反射獲得泛型的實際類型參數:");
try {
Class<?> clazz = Test.class;
Field field = clazz.getDeclaredField("collection");//獲得字段變量
Type type = field.getGenericType();//獲得泛型的類型
ParameterizedType ptype = (ParameterizedType) type;//轉換成參數化類型
System.out.println(ptype.getActualTypeArguments()[0]);//取出第一個參數的實際類型
System.out.println(ptype.getActualTypeArguments()[1]);//取出第二個參數的實際類型
} catch (Exception e) {
e.printStackTrace();
}
}
程式輸出結果:
通過字段變量,反射獲得泛型的實際類型參數:
class java.lang.String
class java.lang.Number
------- android教育訓練、java教育訓練、期待與您交流! ----------