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

Java泛型详解,史上最全图解!

时间:2023-04-01 16:49:44 Java

泛型在java中起着非常重要的作用,无论是开源框架还是JDK源码,都可以看到。可以毫不夸张地说,泛型是通用设计中必不可少的元素,因此真正理解和正确使用泛型是必修课。一:泛型的本质Java泛型(generics)是JDK5引入的新特性,泛型提供了一种编译时类型安全检测机制,允许程序员在编译时检测非法类型。泛型的本质是参数化类型,即为类型指定一个参数,然后在使用时指定参数的具体值,这样在使用时就可以确定类型。这种参数类型可以用在类、接口和方法中,分别称为泛型类、泛型接口和泛型方法。二:为什么要使用泛型泛型的优点是在编译时检查类型安全,所有的强制转换都是自动和隐式的,提高了代码重用。(1)保证类型安全。在没有泛型之前,每一个从集合中读取的对象都必须进行类型转换。如果不小心插入了错误类型的对象,运行时的转换过程就会出错。例如:使用没有泛型的集合:publicstaticvoidnoGeneric(){ArrayListnames=newArrayList();names.add("mikechen'sInternetarchitecture");names.add(123);//正常编译}使用泛型集合:publicstaticvoiduseGeneric(){ArrayListnames=newArrayList<>();names.add("mikechen'sInternetArchitecture");names.add(123);//编译失败}使用泛型,add(123)将在编译定义的集合名称时编译失败。相当于告诉编译器每个集合接收的是什么类型的对象,编译器在编译时会进行类型检查,告知是否插入了错误类型的对象,使程序更安全,增强程序的健壮性。(2)消除类型转换泛型的一个附带好处是您可以消除源代码中的许多类型转换,这使代码更具可读性并减少出错的机会。\或者举个例子,下面没有泛型的代码段需要转换:Listlist=newArrayList();list.add("hello");Strings=(String)list.get(0);当重写为使用泛型时,代码不需要强制转换:Listlist=newArrayList();list.add("hello");Strings=list.get(0);//nocast(3)避免不必要的装箱和拆箱操作,提高程序性能。这个过程很昂贵。引入泛型后,不需要进行装箱和拆箱操作,所以运行效率比较高,尤其是在集合操作频繁的系统中,这个特性带来的性能提升更加明显。泛型变量固定了类型,使用的时候就已经知道是值类型还是引用类型,避免了不必要的装箱和拆箱操作。objecta=1;//因为是object类型,所以会自动装箱。intb=(int)a;//强制转换,拆箱操作。这样,当次数增加时,就会影响程序的运行效率。使用泛型后publicstaticTGetValue(Ta){  returna;}publicstaticvoidMain(){  intb=GetValue(1);//使用该方法的类型已经指定为int,所以不会有装箱和拆箱操作。}(4)提高代码的可重用性。三:如何使用泛型泛型的使用有三种方式,分别是:泛型类、泛型接口、泛型方法。1.泛型类泛型类:在类上定义泛型定义格式:\public类类名<泛型1,...>{}注意:泛型必须是引用类型(不是基本数据类型)才能定义一个泛型类,类名后加一对尖括号,尖括号中填入类型参数。参数可以有多个,多个参数用逗号隔开:publicclassGenericClass{}当然这后面的参数类型也是标准化的,不能像上面那样随意。通常,我们使用单个大写字母来表示类型参数:T:任意类型类型\E:集合元素中元素的类型\K:key-valueformkey\V:key-valueformvalue\samplecode:泛型类:公共类GenericClass{私有T值;publicGenericClass(Tvalue){this.value=value;}publicTgetValue(){返回值;}publicvoidsetValue(Tvalue){this.value=value;}}测试类://TODO1:GenericClassname=newGenericClass<>("mikechen'sInternetarchitecture");System.out.println(name.getValue());GenericClassnumber=newGenericClass<>(123);System.out.println(number.getValue());运行结果:2.泛型接口generic方法概述:在方法上定义泛型类型\\定义格式:publicreturntype方法名(泛型类型变量名){}注意事项:在方法中定义的形参方法声明只能在方法中使用,而接口和类声明中定义的类型参数可以在整个接口和类中使用。调用fun()方法时,编译器会根据传入的实际对象,判断类型参数T所代表的实际类型。publicinterfaceGenericInterface{voidshow(Tvalue);}}publicclassStringShowImpl实现GenericInterface{@Overridepublicvoidshow(Stringvalue){System.out.println(value);}}publicclassNumberShowImpl实现GenericInterface{@Overridepublicvoidshow(Integervalue){System.out.println(value);}}注意:使用泛型时,前后定义的泛型类型必须一致,否则会出现编译异常:GenericInterfacegenericInterface=newNumberShowImpl();//编译异常或者干脆不指定type,那么任何类型的new都是可接受的:GenericInterfaceg1=newNumberShowImpl();GenericInterfaceg2=newStringShowImpl();3.泛型方法generic类型方法是在调用方法时指定泛型类型的具体类型。\定义格式:修饰符<代表泛型的变量>返回值类型方法名(参数){}例如:/*****@paramt传入泛型参数*@param泛型类型*@returnT返回值是T类型*说明:*1)public和返回值之间的很重要,可以理解为声明这个方法为泛型方法。*2)只有声明的方法才是泛型方法,泛型类中使用泛型的成员方法不是泛型方法。*3)表示该方法将使用泛型T,泛型T才能在该方法中使用。*4)同泛型类的定义,这里的T可以写成任意标识符,常用参数如T,E等来表示泛型。*/publicTgenericMethod(Tt){System.out.println(t.getClass());System.out.println(t);返回吨;}publicstaticvoidmain(String[]args){GenericsClassDemogenericString=newGenericsClassDemo("helloGeneric");//这里的泛型可以和下面调用的泛型方法不同。Stringstr=genericString.genercMethod("hello");//输入是String类型,返回也是String类型Integeri=genericString.genercMethod(123);//输入是Integer类型,返回return也是Integer类型}classjava.lang.Stringhelloclassjava.lang.Integer123这里可以看到,泛型方法根据我们传入参数的类型得到了不同的类型。通用方法使方法能够独立于类而变化。四:泛型通配符Java泛型的通配符是用来解决泛型之间引用传递问题的特殊语法。主要有以下三种类型:://表示类型参数可以是任意类型publicclassApple{}//表示类型参数必须是A或者A的子类publicclassApple{}//表示类型参数必须是A或者A的超类型publicclassApple{}1.Unbounded无界通配符是,比如List。无限通配符的主要作用是允许泛型接受未知类型的数据。2.上界通配符(UpperBoundedWildcards)使用<?extendsE>的形式使用了一个带有固定上边界通配符的泛型类型,可以接受指定类及其子类类型的数据。要声明使用这种类型的通配符,请使用,其中E是泛型类型的上边界。注意:这里虽然使用了extends关键字,但不限于继承父类E的子类,还指显示接口E的类。3.修复下界通配符(LowerBoundedWildcards),使用的形式使用了一个下边界固定的泛型通配符,可以接受指定类及其超类类型的数据。要声明使用这种类型的通配符,请使用,其中E是泛型类型的下边界。注意:您可以为通用类型指定上限或下限,但不能同时指定两者。五:KTVE在泛型中的含义如果点开??JDK中一些泛型类的源码,我们会看到如下代码:publicclassArrayListextendsAbstractListimplementsList,RandomAccess,Cloneable,java.io.Serializable{...}publicclassHashMapextendsAbstractMapimplementsMap,Cloneable,Serializable{...}上述泛型类定义中的泛型是做什么的参数E、K和V是什么意思?其实这些参数名是可以任意指定的,就像方法的参数名可以任意指定一样,只是我们通常会起一个有意义的名字,让别人一看就知道是什么意思。通用参数也是如此。E一般是指元素,在集合类中使用。常见的泛型参数名称如下:E:Element(用在集合中,因为集合存储元素)\T:Type(Java类)\K:Key(键)\V:Value(值)\N:Number(数值类型)\?:表示一个不确定的java类型六:泛型的实现原理泛型的本质是参数化数据类型,它是通过擦除实现的,即编译器会“擦除”泛型语法并相应地做一些类型转换动作。看个例子应该就清楚了,比如:publicclassCaculate{privateTnum;}我们定义了一个泛型类,定义了一个类型为泛型的属性成员。这个T具体是什么类型,我们不知道,只是用来限制类型的。反编译这个Caculate类:publicclassCaculate{publicCaculate(){}privateObjectnum;}发现编译器擦掉了Caculate类后面的两个尖括号,将num的类型定义为Object类型。那么是不是所有的泛型类型都用Object抹掉了?在大多数情况下,泛型类型被Object替代,但在一种情况下不是。即使用extends和super语法的有界类型,如:publicclassCaculate{privateTnum;}在这种情况下,num将被String取代,而不是Object。这是一个类型限定的语法,它限制T是一个String或者String的子类,也就是说,当你构造一个Caculate实例时,你只能限制T是一个String或者String的子类,所以不管怎样类型你限制T,String是Parent类,不会有类型不匹配的问题,所以String可以用来类型擦除。事实上,编译器通常会在使用泛型的地方进行编译和类型擦除,然后返回实例。但除此之外,如果在构造泛型实例时使用了泛型语法,编译器会标记该实例并关注该实例后续所有方法的调用,并在每次调用前进行安全检查。无法成功调用该方法。事实上,编译器不仅仅关注泛型方法的调用,还会对一些返回值为限定泛型类型的方法进行强制类型转换。由于类型擦除,返回值为泛型类型的方法将被擦除为Object类型,当调用这些方法时,编译器会额外插入一行checkcast指令进行强制类型转换,这个过程称为“泛型翻译”。七:最后,从Java泛型的诞生,到泛型的使用,泛型的实现原理,这六个方面,我做了一个完整而详尽的讲解。希望对你有用!作者简介:mikechen,十余年BAT架构经验,资深技术专家,曾就职于阿里、淘宝、百度。阅读mikechen的互联网架构更多技术文章合集Java|Database|Framework|Distributed|Microservice|Middleware|Architect在公众号菜单栏对话框回复【架构】关键词查看我原创300期+BAT架构技术系列文章和一篇1000+大厂面试题及答案合集。