帶你入門Java的泛型
泛型
1、簡(jiǎn)單泛型
泛型的主要目的之一就是用來(lái)指定容器要持有什么類型的對(duì)象,而且由編譯器來(lái)保證類型的正確性。
泛型暫時(shí)不指定類型,在使用時(shí)決定具體使用什么類型。通過(guò)<T>來(lái)實(shí)現(xiàn),T就是類型參數(shù)。
(1)元組
class TwoTuple<A,B>{ public final A first; public final B second; public TwoTuple(A a,B b){ first = a; second = b; } @Override public String toString() { return "{ " + first + ", " + second + '}'; } }
(2)堆棧
class LinkedStack<T>{ private class Node { T item; Node next; Node() { item = null; next = null; } Node(T item, Node next) { this.item = item; this.next = next; } boolean end() { return item == null && next == null; } } private Node top = new Node(); public void push(T item) { top = new Node(item, top); } public T pop() { T result = top.item; if(!top.end()) top = top.next; return result; } } (3)RandomList class RandomList<T>{ private ArrayList<T> storage = new ArrayList<>(); private Random rand = new Random(47); public void add(T item){ storage.add(item); } public T select(){ return storage.get(rand.nextInt(storage.size())); } }
2、泛型接口
泛型也可以應(yīng)用于接口,例如生成器,這是一種專門負(fù)責(zé)創(chuàng)建對(duì)象的類。
import net.mindview.util.Generator; import java.util.Iterator; class Fibonacci implements Generator<Integer> { private int count = 0; public Integer next(){ return fib(count++); } private int fib(int n){ if(n<2) return 1; return fib(n-2) + fib(n-1); } } class IterableFibonacci implements Iterable<Integer> { private Fibonacci fib = new Fibonacci(); private int n; public IterableFibonacci(int count){ n = count; } @Override public Iterator<Integer> iterator() { return new Iterator<Integer>() { @Override public boolean hasNext() { return n>0; } @Override public Integer next() { n--; return fib.next(); } public void remove() { // Not implemented throw new UnsupportedOperationException(); } }; } }
3、泛型方法
泛型方法使得該方法能夠獨(dú)立于類而產(chǎn)生變化。使用泛型方法的時(shí)候,通常不必指明參數(shù)類型,因?yàn)榫幾g器會(huì)為我們找出具體的類型,這稱為類型參數(shù)推斷。
class GenericMethods{ public <T> void f(T x){ System.out.println(x.getClass().getSimpleName()); } }
(1)類型推斷
使用泛型有時(shí)候需要向程序中加入更多的代碼。如下所示:
Map<Person,List<? extends Pet>> petPerson = new HashMap<Person,List<? extends Pet>>();
在泛型方法中可以通過(guò)類型推斷來(lái)簡(jiǎn)化一部分工作。如下所示:
class New{ public static <K,V> Map<K,V> map(){ return new HashMap<K,V>(); } public static void main(String[] args) { Map<Person,List<? extends Pet>> petPerson = New.map(); } }
類型推斷只對(duì)賦值操作有效,其他時(shí)候并不起作用。如果將一個(gè)泛型方法的結(jié)果作為參數(shù),傳遞給另一個(gè)方法時(shí),另一個(gè)方法需要顯式的類型說(shuō)明。如下所示:
public class ExplicitTypeSpecification{ static void f(Map<Person,List<? extends Pet>> petPerson){} public static void main(String[] args) { f(New.<Person,List<? extends Pet>>map()); } }
(2)通用的Generator
import net.mindview.util.Generator; public class BasicGenerator<T> implements Generator<T>{ private Class<T> type; public BasicGenerator(Class<T> type){ this.type = type; } public T next(){ try { return type.newInstance(); }catch (Exception e){ throw new RuntimeException(e); } } public static <T> Generator<T> create(Class<T> type){ return new BasicGenerator<T>(type); } }
(3)Set實(shí)用工具實(shí)現(xiàn)數(shù)學(xué)方法
public class Sets{ @SuppressWarnings("unchecked") protected static <T> Set<T> copy(Set<T> s) { if(s instanceof EnumSet) return ((EnumSet)s).clone(); return new HashSet<T>(s); } //并集 public static <T> Set<T> union(Set<T> a, Set<T> b) { Set<T> result = copy(a); result.addAll(b); return result; } //交集 public static <T> Set<T> intersection(Set<T> a, Set<T> b) { Set<T> result = copy(a); result.retainAll(b); return result; } //差集 public static <T> Set<T> difference(Set<T> superset, Set<T> subset) { Set<T> result = copy(superset); result.removeAll(subset); return result; } //包含除了交集以外的所有元素 public static <T> Set<T> complement(Set<T> a, Set<T> b) { return difference(union(a, b), intersection(a, b)); } }
4、擦除
Java泛型是使用擦除來(lái)實(shí)現(xiàn)的,這意味著當(dāng)你在使用泛型時(shí),任何具體的類型信息都被擦除了,你唯一知道的就是你在使用一個(gè)對(duì)象。因此List<String>和List<Integer>在運(yùn)行時(shí)事實(shí)上是相同的類型,都被擦除成它們的“原生”類型List。
(1)遷移兼容性
泛型類型只有在靜態(tài)類型檢查期間才出現(xiàn),在此之后,程序中的所有泛型類型都將被擦除,替換為他們的非泛型上界。擦除的核心動(dòng)機(jī)是它使得泛化的客戶端可以用非泛化的類庫(kù)來(lái)使用,反之亦然,這經(jīng)常被稱為“遷移兼容性”。
(2)擦除的問(wèn)題
泛型的所有關(guān)于參數(shù)的類型信息都丟失了,所以不能用于顯式地引用運(yùn)行時(shí)類型的操作之中,例如轉(zhuǎn)型、instanceof操作和new表達(dá)式。
5、擦除的補(bǔ)償
(1)由于擦除原因,無(wú)法通過(guò)instanceof比較類型。如果引入類型標(biāo)簽,就可以轉(zhuǎn)而使用動(dòng)態(tài)的isInstance()。
public class ClassTypeCapture<T>{ Class<T> kind; public ClassTypeCapture(Class<T> kind){ this.kind = kind; } public boolean f(Object arg){ return kind.isInstance(arg); } }
(2)創(chuàng)建類型實(shí)例
通過(guò)工廠對(duì)象來(lái)創(chuàng)建實(shí)例。如果使用類型標(biāo)簽,就可以使用newInstance()來(lái)創(chuàng)建這個(gè)類型的新對(duì)象。
class ClassAsFactory<T>{ T x; public ClassAsFactory(Class<T> kind){ try{ x = kind.newInstance(); }catch(Exception e){ throw new RuntimeException(e); } } }
如果類沒(méi)有默認(rèn)的構(gòu)造器,上面的案例會(huì)創(chuàng)建失敗。為了解決這個(gè)問(wèn)題,可以通過(guò)顯示的工廠來(lái)實(shí)現(xiàn)。
interface FactoryI<T>{ T create(); } class Foo2<T>{ private T x; public <F extends FactoryI<T>> Foo2(F factory){ x = factory.create(); } } class IntegerFactory implements FactoryI<Integer>{ public Integer create(){ return new Integer(6); } }
另一種方式是模板方法設(shè)計(jì)模式。
abstract class GenericWithCreate<T>{ final T element; GenericWithCreate(){ element = create(); } abstract T create(); } class X{} class Creator extends GenericWithCreate<X>{ X create(){ return new X(); } }
(3)泛型數(shù)組
無(wú)法通過(guò) T[] array = new T[sz] 來(lái)創(chuàng)建泛型數(shù)組,一般的解決方法是在需要泛型數(shù)組的地方都使用ArrayList。
在創(chuàng)建泛型數(shù)組時(shí),有以下三種情況:
①創(chuàng)建時(shí)強(qiáng)制轉(zhuǎn)型
public class GenericArray<T>{ private T[] array; @SuppressWarnings("unchecked") public GenericArray(int sz){ array = (T[])new Object[sz]; } public T[] rep(){ return array; } public static void main(String[] args) { GenericArray<Integer> gai = new GenericArray<Integer>(10); Integer[] ia = gai.rep();//引起ClassCastException Object[] oa = gai.rep(); } }
②方法返回時(shí)強(qiáng)制轉(zhuǎn)型
class GenericArray2<T>{ private Object[] array; @SuppressWarnings("unchecked") public GenericArray(int sz){ array = new Object[sz]; } public T[] rep(){ return (T[])array; } public static void main(String[] args) { GenericArray<Integer> gai = new GenericArray<Integer>(10); Integer[] ia = gai.rep();//引起ClassCastException Object[] oa = gai.rep(); } }
③使用Array.newInstance()
以上兩種方法都無(wú)法創(chuàng)建具體類型的數(shù)組,無(wú)法推翻底層的數(shù)組類型,只能是Object[]。通過(guò)傳入類型標(biāo)記Class<T>,可以從擦除中恢復(fù)。
class GenericArray3<T>{ private T[] array; @SuppressWarnings("unchecked") public GenericArray(Class<T> type,int sz){ array = (T[]) Array.newInstance(type,sz); } public T[] rep(){ return array; } public static void main(String[] args) { GenericArray<Integer> gai = new GenericArray<Integer>(Integer.class,10); Integer[] ia = gai.rep();//可以正常運(yùn)行 Object[] oa = gai.rep(); } }
6、邊界
邊界使得你可以在用于泛型的參數(shù)類型上設(shè)置限制條件,可以按照自己的邊界類型來(lái)調(diào)用方法。
public class Test { public static void main(String[] args) { Man m = new Man(); m.hear(); m.smell(); } } interface SuperPower{} interface SuperHearing extends SuperPower{ void hearSubtleNoises(); } interface SuperSmell extends SuperPower{ void trackBySmell(); } class SuperHero<POWER extends SuperPower>{ POWER power; SuperHero(POWER power){ this.power = power; } POWER getPower(){ return power; } } class CaineHero<POWER extends SuperHearing & SuperSmell> extends SuperHero<POWER>{ CaineHero(POWER power){ super(power); } void hear(){ power.hearSubtleNoises(); } void smell(){ power.trackBySmell(); } } class SuperHearSmell implements SuperHearing,SuperSmell{ @Override public void hearSubtleNoises() { System.out.println("hearSubtleNoises"); } @Override public void trackBySmell() { System.out.println("trackBySmell"); } } class Man extends CaineHero<SuperHearSmell>{ Man(){ super(new SuperHearSmell()); } }
7、通配符
(1)List<? extends Fruit>協(xié)變
表示具有任何從Fruit繼承的類型的列表。List<? extends Fruit>可以合法地指向一個(gè)List<Apple>。一旦執(zhí)行這種類型的向上轉(zhuǎn)型,就將丟失掉向其中傳遞任何對(duì)象的能力,甚至是傳遞Object也不行。
List<? extends Fruit> flist = Arrays.asList(new Apple()); //Compile Error:can't add any type of object //add()的參數(shù)是<? extends Fruit>,編譯器不知道需要Fruit的哪個(gè) //具體的子類型,因此不接受任何類型的Fruit //flist.add(new Apple()); //flist.add(new Fruit()); //flist.add(new Object()); flist.add(null);//Legal but uninteresting Apple a = (Apple)flist.get(0);//No warning Fruit f = flist.get(0);//No warning flist.contains(new Apple());//參數(shù)是Object flist.indexOf(new Apple());//參數(shù)是Object
(2)List<? super Fruit>逆變
超類型通配符可以安全地傳遞一個(gè)類型對(duì)象到泛型類型中。List<? super Fruit>意味著向其中添加Fruit或Fruit的子類型是安全的。
List<? super Fruit> flist = new ArrayList<Fruit>(); flist.add(new Apple()); flist.add(new Fruit()); //Error:Incompatible Type //Fruit f = flist.get(0); Object f = flist.get(0);//OK,but type information has been lost
(3)無(wú)界通配符List<?>
List實(shí)際上表示“持有任何Object類型的原生List”,List<?>表示“具有某種特定類型的非原生List,只是我們不知道那種類型是什么”,List<? extends Object>表示“類型是Object的導(dǎo)出類”。
無(wú)界通配符的一個(gè)重要應(yīng)用:處理多個(gè)泛型參數(shù)時(shí),允許一個(gè)參數(shù)可以是任何類型,同時(shí)為其他參數(shù)確定某種特定類型。
Map<String,?> map = new HashMap<String,Integer>; map = new HashMap<String,String>;
原生Holder與Holder<?>是大致相同的事物,但存在不同。它們會(huì)揭示相同的問(wèn)題,但是后者將這些問(wèn)題作為錯(cuò)誤而不是警告報(bào)告。
static void rawArgs(Holder holder,Object arg){ //holder.set(arg); //Warning:Unchecked call to set(T) as member //of the raw type Holder //holder.set(new Wildcards());//Same Warning //Can't do this:don't have any 'T' //T t = holder.get(); //OK,but type infomation has been lost Object obj = holder.get(); } //Similar to rawArgs(),but errors instead of warnings static void unboundedArg(Holder<?> holder,Object arg){ //holder.set(arg); //Error:set(capture of ?) in Holder<capture of ?> //cannot be applied to (Object) //holder.set(new Wildcards());//Same Error //Can't do this:don't have any 'T' //T t = holder.get(); //OK,but type infomation has been lost Object obj = holder.get(); }
(4)捕獲轉(zhuǎn)換
未指定的通配符類型被捕獲,并被轉(zhuǎn)換為確切類型。在f2()中調(diào)用f1(),參數(shù)類型在調(diào)用f2()的過(guò)程中被捕獲,因此它可以在對(duì)f1()的調(diào)用中被使用。不能從f2()中返回T,因?yàn)門對(duì)于f2()來(lái)說(shuō)是未知的。
static <T> void f1(Holder<T> holder){ T t = holder.get(); System.out.println(t.getClass().getSimpleName()); } static <T> void f2(Holder<T> holder){ f1(holder); }
8、問(wèn)題
(1)任何基本類型都不能作為類型參數(shù)
(2)實(shí)現(xiàn)參數(shù)化接口
一個(gè)類不能實(shí)現(xiàn)同一個(gè)泛型接口的兩種變體。將泛型參數(shù)移除掉后,這段代碼就可以正常編譯了。
interface Payable<T>{} class Employee implements Payable<Employee>{} //Compile Error:cannot be inherited with different type arguments class Hourly extends Employee implements Payable<Hourly>{}
(3)轉(zhuǎn)型和警告
使用帶有泛型類型參數(shù)的轉(zhuǎn)型或instanceof不會(huì)有任何效果。
由于擦除原因,編譯器無(wú)法知道這個(gè)轉(zhuǎn)型是否安全,并且pop()方法實(shí)際上并沒(méi)有執(zhí)行任何轉(zhuǎn)型。如果沒(méi)有@SuppressWarnings注解,編譯器將對(duì)pop()產(chǎn)生“Unchecked cast”警告。
private int index = 0; private Object[] storage; @SuppressWarnings("unchecked") public T pop(){ return (T)storage[--index]; }
(4)重載
由于擦除的原因,重載方法將產(chǎn)生相同的類型簽名,導(dǎo)致程序不能編譯。
public class UseList<W,T>{ void f(List<T> v){} void f(List<W> v){} }
(5)基類劫持了接口
一旦為Comparable確定了ComparablePet參數(shù),那么其他任何實(shí)現(xiàn)類都不能與ComparablePet之外的任何對(duì)象比較。在前面的“實(shí)現(xiàn)參數(shù)化接口”章節(jié)里面的第一個(gè)例子,就體現(xiàn)了基類劫持接口。
public class ComparablePet implements Comparable<ComparablePet> { public int compareTo(ComparablePet arg) { return 0; } } class Cat extends ComparablePet implements Comparable<Cat>{ // Error: Comparable cannot be inherited with // different arguments: <Cat> and <Pet> public int compareTo(Cat arg) { return 0; } } ///:~ class Hamster extends ComparablePet implements Comparable<ComparablePet>{ public int compareTo(ComparablePet arg) { return 0; } }
9、自限定
class Subtype extends BasicHolder<Subtype> {}這樣用,就構(gòu)成自限定了。從定義上來(lái)說(shuō),它繼承的父類的類型參數(shù)是它自己。
從使用上來(lái)說(shuō),Subtype對(duì)象本身的類型是Subtype,且Subtype對(duì)象繼承而來(lái)的成員(element)、方法的形參(set方法)、方法的返回值(get方法)也是Subtype了(這就是自限定的重要作用)。這樣Subtype對(duì)象就只允許和Subtype對(duì)象(而不是別的類型的對(duì)象)交互了。
class BasicHolder<T> { T element; void set(T arg) { element = arg; } T get() { return element; } void f() { System.out.println(element.getClass().getSimpleName()); } } class Subtype extends BasicHolder<Subtype> {} public class CRGWithBasicHolder { public static void main(String[] args) { Subtype st1 = new Subtype(), st2 = new Subtype(), st3 = new Subtype(); st1.set(st2); st2.set(st3); Subtype st4 = st1.get().get(); st1.f(); } } /* Output: Subtype */
10、異常
由于擦除原因,將泛型應(yīng)用于異常是非常受限的。但是,類型參數(shù)可能會(huì)在一個(gè)方法的throws子句中用到,這使得你可以編寫隨檢查型異常的類型而發(fā)生變化的泛型代碼。
interface Processor<T,E extends Exception> { void process(List<T> resultCollector) throws E; } class ProcessRunner<T,E extends Exception> extends ArrayList<Processor<T,E>> { List<T> processAll() throws E { List<T> resultCollector = new ArrayList<T>(); for(Processor<T,E> processor : this) processor.process(resultCollector); return resultCollector; } } class Failure extends Exception {} class Processor1 implements Processor<String,Failure> { static int count = 3; public void process(List<String> resultCollector) throws Failure1_1, Failure1_2 { if(count-- > 1) resultCollector.add("Hep!"); else resultCollector.add("Ho!"); if(count < 0) throw new Failure1(); } } public class Test { public static void main(String[] args) { ProcessRunner<String,Failure> runner = new ProcessRunner<String,Failure>(); for(int i = 0; i < 3; i++) runner.add(new Processor1()); try { System.out.println(runner.processAll()); } catch(Failure e) { System.out.println(e); } } }
總結(jié)
本篇文章就到這里了,希望能給您帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
shrio中hashedCredentialsMatcher密碼匹配示例詳解
shrio是一個(gè)輕量級(jí)權(quán)限管理框架,密碼的匹配由框架內(nèi)部完成。密碼是否匹配由接口CredentialsMatcher定義實(shí)現(xiàn)類完成,CredentialsMatcher實(shí)現(xiàn)類有SimpleCredentialsMatcher和HashedCredentialsMatcher兩個(gè)2021-10-10Java求解兩個(gè)非負(fù)整數(shù)最大公約數(shù)算法【循環(huán)法與遞歸法】
這篇文章主要介紹了Java求解兩個(gè)非負(fù)整數(shù)最大公約數(shù)算法,結(jié)合實(shí)例形式分析了java求解最大公約數(shù)的實(shí)現(xiàn)方法,并附帶了循環(huán)法與遞歸法算法思路,需要的朋友可以參考下2018-03-03springboot2 生產(chǎn)部署注意事項(xiàng)及示例代碼
這篇文章主要介紹了springboot2 生產(chǎn)部署注意事項(xiàng)及示例代碼,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-04-04java.lang.AbstractMethodError: org.apache.xerces.dom.Documen
這篇文章主要介紹了java.lang.AbstractMethodError: org.apache.xerces.dom.DocumentImpl.setXmlVersion問(wèn)題解決方法,導(dǎo)致本文問(wèn)題的原因是缺少一個(gè)xerces.jar jar包,需要的朋友可以參考下2015-03-03java針對(duì)電話號(hào)碼正則匹配實(shí)例
這篇文章主要介紹了java針對(duì)電話號(hào)碼正則匹配的方法,涉及java正則匹配與字符串操作的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07Spring創(chuàng)建Bean的過(guò)程Debug的詳細(xì)流程
這篇文章主要介紹了Spring創(chuàng)建Bean的過(guò)程Debug的流程,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11微服務(wù)mybatis?typehandler使用詳解(就這一篇夠了)
TypeHandler是MyBatis框架的核心組件,實(shí)現(xiàn)數(shù)據(jù)庫(kù)表字段類型和Java?數(shù)據(jù)類型之間的相互轉(zhuǎn)換,本文介紹通過(guò)實(shí)例代碼mybatis?typehandler使用,感興趣的朋友一起看看吧2024-02-02