还是不懂Java的泛型?仅使用本文以确保您的面试答案流畅。转载本文请联系程序新视界公众号。在最近的技术交流群里,有朋友问:Object和泛型T有什么区别,回答完问题不禁想,马上就要面试了,还有那么多朋友不知道了解泛型?是时候整理一篇泛型相关的文章给大家了。一篇文章就可以把泛型完全搞定,让大家在实践中再也不用担心面试或者泛型相关的问题。什么是泛型?Generic是JDK5引入的新特性,即“参数化类型”。通俗的说就是通过参数化定义原有的具体类型,然后在使用或调用时传入具体类型(typeargument)。泛型的本质是参数化类型(在不创建新类型的前提下,通过泛型指定的不同类型来控制形参的具体类型)。在使用泛型的过程中,将操作的数据类型指定为参数。这种参数类型可以用在类、接口和方法中,分别称为泛型类、泛型接口和泛型方法。为什么要使用泛型当不使用泛型时,“任意”的参数可以通过Object来实现,但这样做的缺点是需要显式的类型转换,这需要开发者知道实际的类型。但是,强制类型转换会导致错误。例如,如果Object将实际类型转换为String,则会强制转换为Integer。编译时不会提示错误,但运行时会抛出异常,这是一个明显的安全隐患。Java通过引入泛型机制,在编译期提前检查了上述隐患。开发者不仅可以清楚地知道实际类型,还可以通过编译时的检查提示错误,从而提高代码的安全性和健壮性。使用泛型前后对比举一个经典的例子来说明不使用泛型会出现的问题。Listlist=newArrayList();list.add(1);list.add("zhuan2quan");list.add("程序新视野");for(inti=0;ilist=newArrayList<>();list.add(1);list.add("zhuan2quan");list.add("ProgramNewVision");for(Stringvalue:list){System.out.println("value="+value);}可以看出代码变得更加清爽简洁,代码行list.add(1)在IDE中直接显示会提示错误信息:Requiredtype:StringProvided:int错误信息是泛型类型对添加到List的数据有约束,只能是String类型。泛型中的通配符在使用泛型时,经常会看到T、E、K、V等通配符,它??们是什么意思呢?本质上都是通配符,没有区别。换成A-Z之间的任意一个字母就可以了。但是,开发者之间有一些不成文的约定:T(type)代表一个特定的java类型;KV(keyvalue)表示java键值中的KeyValue;E(element)代表Element;该类型是假泛型类型。为了实现向后兼容,Java中的泛型只是一个语法糖,并不是像C++那样真正意义上的泛型。还是上面的例子,直接给泛型为String的List添加int类型会提示错误:Listlist=newArrayList<>();列表.添加(1);对于上面的代码,我们使用反射间接调用add方法:@Testpublicvoidtest3()throwsNoSuchMethodException,InvocationTargetException,IllegalAccessException{Listlist=newArrayList<>();list.add(1);Methodadd=list.getClass().getMethod("add",Object.class);add.invoke(list,"ProgramNewVision");System.out.println(list);System.out.println(list.get(1));}执行上面的代码,我们发现程序并没有抛出异常,正常打印出来:[1,ProgramNewVision]ProgramNewVision只能加载成一个ListofIntegers,但是成功加载了一个String类型的值。可见,所谓的泛型确实是假泛型。同时,我们也可以通过字节码来证明。以上面使用泛型的示例代码为例,使用javap-c命令查看字节码:Code:0:new#2//classjava/util/ArrayList3:dup4:invokespecial#3//Methodjava/util/ArrayList。"":()V7:astore_18:aload_19:ldc#6//Stringzhuan2quan11:invokeinterface#5,2//InterfaceMethodjava/util/List.add:(Ljava/lang/Object;)Z16:pop17:aload_118:ldc#7//字符串程序新视界20:invokeinterface#5,2//InterfaceMethodjava/util/List.add:(Ljava/lang/Object;)Z25:pop26:aload_127:invokeinterface#18,1//InterfaceMethodjava/util/List.iterator:()Ljava/util/Iterator;32:astore_233:aload_234:invokeinterface#19,1//InterfaceMethodjava/util/Iterator.hasNext:()Z39:ifeq80从字节码可以看出,List.add方法本质上是一个对象。再次证明了Java的泛型只在编译时有效,在运行时会被清除,也就是说所有的泛型参数类型在编译后都会被清除。这就是我们常说的类型擦除。因此,也可以说:泛型在逻辑上可以看作是多个不同的类型,但实际上是同一个基本类型。泛型的定义和使用泛型分为三种类型,即:泛型类、泛型接口和泛型方法。在学习这三类泛型使用场景之前,我们需要先明确一个基本原则,即泛型声明通常以<>等大写字母来定义。只是不同的类型有不同的声明位置和使用方法。泛型类泛型类的语法形式:classname{/*...*/}泛型类的声明与非泛型类的声明类似,只是在类名之后添加了类型参数声明部分。由尖括号(<>)分隔的类型参数部分跟在类名之后。它指定类型参数(也称为类型变量)T1、T2、……和Tn。一般泛型中的类名称为原型,<>指定的参数称为类型参数。使用示例://T为任意标识符,如T、E、K、V等表示泛型publicclassFoo{//广义成员变量,T的类型由外部指定privateTinfo;//构造方法的类型为T,外部指定T的类型publicFoo(Tinfo){this.info=info;}//方法的返回值类型为T,外部指定T的类型publicTgetInfo(){returninfo;}publicstaticvoidmain(String[]args){//实例化泛型类时,必须指定T的具体类型,这里是String。//传入的实参类型必须和泛型类型的类型参数类型一致,这里是String。Foofoo=newFoo<>("ProgramNewVision");System.out.println(foo.getInfo());}}当然,在上面的例子中,也可以在使用通用类。语法上支持,那么这个方法暂时不推荐,就像undefinedgenerics一样。Foofoo11=newFoo(1);比如上面的写法也是可行的,但是时区定义了泛型的含义。泛型接口泛型接口的声明与泛型类的声明一致。泛型接口的语法形式为:publicinterfaceContext{TgetContext();}实现泛型接口有两种方式:子类显式声明泛型类型和子类不显式声明泛型类型。先看子类显式声明泛型的例子://在实现泛型接口的时候,已经传入了实参类型,所以凡是使用泛型的地方都要换成传入的实参类型publicclassTomcatContextimplementsContext{@OverridepublicStringgetContext(){return"Tomcat";}}子类没有显式声明泛型类型://不传入泛型参数时,与泛型类的定义相同。在声明类的时候,泛型的声明需要在类中也加上作为上面提到的普通类。上面的例子中,只有一个泛型参数,当然也可以指定两个或多个:publicinterfaceGenericInterfaceSeveralTypes{RperformAction(finalTaction);}多个泛型参数之间可以用逗号(,)隔开。泛型方法在实例化类时指定了泛型的具体类型;泛型方法在调用方法时指定了泛型的具体类型。泛型方法可以是普通方法、静态方法、抽象方法、final修饰方法、构造方法。泛型方法的语法如下:publicTfunc(Tobj){}尖括号是类型参数列表,在方法返回值T或void关键字之前。尖括号中定义的T可以在方法的任何地方使用,例如参数、方法和返回值。protectedabstractRperformAction(finalTaction);staticRperformActionOn(finalCollectionaction){finalRresult=...;//这里实现returnresult;}从上面的例子可以看出泛型方法也可以定义多个泛型类型。再看一个示例代码:publicclassGenericsMethodDemo1{//1,public和返回值之间的,声明该方法的泛型类型。//2。只有声明的方法才是泛型方法,泛型类中使用泛型的成员方法不是泛型方法。//3。表示该方法将使用泛型T,泛型T才能在该方法中使用。//4。T可以是任何标志,例如T、E、K、V等。abc");printClass(123);}}需要注意的是,泛型方法与类是否泛型无关。此外,静态方法不能访问类上定义的泛型;如果静态方法操作的引用数据类型不确定,则必须在方法上定义泛型。在上面的例子中,如果GenericsMethodDemo1定义为GenericsMethodDemo1,那么printClass方法就不能直接使用类上的T,只能像上面的代码那样访问自己定义的T。泛型方法和普通方法的区别接下来我们比较一下泛型方法和非泛型方法的区别://方法一publicTgetKey(){returnkey;}//方法二publicTshowKeyName(Tt){return;}方法一虽然使用了T的泛型声明,但是使用的是泛型类中定义的变量,所以该方法不是泛型方法。而方法2中通过两个尖括号声明T,才是真正的泛型方法。对于方法二,还有一种情况,就是类中也声明了T,那么方法参数的T只是指本方法的T,不是类的T。通用方法和可变参数@SafeVarargspublicfinalvoidprint(T...args){for(Tt:args){System.out.println("t="+t);}}publicstaticvoidmain(String[]args){GenericDemo2demo2=newGenericDemo2();demo2.print("abc",123);}print方法在可变参数args中打印出结果,可变参数可以传递不同的具体类型。打印结果:t=abct=123对泛型方法的一个总结就是:能用泛型方法就尽量用泛型方法,这样泛型方法才能被要求到最需要的范围。如果使用泛型类,则泛化整个类。Genericwildcard类型的通配符一般是用什么?而不是特定的类型参数(这里是类型参数,不是类型参数)。当操作类型不需要使用该类型的具体函数时,只使用Object类中的函数,那么?通配符可用于表示未知类型。例如List在逻辑上是List、List等所有List的父类。/***在使用List作为形参的方法中,不能传入List的实例,*也就是说List不能被视为List;*/publicstaticvoidgetNumberData(Listdata){System.out.println("data:"+data.get(0));}/***在正式使用List的方法中参数,List<不能用Number>的实例传入;*/publicstaticvoidgetStringData(Listdata){System.out.println("data:"+data.get(0));}/***使用类型通配符可以同时表明它是一个List和List、List的引用类型。*一般使用类型通配符?替换特定类型的参数。请注意,这是一个类型参数;*与Number、String和Integer一样,是实际类型。您可以使用?被视为所有类型的父类。*/publicstaticvoidgetData(List>data){System.out.println("data:"+data.get(0));}以上三种方法中,getNumberData只能传List类型参数,getStringData只能传List类型的参数。如果都只使用Object类的功能,可以用getData方法的形式声明,同时支持多种类型。上述这种类型的通配符也称为无界通配符,应用场景有两种:一种方法,可以使用Object类中提供的函数来实现。在不依赖于类型参数的泛型类中使用方法。在获取数据中,?用作通配符,但在某些场景下,需要限制泛型类型参数的上下边界。例如:类型参数只允许传入某类父类或某类子类。/***类型通配符的上限是以List的形式定义的,所以定义的意思是通配符的泛型值接受Number及其子类类型。*/publicstaticvoidgetUperNumber(Listdata){System.out.println("data:"+data.get(0));}通过extends限制通配符的上边界,即只接受Number及其子类类型。无论是接口的实现,还是类的集成,都可以用extends来表示。这里的Number也可以换成T,表示通配符表示的类型是T类型的子类。publicstaticvoidgetData(Listdata){System.out.println("data:"+data.get(0));}与上限通配符相比,还有一个下限通配符:publicstaticvoidgetData(Listdata){System.out.println("data:"+data.get(0));}下界通配符表示通配符表示的类型是T类型的超类。泛型限制原始类型(如:int、long、byte等)不能用于泛型,在使用过程中需要替换为它们的包装类(如:Integer、Long、Byte等)。finalListlongs=newArrayList<>();finalSetintegers=newHashSet<>();当然在使用过程中会涉及到自动拆箱和自动装箱的操作:finalListlongs=newArrayList<>();longs.add(0L);//'long'被打包为'Long'longvalue=longs.get(0);//'Long'解包'long'泛型类型推断当泛型使用泛型后,开发者需要处处添加相应的泛型,如:finalMap>map=newHashMap>();for(finalMap.Entry>entry:map.entrySet()){}为了解决上述问题,Java7引入了运算符<>,编译器可以推导出该运算符所代表的原始类型。因此在Java7及之后,泛型对象的创建就变成了这样:finalMap>map=newHashMap<>();逐步解释了泛型的使用。通过本文的学习,你在使用和面试过程中基本可以应对90%的场景。如果对你有帮助,可以点个赞。参考文章:https://blog.csdn.net/s10461/article/details/53941091https://www.cnblogs.com/jingmoxukong/p/12049160.htmlhttps://blog.csdn.net/lxxiang1/article/details/81429987https://www.javacodegeeks.com/2015/09/how-and-when-to-use-generics.html