当前位置: 首页 > 后端技术 > Java

Java泛型通配符详解

时间:2023-04-02 09:37:48 Java

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