一文徹底搞懂Java泛型中的PECS原則(在坑里躺了多年終于爬出來了)
1.什么是泛型毁渗?
泛型,就我的理解就是類型參數(shù)化
一個栗子
List arrayList = new ArrayList();
arrayList.add("aaaa");
arrayList.add(100);
for(int i = 0; i< arrayList.size();i++){
String item = (String)arrayList.get(i);
System.out.println("泛型測試---item = " + item);
}
毫無疑問灸异,程序的運(yùn)行結(jié)果會以崩潰結(jié)束:
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
ArrayList可以存放任意類型肺樟,例子中添加了一個String類型,添加了一個Integer類型疟暖,再使用時都以String的方式使用田柔,因此程序崩潰了。為了解決類似這樣的問題(在編譯階段就可以解決)欣舵,泛型應(yīng)運(yùn)而生摆屯。
我們將第一行聲明初始化list的代碼更改一下,編譯器會在編譯階段就能夠幫我們發(fā)現(xiàn)類似這樣的問題准验。
List<String> arrayList = new ArrayList<String>();
...
//arrayList.add(100); 在編譯階段廷没,編譯器就會報(bào)錯
2.泛型特性
通過下面的例子可以證明颠黎,在編譯之后程序會采取去泛型化的措施滞项。也就是說Java中的泛型文判,只在編譯階段有效室梅。在編譯過程中,正確檢驗(yàn)泛型結(jié)果后赏殃,會將泛型的相關(guān)信息擦出仁热,并且在對象進(jìn)入和離開方法的邊界處添加類型檢查和類型轉(zhuǎn)換的方法勾哩。也就是說,泛型信息不會進(jìn)入到運(yùn)行時階段物蝙。
對此總結(jié)成一句話:泛型類型在邏輯上看以看成是多個不同的類型敢艰,實(shí)際上都是相同的基本類型钠导。
List<String> stringList=new ArrayList<>();
List<Integer> integerList=new ArrayList<>();
if(stringList.getClass().equals(integerList.getClass())){
//如果輸出亂碼,需要將文件設(shè)置成GB2312編碼格式
System.out.println("類型相同--->證明了泛型只是在編譯階段有效,編譯成字節(jié)碼就
無效了");
}
3.泛型的具體使用場景
3.1泛型類
/****
*一個普通的java泛型類
* 此處T可以隨便寫為任意標(biāo)識票堵,常見的如T悴势、E措伐、K、V等形式的參數(shù)常用于表示泛型
在實(shí)例化泛型類時捧存,必須指定T的具體類型
* @param <T>
*/
public class Generics<T> {
//key這個成員變量的類型為T,T的類型由外部指定
private T key;
public Generics(T key) {
this.key = key;
}
public T getKey() {
return key;
}
}
Generics<Integer> integerGenerics=new Generics<>(123456);
Generics<String> stringGenerics=new Generics<>("one_two_three_four_five_six");
System.out.println("key is "+integerGenerics.getKey());
System.out.println("key is "+stringGenerics.getKey());
注意:定義的泛型類昔穴,就一定要傳入泛型類型實(shí)參么?并不是這樣泳唠,在使用泛型的時候如果傳入泛型實(shí)參警检,則會根據(jù)傳入的泛型實(shí)參做相應(yīng)的限制害淤,此時泛型才會起到本應(yīng)起到的限制作用窥摄。如果不傳入泛型類型實(shí)參的話础淤,在泛型類中使用泛型的方法或成員變量定義的類型可以為任何的類型。
Generics generics1=new Generics(111111);
Generics generics2=new Generics("one_two_three_four_five_six");
Generics generics3=new Generics(false);
Generics generics4=new Generics(11.00f);
System.out.println("key is "+generics1.getKey());
System.out.println("key is "+generics2.getKey());
System.out.println("key is "+generics3.getKey());
System.out.println("key is "+generics4.getKey());
3.2泛型接口
泛型接口與泛型類的定義及使用基本相同币砂。泛型接口常被用在各種類的生產(chǎn)器(構(gòu)造方法)中决摧,可以看一個例子:
//定義一個泛型接口
public interface Generator<T> {
public T next();
}
當(dāng)實(shí)現(xiàn)泛型接口的類凑兰,未傳入泛型實(shí)參時:
/**
* 未傳入泛型實(shí)參時姑食,與泛型類的定義相同,在聲明類的時候则拷,需將泛型的聲明也一起加到類中
* 即:class FruitGenerator<T> implements Generator<T>{
* 如果不聲明泛型曹鸠,如:class FruitGenerator implements Generator<T>,編譯器會報(bào)錯:"Unknown class"
*/
class FruitGenerator<T> implements Generator<T>{
@Override
public T next() {
return null;
}
}
當(dāng)實(shí)現(xiàn)泛型接口的類宣旱,傳入泛型實(shí)參時:
/**
* 傳入泛型實(shí)參時:
* 定義一個生產(chǎn)器實(shí)現(xiàn)這個接口,雖然我們只創(chuàng)建了一個泛型接口Generator<T>
* 但是我們可以為T傳入無數(shù)個實(shí)參浑吟,形成無數(shù)種類型的Generator接口笙纤。
* 在實(shí)現(xiàn)類實(shí)現(xiàn)泛型接口時省容,如已將泛型類型傳入實(shí)參類型腥椒,則所有使用泛型的地方都要替換成傳入的實(shí)參類型
* 即:Generator<T>候衍,public T next();中的的T都要替換成傳入的String類型。
*/
public class FruitGenerator implements Generator<String> {
private String[] fruits = new String[]{"Apple", "Banana", "Pear"};
@Override
public String next() {
Random rand = new Random();
return fruits[rand.nextInt(3)];
}
}
3.3泛型方法
在java中,泛型類的定義非常簡單,但是泛型方法就比較復(fù)雜了像啼,泛型類邀杏,是在實(shí)例化類的時候指明泛型的具體類型响逢;
泛型方法棕孙,是在調(diào)用方法的時候指明泛型的具體類型 ,必須通過<T,K,E>的形式聲明。
常見的泛型方法:
//T可以是MapListBean類或者是MapListBean的子類
public <T extends MapListBean> void fetchData(T url) {
}
public <T> T showKeyName(Generic<T> container){
System.out.println("container key :" + container.getKey());
//當(dāng)然這個例子舉的不太合適分歇,只是為了說明泛型方法的特性职抡。
T test = container.getKey();
return test;
}
public <T,K> K showKeyName(Generic<T> container){
}
這些不是泛型方法:
//這不是一個泛型方法,沒有聲明泛型E
public E setKey(E key){
this.key = keu
}
//這不是一個泛型方法误甚,沒有聲明泛型T
public T getKey(){
return key;
}
//這也不是一個泛型方法,這就是一個普通的方法擅威,只是使用了Generic<Number>這個泛型類做形參而已郊丛。
public void showKeyValue1(Generic<Number> obj){
Log.d("泛型測試","key value is " + obj.getKey());
}
/**
* 這個方法是有問題的,編譯器會為我們提示錯誤信息:"UnKnown class 'E' "
* 雖然我們聲明了<T>,也表明了這是一個可以處理泛型的類型的泛型方法导盅。
* 但是只聲明了泛型類型T揍瑟,并未聲明泛型類型E,因此編譯器并不知道該如何處理E這個類型滤馍。**/
public <T> T showKeyName(Generic<E> container){
}
泛型類中的方法:
public class GenericFruit {
class Fruit{
@Override
public String toString() {
return "fruit";
}
}
class Apple extends Fruit{
@Override
public String toString() {
return "apple";
}
}
class Person{
@Override
public String toString() {
return "Person";
}
}
class GenerateTest<T>{
public void show_1(T t){
System.out.println(t.toString());
}
//在泛型類中聲明了一個泛型方法巢株,使用泛型E熙涤,這種泛型E可以為任意類型。可以類型與T相同茸歧,也可以不同显沈。
//由于泛型方法在聲明的時候會聲明泛型<E>,因此即使在泛型類中并未聲明泛型涤浇,編譯器也能夠正確識別泛型方法中識別的泛型魔慷。
public <E> void show_3(E t){
System.out.println(t.toString());
}
//在泛型類中聲明了一個泛型方法,使用泛型T蜻展,注意這個T是一種全新的類型邀摆,可以與泛型類中聲明的T不是同一種類型。
public <T> void show_2(T t){
System.out.println(t.toString());
}
}
public static void main(String[] args) {
Apple apple = new Apple();
Person person = new Person();
GenerateTest<Fruit> generateTest = new GenerateTest<Fruit>();
//apple是Fruit的子類施逾,所以這里可以
generateTest.show_1(apple);
//編譯器會報(bào)錯汉额,因?yàn)榉盒皖愋蛯?shí)參指定的是Fruit,而傳入的實(shí)參類是Person
//generateTest.show_1(person);
//使用這兩個方法都可以成功
generateTest.show_2(apple);
generateTest.show_2(person);
//使用這兩個方法也都可以成功
generateTest.show_3(apple);
generateTest.show_3(person);
}
}
3.4 通配符
- 通配符
public void showKeyValue1(Generic<Float> obj){
Log.d("泛型測試","key value is " + obj.getKey());
}
Generics<Integer> integerG=new Generics<>(345);
Generics<Float> floatG=new Generics<>(678f);
看上面的例子:
showKeyValue(gInteger );//此時必定報(bào)錯cannot be applied to Generic<java.lang.Float>
所有通配符在這個時候派上用場了整葡,我們可以:
public void showKeyValue1(Generic<?> obj){
Log.d("泛型測試","key value is " + obj.getKey());
}
3.5.泛型中的PECS原則
3.5.1.PECS 是 Producer extends Consumer Super 的縮寫遭居, 生產(chǎn)者提供數(shù)據(jù)(get)旬渠,用extends ,消費(fèi)者使用數(shù)據(jù) (add)枪蘑,用super .
3.5.2.上邊界( ? extends T) 表示泛型的類型必須為T類型或者T的子類 【一般只用獲取元素】
為了更好的理解岳颇,我們先定義一些類
public class Fruit{}
public class Apple extends Fruit{}
public class Banana extends Fruit{}
例如:List<? extends Fruit>中 <? extends Fruit> 表示泛型的類型是Fruit類或者是Fruit的子類颅湘,我們給list賦值的時候,可以是new ArrayList<Fruit>(), new ArrayList<Apple>(),new ArrayList<Banana>().
private static List<? extends Fruit> getExtendsList(){
List<? extends Fruit> list;
list=new ArrayList<Apple>();
list=new ArrayList<Banana>();
list=new ArrayList<Fruit>();
return list;
}
List<? extends Fruit>允許我們將ArrayList<Fruit>(),ArrayList<Apple>(),ArrayList<Banana>()賦值給它瞻鹏。然而鹿寨,雖然我們可以從這個列表中獲取元素(因?yàn)槲覀冎浪鼈冎辽偈荈ruit類型),但是我們無法往列表中添加元素(除了null),因?yàn)榫幾g器不知道list的具體類型赫悄,可能是new ArrayList<Banana>().
public static void m1(List<? extends Fruit> list){
Fruit fruit=list.get(0);//安全的涩蜘,因?yàn)槲覀冎懒斜碇械臄?shù)據(jù)類型至少是Fruit類型
// list.add(new Apple());//錯誤熏纯,編譯器不允許,list可能是new ArrayList<Banana>()
}
3.5.3.下邊界( ? super T) 表示泛型的類型必須為T類型或者T的父類 【一般只用于添加元素误窖,因?yàn)楂@取的元素類型只能是Object類型,意義不大】
例如:List<? super Fruit> 表示泛型的類型必須是Fruit類型或者是Fruit的父類霹俺,我們給list賦值的時候,可以是new ArrayList<Fruit>(),new ArrayList<Object>().
private static List<? super Fruit> getSuperList(){
List<? super Fruit> list;
list=new ArrayList<Fruit>();
list=new ArrayList<Object>();
return list;
}
List<? super Fruit> 允許我們將ArrayList<Fruit>(),ArrayList<Object>()賦值給它愈魏,但是沒法獲取特定類型的元素培漏,只能獲取Object類型的元素胡本。
private static void m2(List<? super Fruit> list){ //list列表只能添加Fruit類型及其子類型
list.add(new Apple()); //可以,因?yàn)锳pple是Fruit類型
list.add(new Banana());//可以珊佣,因?yàn)锽anana是Fruit類型
Object item=list.get(0);//只能是Object,因?yàn)槲覀儾恢谰唧w的類型
}
4.深入理解泛型通配符
4.1.協(xié)變和逆變
- <? extends T> 是協(xié)變的披粟,這意味著如果Apple是Fruit的子類,那么List<Apple> 也是 List<? extends Fruit> 的子類
- <? super T> 是逆變的虫碉,這意味著如果Fruit是Apple的父類,那么List<Fruit>也是List<? super Apple>的父類须板。
4.2.泛型方法中的PECS (extends out super in )
public static <T> void addToList(List<? super T> list,T item){
list.add(item);
}
public static <T> void copyList(List<? extens T> source ,List<? super T> destination){
for(T item:source){
destination.add(item);
}
}
Kotlin中的協(xié)變(out)和逆變(in)
- 協(xié)變(out) 允許將T類型或者T類型的子類賦值給泛型
open class Animal {}
class Cat : Animal() {}
interface Container<out T>{
fun getItem():T
}
fun main(){
val catContainer:Container<Cat> =object:Container<Cat>{
override fun getItem():Cat{
return Cat();
}
}
val animalContainer:Container<Animal>=catContainer ;
val animal:Animal =animalContainer.getItem();
println(animal);
}
- 逆變 (in) 允許將T類型或者T類型的父類賦值給泛型
interface Processor<in T> {
fun process(item: T)
}
fun main() {
val animalProcessor: Processor<Animal> = object : Processor<Animal> {
override fun process(item: Animal) {
println("Processing animal: $item")
}
}
val catProcessor: Processor<Cat> = animalProcessor // 逆變
catProcessor.process(Cat())
}
總結(jié)
- List<? extends Fruit> list:表示泛型是Fruit或者Fruit的子類习瑰,一般用于獲取元素。
- List<? super Fruit> list: 表示泛型是Fruit或者是Fruit的父類柠横,一般用于添加元素课兄。
- List list :明確的泛型,可以獲取元素搬俊,也可以添加元素,是最常用的泛型唉擂。