前言Java泛型(generics)是JDK5引入的新特性,泛型提供了编译时类型安全检测机制,允许开发者在编译时检测非法类型。泛型的本质是参数化类型,也就是说将被操作的数据类型指定为参数。泛型带来的好处在没有泛型的情况下,参数的“任意”是通过引用Object类型来实现的。“任意”的缺点是需要显式强制类型转换,而这种转换是在需要开发者预测实际参数类型时进行的。对于强制类型转换错误的情况,编译器可能不会提示错误,只会在运行时出现异常,这本身就是一种安全隐患。那么泛型的好处就是可以在编译时检查类型安全,所有的强制转换都是自动隐式的。publicclassGlmapperGeneric{privateTt;publicvoidset(Tt){this.t=t;}publicTget(){returnt;}publicstaticvoidmain(String[]args){//donothing}/***不指定类型*/publicvoidnoSpecifyType(){GlmapperGenericglmapperGeneric=newGlmapperGeneric();glmapperGeneric.set("test");//需要强制类型转换Stringtest=(String)glmapperGeneric.get();System.out.println(test);}/***指定类型*/publicvoidspecifyType(){GlmapperGenericglmapperGeneric=newGlmapperGeneric();glmapperGeneric.set("test");//无强制类型转换Stringtest=glmapperGeneric.get();System.out.println(test);}}上面代码中的specifyType方法省略了强制转换,可以在编译时检查类型安全,可以用在类、方法和接口上。泛型中的通配符我们在定义泛型类、泛型方法、泛型接口的时候,经常会遇到很多不同的通配符,比如T、E、K、V等等,这些通配符是什么意思呢?常用T、E、K、V、?这些本质上都是通配符,没有区别,只是编码时的约定而已。比如上面代码中的T,我们可以将其替换为A-Z之间的任意一个字母,不会影响程序的正常运行,但是如果将其替换为其他字母而不是T,可读性可能会变弱。通常,T、E、K、V、?以这种方式达成一致:?代表一个不确定的java类型T(type)代表一个特定的java类型KV(keyvalue)代表java中的KeyValueEkeyvalue(element)代表Element?Unboundedwildcards先从一个小例子开始,原文在这里。我有一个父类Animal和几个子类,例如狗、猫等。现在我需要一个动物列表。我的第一个想法是这样的:ListlistAnimals但是老大的想法是这样的:ListlistAnimals为什么要用通配符而不是简单的泛型呢?通配符在声明局部变量的时候其实是没有意义的,但是当你为方法声明参数的时候,它就很重要了。staticintcountLegs(Listanimals){intretVal=0;for(Animalanimal:animals){retVal+=animal.countLegs();}returnretVal;}staticintcountLegs1(Listanimals){intretVal=0;for(Animalanimal:animals){retVal+=animal.countLegs();}returnretVal;}publicstaticvoidmain(String[]args){Listdogs=newArrayList<>();//不会报错countLegs(dogs);//报错countLegs1(dogs);}调用countLegs1时会变成红色,报错信息如下:因此对于不确定或者不关心实际操作的类型,可以使用无限通配符(尖括号中的问号,即),这意味着您可以持有任何类型。和countLegs方法一样,最后一个类是有限的,但是不关心具体是什么类型,所以传入的Animal的所有子类都可以支持,不会报错。而countLegs1将不起作用。上限通配符Previous:用extends关键字声明,表示参数化类型可以是指定类型,也可以是该类型的子类。在类型参数中使用extends意味着这个泛型中的参数必须是E或者E的子类,这样有两个好处:如果传入的类型不是E或者E的子类,泛型中就可以使用E如果编译失败方法,或者你必须强制它进入E才能使用privateEtest(Karg1,Earg2){Eresult=arg2;arg2.compareTo(arg1);//.....returnresult;}类型参数列表如果类型参数有多个上界,下界用逗号分隔。通配符Lowerbounds:使用super声明,表示参数化类型可以是指定类型,也可以是该类型的父类型,直到Object在该类型中使用super表示该泛型类型中的参数必须是E或E的父类。privatevoidtest(Listdst,Listsrc){for(Tt:src){dst.add(t);}}publicstaticvoidmain(String[]args){Listdogs=newArrayList<>();Listanimals=newArrayList<>();newTest3().test(animals,dogs);}//Dog是Animal类DogeextendsAnimal的子类{}dsttype"大于等于"srctype,这里的"大于等于"是指dst表示的范围大于src,所以能装dst的容器也能装src。和...之间的不同?和T?和T都代表不确定类型。不同的是,我们可以对T进行操作,而不能对?进行操作,比如下面这样://YesTt=operate();//No?汽车=操作();简单总结一下:T是一个确定类型,通常用在泛型类和泛型方法的定义中,?是不确定类型,通常用在泛型方法的调用代码和形参中,不能用来定义类和泛型方法。区别1:使用T来保证泛型参数的一致性//通过T来保证泛型参数的一致性publicvoidtest(Listdest,Listsrc)//通配符是不确定的,所以这个方法不能保证两个列表具有相同的元素类型。使用的是字符串,所以会是红色的,报错。不保证两个List具有相同的元素类型GlmapperGenericglmapperGeneric=newGlmapperGeneric<>();Listdest=newArrayList<>();Listsrc=newArrayList<>();glmapperGeneric。testNon(目标,src);上面的代码在编译器中不会报错,但是在进入testNon方法的内部操作时(比如赋值),对于dest和src,还是需要进行类型转换的。区别二:类型参数可以是多限制的,但通配符不能。使用&符号设置多边界(MultiBounds),指定泛型T必须是MultiLimitInterfaceA和MultiLimitInterfaceB的公共子类型。这时候变量t就拥有了所有有限的方法和Attributes。对于通配符,因为不是确定类型,所以不能进行多重限定。区别三:通配符可以被超类限制,但是类型参数不能。类型参数T只有一种类型的限制:TextendsA,但通配符?可以通过两种方式进行限制:?extendsA?superAClass和Class之间的区别?和T是前面介绍过的。那么Class和Class有什么区别呢?Class和Class最常见的用途是在反射场景中。这里有一段发射代码来说明。//通过反射生成multiLimit//对象。这里很明显我们需要使用强制类型转换MultiLimitmultiLimit=(MultiLimit)Class.forName("com.glmapper.bridge.boot.generic.MultiLimit").newInstance();对于上面的代码,在运行时,如果反射的类型不是MultiLimit类,就会报java.lang.ClassCastException。在这种情况下,可以改用下面的代码,这样就可以在编译时直接检查类型问题:Class实例化时,T必须替换为具体的类。Class是通配符泛型类型,而?可以表示任何类型,所以主要用于声明的限制。例如,我们可以这样声明://YespublicClass>clazz;//不是,因为T需要指定类型publicClassclazzT;所以当你不知道要声明什么类型的类时,你可以定义一个类。那么如果你也想要publicClassclazzT;这种情况下,必须为当前类指定T,publicclassTest3{publicClass>clazz;//不会报错publicClassclazzT;泛型中有些点不是很完整,仅供参考。文中如有不当之处,敬请指正。