首頁 > Java > java教程 > 主體

Java中的泛型詳解

高洛峰
發布: 2017-01-18 10:54:11
原創
1365 人瀏覽過

所謂泛型:就是允許在定義類別、介面指定類型形參,這個類型形參在將在宣告變數、建立物件時決定(即傳入實際的型別參數,也可稱為型別實參)

泛型類別或介面

「菱形」語法

//定义
 
public interface List<E> extends Collection<E>  
 
public class HashMap<K,V> extends AbstractMap<K,V>  implements Map<K,V>, Cloneable, Serializable 
//使用
 
List<String> list = new ArrayList();
 
//Java7以后可以省略后面尖括号的类型参数
 
List<String> list = new ArrayList<>();
登入後複製

從泛型類派生子類

//方式1
 
public class App extends GenericType<String>
 
//方式2
 
public class App<T> extends GenericType<T>
 
//方式3
 
public class App extends GenericType
登入後複製

偽泛型

不存在真正的泛型類,泛型類對Java虛擬機來說是透明的. JVM並不知道泛型類別的存在,換句話說,JVM處理泛型類別和普通類別沒什麼區別的.因此在靜態方法、靜態初始化區塊、靜態變數裡面不允許使用類型形參。
- 以下方式都是錯誤的

private static T data;
 
static{
 
    T f;
 
}
 
public static void func(){
 
    T name = 1;
 
}
登入後複製

下面的例子可以從側面驗證不存在泛型類

public static void main(String[] args){
 
        List<String> a1 = new ArrayList<>();
        List<Integer> a2 = new ArrayList<>();  
    System.out.println(a1.getClass() == a2.getClass());
 
    System.out.println(a1.getClass());
 
    System.out.println(a2.getClass());
 
}
登入後複製

輸出

true
 
class java.util.ArrayList
 
class java.util.ArrayList
登入後複製

類型通配符

首先必須明確,假如FooListBar是一點的父類,但< Foo>並不是List的父類別.為了表示各種泛型的父類別,Java使用"?"來表示泛型通配.即List來表示各種泛型List的父類別.帶這種通配符List泛型不能設定(set)元素,只能取得(get)元素。因為程式無法確定List中的類型,所以不能新增物件。但取得的物件肯定是Object類型。

以下方法會編譯出錯:

List<?> list = new ArrayList<>();
 
list.add(new Object());
登入後複製

主意幾點:

1.List物件不能被當成List物件使用,也就是說:List類別並不是List類別的子類別。

2.陣列和泛型有所不同:假設Foo是Bar的一個子型別(子類別或子介面),那麼Foo[]依然是Bar[]的子型別;但G不是G的子類型。

3.為了表示各種泛型List的父類,我們需要使用類型通配符,類型通配符是一個問號(?),將一個問號作為類型實參傳給List集合,寫作:List(意思是未知類型元素的List)。這個問號(?)被稱為通配符,它​​的元素類型可以匹配任何類型。

通配符的上限

List表示所有SuperType泛型List的父類或本身。有通配符上限的泛型不能有set方法,只能有get方法。

設定通配符上限能解決如下問題:Dog是Animal子類,有個getSize方法要獲取傳入List的個數,代碼如下

abstract class Animal {
    public abstract void run();
}
class Dog extends Animal {
    public void run() {
        System.out.println("Dog run");
    }
}
public class App {
    public static void getSize(List<Animal> list) {
        System.out.println(list.size());
    }
    public static void main(String[] args) {
        List<Dog> list = new ArrayList<>();
        getSize(list); // 这里编译报错
    }
}
登入後複製

這裡編程出錯的原因是List並不是List的父類。解一可以把getSize方法中形參List改為List,不過這樣的話在每次get物件的時候都要強制型別轉換,比較麻煩。使用通配符上限很好的解決了這個問題,可以把List改為List,編譯就不會錯了,也不用型別轉換。


通配符的下限

List表示SubType泛型List的下限。有通配符上限的泛型不能有get方法,只能有set方法。

泛型方法

如果定義類別、介面是沒有使用型別形參,但定義方法時想自己定義型別形參,這也是可以的,JDK1.5也提供了泛型方法的支援。泛型方法的方法簽名比普通方法的方法簽名多了類型形參聲明,類型形參聲明以尖括號括起來,多個類型形參之間以逗號(,)隔開,所有類型形參聲明放在方法修飾符與方法傳回值型別之間.語法格式如下:

修饰符 返回值类型 方法名(类形列表){
 
//方法体
 
}
登入後複製

泛型方法允許型別形參被用來表示方法的一個或多個參數之間的型別依賴關係,或是方法傳回值與參數之間的類型依賴關係。如果沒有這樣的類型依賴關係,就不應該使用泛型方法。 Collections的copy方法就使用泛型方法:

 public static <T> void copy(List<? super T> dest, List<? extends T> src){ ...}
登入後複製

這個方法要求src型別必須是dest型別的子類別或本身。

擦除和轉換

在嚴格的泛型程式碼裡,帶有泛型聲明的類別總是應該帶著型別參數。但為了與舊的Java程式碼保持一致,也允許在使用帶有泛型聲明的類別時不指定類型參數。如果沒有為這個泛型類別指定型別參數,則該型別參數被稱為一個raw type(原始型別),預設是該宣告該參數時指定的第一個上限型別。

當把一個具有泛型資訊的物件賦給另一個沒有泛型資訊的變數時,則所有在尖括號之間的類型資訊都被丟掉了。比如說一個List型別轉換為List,則該List對集合元素的型別檢查變成了成型變數的上限(即Object),這種情況被為擦除。

範例

class Apple<T extends Number>
 
{
 
 T size;
 
 public Apple()
 
 {
 
 }
 
 public Apple(T size)
 
 {
 
  this.size = size;
 
 }
 
 public void setSize(T size)
 
 {
 
  this.size = size;
 
 }
 
 public T getSize()
 
 {
 
  return this.size;
 
 }
 
}
 
public class ErasureTest
 
{
 
 public static void main(String[] args)
 
 {
 
  Apple<Integer> a = new Apple<>(6);    // ①
 
  // a的getSize方法返回Integer对象
 
  Integer as = a.getSize();
 
  // 把a对象赋给Apple变量,丢失尖括号里的类型信息
 
  Apple b = a;      // ②
 
  // b只知道size的类型是Number
 
  Number size1 = b.getSize();
 
  // 下面代码引起编译错误
 
  Integer size2 = b.getSize();  // ③
 
 }
 
}
登入後複製

更多Java中的泛型詳解相關文章請關注PHP中文網!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!