关于泛型,有一道经典的试题:publicstaticvoidmain(String[]args){Listlist1=newArrayList();Listlist2=newArrayList();System.out.println(list1.getClass()==list2.getClass());}上面代码的输出是什么?如果你是一个懂泛型的学生,会很容易回答:是的,如果是不懂泛型的学生很可能会给出错误的答案。今天就和大家一起回顾一下Java泛型相关的知识。1.什么是泛型?泛型是JDK5引入的新特性,泛型提供了一种编译时类型安全检测机制,允许程序员在编译时检测非法类型。泛型的本质是参数化类型,也就是说将被操作的数据类型指定为参数。它具有以下特点:与替代所有类型的普通对象相比,泛型类型允许数据类型像参数一样从外部传入。它提供了一种可扩展性。更符合软件编程抽象开发的目的。当特定类型确定后,泛型类型提供类型检测机制。只有匹配的数据才能正常赋值,否则编译不通过。因此,它是一种类型安全检测机制,在一定程度上提高了软件的安全性,防止了低级错误。泛型提高了程序代码的可读性,不必等到运行时才强制转换。在定义或实例化阶段,由于Cache类型的显式作用,程序员一眼就能猜出代码要操作的数据类型。泛型按照用途可以分为三种:泛型类、泛型方法和泛型接口。1.泛型类我们可以定义一个泛型类如下}publicTgetVar(){returnvar;}publicstaticvoidmain(String[]args){Generici=newGeneric(1000);Generics=newGeneric("hello");System.out.println(i.getVar());System.out.println(s.getVar());}}输出结果:1000hello常用的类似于T的类型参数包括:T:表示任意通用类E:表示Element的意思,或者Exception的意思K:表示钥匙的意思。V:代表Value,通常与K一起使用S:代表Subtype泛型类不仅可以接受一个参数T,还可以接受多个参数,类似如下:publicclassGeneric{privateEvar1;privateTvar2;publicGeneric(Evar1,Tvar2){this.var1=var1;this.var2=var2;}publicstaticvoidmain(String[]args){Genericgeneric=newGeneric(1000,"hello");System.out.println(generic.var1);System.out.println(generic.var2);}}2.泛型方法publicclassGeneric{publicvoidtestMethod(Tt){}}泛型方法和泛型Type类略有不同类型参数,即尖括号,写在返回值之前。中的T称为类型参数,而方法中的T称为参数化类型,在运行时不是真正的参数。当然,声明的类型参数实际上可以作为返回值的类型。泛型类和泛型方法共存:publicclassGeneric{publicvoidtestMethod(Tt){System.out.println(t.getClass().getName());}publicTtestMethod1(Tt){returnt;}}上面代码中,Test1是泛型类,testMethod是泛型类中的普通方法,testMethod1是泛型方法。但是泛型类中的类型参数与泛型方法中的类型参数没有关系,泛型方法总是以自己定义的类型参数为准。3.泛型接口泛型接口和泛型类的定义和使用基本相同。通用接口通常用于各种类型的生成器。可以看一个例子://定义一个泛型接口publicinterfaceGenerator{publicTnext();}当实现了泛型接口的类没有传入泛型实参时:/***当没有泛型实参时传入的,和泛型类的定义是一样的。在声明类的时候,泛型类型的声明也必须加入到类中*即:classFruitGeneratorimplementsGenerator{*如果不声明泛型,如:classFruitGeneratorimplementsGenerator,编译器会报错报错:"Unknownclass"*/classFruitGeneratorimplementsGenerator{@OverridepublicTnext(){returnnull;}}实现泛型类型接口类时,传入泛型参数时:/***传入泛型参数时,定义一个producer来实现这个接口,虽然我们只是创建了一个genericinterfaceGenerator*但我们可以为T传入无数个实际参数,组成无数种Generator接口。*实现类实现泛型接口时,如果泛型已经传入实参类型,所有使用泛型的地方都必须替换为传入的实参类型*即:Generator,publicTnext();字符串中的T必须替换为传入的String类型。*/publicclassFruitGeneratorimplementsGenerator{privateString[]fruits=newString[]{"Apple","Banana","Pear"};@OverridepublicStringnext(){Randomrand=newRandom();returnfruits[rand.nextInt(3)];}}4。通配符?通配符的出现是为了在泛型中指定类型范围,包括以下三种形式。>称为不合格的通配符。称为上限通配符。称为具有下限的通配符。无限通配符>无限通配符通常与容器类结合使用。这?其中其实代表了一个未知的类型,那么涉及到什么时候的操作呢?必须与特定类型无关。上面代码中publicvoidtestWildCards(Collection>collection){},方法中的参数是用不受限通配符装饰的Collection对象,隐含的表达了一个意图或者可以说是受限的,即方法testWidlCards()内部无需关心Collection中的实际类型,因为它是未知的。因此,您只能调用Collection上与类型无关的方法。相应地,前者?代表类型T及其子类,而后者?代表T及其超类。值得注意的是,如果把通配符换成泛型方法,那么上面代码中的集合就可以写出来了。这只是一种强制转换。2.什么是泛型类型擦除?Java泛型的特性是从JDK1.5才添加的。因此,为了兼容之前的版本,Java泛型的实现采用了一种“伪泛型”的策略,即Java在语法上是支持泛型的,但是所谓的“类型擦除”(TypeErasure)会在编译阶段执行,将所有泛型表示(尖括号中的内容)替换为特定类型(对应的原始生态类型),就好像根本没有泛型一样。理解类型擦除对于用好泛型非常有帮助,尤其是对于一些看似“难而杂”的问题。理解类型擦除后,就很容易解决了。泛型类型擦除的原理是:消除类型参数声明,即删除<>及其周围的部分。根据类型参数的上下界,推断并替换所有类型参数为原始类型:如果类型参数为无限通配符或无上下界,则替换为Object;如果有上下界,则根据子类替换原则,将类型参数取最左边的符合条件的类型(即父类)。为确保类型安全,在必要时插入转换代码。自动生成“桥接方法”,确保类型擦除后的代码仍然具有泛型“多态性”。1.类型擦除有什么作用?上面我们说过,类型擦除会在编译完成后对泛型进行类型擦除。如果你想眼见为实,那么如果你真的看着它,你应该怎么做?然后你需要编译word字节码文件已经反编译。这里我们使用轻量级工具Jad进行反编译。Jad的使用也很简单。下载解压后,将需要反编译的字节码文件放在该目录下,然后在命令行中执行以下命令,在同一目录下生成反编译后的.java文件:jad-sjavaTest.classOK,工具准备好了,我们来看看不同情况下的类型擦除。无限制类型擦除当类定义中的类型参数没有限制时,类型擦除后将直接替换为Object。在下面的例子中,类型参数Tin全部替换为Object(左边是编译前的代码,右边是反编译字节码文件得到的代码):受限类型擦除当类定义中有时是对的类型参数的限制,在类型擦除中用类型参数的上限或下限代替。下面的代码中,擦除后T被Integer代替:擦除方法中的类型参数比较下面两边的代码可以看到,擦除方法中的类型参数时,与擦除类中的类型是一样的definition中的参数是一致的。没有限制时,直接作为Object擦除,有限制时,作为上限或下限擦除:2、类型擦除带来什么限制?类型擦除是泛型类型,可以和之前的java版本代码兼容共存的原因。但也因为类型擦除,它会擦除??很多继承相关的特性,这是它带来的局限性。理解类型擦除可以帮助我们绕过开发中可能遇到的雷区,理解类型擦除也可以让我们绕过泛型本身的一些限制。比如一般情况下,因为泛型的限制,编译器不会让最后一行代码因为类似的不匹配而编译。但是,基于对类型擦除的理解,我们可以使用反射来绕过这个限制。publicinterfaceListextendsCollection{booleanadd(Ee);}以上是List及其add()方法的源码定义。因为E代表任意类型,add方法其实等价于:booleanadd(Objectobj);然后,使用反射,我们绕过编译器调用add方法。publicclassToolTest{publicstaticvoidmain(String[]args){Listls=newArrayList<>();ls.add(23);//ls.add("text");try{Methodmethod=ls.getClass().getDeclaredMethod("add",Object.class);method.invoke(ls,"test");method.invoke(ls,42.9f);}catch(NoSuchMethodExceptione){//TODOAuto-generatedcatchblocke.printStackTrace();}catch(SecurityExceptione){//TODOAuto生成的catchblocke.printStackTrace();}catch(IllegalAccessExceptione){//TODOAuto生成的catchblocke.printStackTrace();}catch(IllegalArgumentExceptione){//TODOAuto生成的catchblocke.printStackTrace();}catch(InvocationTarget){//TODOAuto-generatedcatchblocke.printStackTrace();}for(Objecto:ls){System.out.println(o);}}}打印结果为:23test42.9123可以看到利用原理类型擦除,通过反射的方式,绕过了正常开发中编译器不允许的操作限制。