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

《回炉重造》——泛型

时间:2023-04-01 22:34:42 Java

泛型前言之前学习“泛型”的时候,对于可以限制类型只有粗浅的认识,并没有深入的了解。可以说是基础没学好。Surface,所以,现在回炉子重建,重新学习泛型!打好基础!什么是泛型?Generic(通用),Generic的意思是“广义的,一般的”。它是JDK5引入的一个新特性,它提供了编译时类型安全检测,可以让我们在编译时检测到非法数据类型。它本质上是一个参数化类型。这里还涉及到一个词“参数化类型”。这意味着什么?意思是:将类型参数化(只能感受到中国文化的博大精深),也就是我们可以把类型作为参数,换句话说就是把要操作的数据类型指定为参数。说到参数,我们也很熟悉。你看,方法上的形参和调用方法时的实参都是参数吧?同样的,类型,也就是Java中的各种基本引用类型,当然也包括你自己定义的类型。说白了就是各种类(Class),好像说了一堆废话)。这就涉及到另外一个词,即“类型参数”。我们可以看下ArrayList的源码,如下:E>,其中E可以说是“类型参数”。我们在写ArrayListlist=newArrayList<>()的时候,为ArrayList集合指定了一个具体的类型String,而形参E传入的实参是String,也就是说String是一个“类型真正的人参”。简而言之:ArrayList中的E称为类型参数;ArrayList中的String称为类型参数。这两个加起来就是上面说的“类型参数”。为什么会出现泛型?泛型和集合有着千丝万缕的联系。我们现在使用集合时,也使用“通用集合”。Listlist=newArrayList<>();//泛型集合当然,我们刚开始学习的时候并没有用到泛型,也就是非泛型集合。列表list=newArrayList();//非泛型集合在之前没有泛型的情况下,我们看看会出现什么问题。默认的ArrayList集合中存储的元素类型是Object,这个很好。Java中任何类型的最终父类都是Object,任何类型的数据都可以存放在这个集合中。比如我可以这样操作(经典案例):Listlist=newArrayList();//非泛型集合list.add("HelloWorld!");//存储字符串类型list.add(23);//存储Int类型,这里会自动装箱为Integer类型list.add(true);//storeBooleantypefor(Objecto:list){//使用Object来接收,合理System.out.println(o);}我们存储数据之后,如果后面需要用到,需要拿outofthecollection,进一步的操作需要指定具体的数据类型,所以需要进行强制类型转换。for(Objecto:list){Strings=(String)o;//强制类型转换//后续操作...}这时候的代码是不会报错的,等到我们运行起来编译就没有问题了。会有一个异常——ClassCastException。这也是不可避免的。毕竟,我们的集合中还有其他类型的数据。对于其他类型的数据,再怎么类型转换,也不可能转换成String类型,就会出现异常。看到这里,可能有小伙伴要问了,难道不能一一强制转换吗?我知道存储的是什么数据,直接获取对应的数据强制传输即可。对,没错,你可以一个一个的强制转账,人数少还好,人数多了怎么办?你会怎么办?于是,泛型出现了,它可以在编译时限制我们的类型,保证类型是安全的,即运行时不会出现异常。Listlist=newArrayList<>();//通用集合list.add("HelloWorld!");//存储String类型list.add("23");list.add("CodingCoding");for(Strings:list){//使用String接收System.out.println(s);//后续操作...}看到这里的小伙伴们可能会有这样的疑问:那为什么不直接使用String数组呢?好问题。数组确实可以存储相同数据类型的数据,但是当你想无限制地存储元素时,数组也有它的缺点。数组的长度是固定的,不可变的。总而言之,数组使用起来不方便,所以才有了集合,集合就有了这样的问题,然后泛型集合就出现了。这里使用了泛型,所以我们在add()的时候,添加的元素在编译的时候会进行类型检查,在获取集合元素的时候,不需要强制进行类型转换,直接使用指定的类型接收起来即可。使用泛型有什么好处?不需要强制类型转换(集合、反射)来增加代码的可读性。我们可以通过泛型来知道我们现在操作的是什么数据类型。一句话,给人看。代码复用,可以根据不同的情况传入不同的数据类型,进行不同的操作。通用类定义语法:class类名{privatewildcardvariablename;...}通配符:T、E、K、V,就是上面说的类型参数。(这里的通配符也叫通用标识符)使用语法:类名<具体数据类型>对象名=新类名<具体数据类型>();类名<特定数据类型>对象名=new类名<>();//从JDK7开始,可以省略。人们称之为菱形句法。例如:/**定义一个泛型类*/publicclassGeneric{privateTvariable;publicvoidsetVariable(Tvariable){returnthis.variable=variable;}publicTgetVariable(){返回变量;}}testGenericg=newGeneric<>();//指定泛型类型为Stringg.setVariable("god23bin");//正常g.setVariable(23);//提示错误,因为这里是一个int类型Stringvar=g.getVariable();注意事项:使用泛型类时,如果不指定数据类型,将默认为Object类型泛型的类型参数只能是引用数据类型,不支持基本数据类型。泛型在逻辑上,你操作的是不同的数据类型,但实际上,它们还是同一个类型(比如上面例子中的Generic类,泛型指定了不同的数据类型,逻辑上是不同的,但实际上它们仍然是Generic类型,这涉及“类型擦除”)如果有继承://如果子类需要是一个泛型类,那么它的类型参数需要包含父类的类型参数classChildGenericextendsGeneric{}//OKclassChildGenericextendsGeneric{}//OK//子类不是泛型类,所以需要明确父类的类型参数classChildGenericextendsGeneric{}通用接口定义语法:interface接口名{wildcard方法名();...}使用语法://接口实现类是一个泛型类,所以实现类的类型参数需要包含接口参数的类型classDemoimplementsGeneric{}//OKclassDemoimplementsGeneric{}//OK//接口实现类不是泛型类,所以接口类型参数需要指定classDemoimplementsGeneric{}之前是泛型方法在类和接口上定义。然而,有时,我们不需要整个类来定义类型。我们只需要定义泛型类型的其中一个方法,并且只关心这一个方法,那么就可以在方法上使用泛型定义,这样在调用泛型方法时,指定具体的类型参数。定义语法:访问修饰符返回值类型方法名(参数列表){//方法体}例如:publicvoidgetGeneric(){//方法体}publicvoidgetGeneric(Gamegame){//方法体}这里要注意泛型方法不同于泛型类中使用泛型的普通方法。//这是泛型类中使用泛型的普通方法publicTgetVariable(){returnvariable;}//这是泛型方法,只有定义的方法才是泛型方法publicvoidgetGeneric(){//方法体}而且,如果在泛型类中定义泛型方法,那么泛型方法中的类型参数和泛型类型类上的类型参数是彼此不同且独立。还有,泛型方法可以定义为静态的,这还不算完,泛型方法还可以结合可变参数。例如:/**定义泛型类*/publicclassGeneric{privateT变量;publicvoidsetVariable(Tvariable){returnthis.variable=variable;}publicTgetVariable(){返回变量;}//泛型方法,这里的T和类上的T不一样publicTgetGeneric(Listlist){returnlist.get(0);}//静态泛型方法publicstaticTgetGenericStatic(Listlist){returnlist.get(0);}//结合可变参数的泛型方法publicstaticvoidprint(E...e){//这里参数可以作为数组遍历for(Eelem:e){System.out.println(元素);通配符问号前出现的T、E、K、V也是通配符,但是,这些通配符是属于类型参数的通配符。类型参数的通配符呢?它来了!类型参数通配符:?。没错,你没有看错,只是一个问号。类型参数的通配符是使用?表示特定类型参数,表示任何类型。例如:publicclassGeneric{...publicstaticvoidshowGame(Gamesgames){//Games指定的类型必须是StringStringone=games.getOne();System.out.println(一);}}Games指定的类型必须是String。然后我们这样操作:Genericg=newGeneric<>();Gamesgames=newGames();g.showGame(games);//OKGamesgames2=newGames();g.showGame(games2);//错误,使用?通配符,因为指定了StringpublicclassGeneric{...publicstaticvoidshowGame(Gamesgames){//使用类型参数通配符?字符串一=games.getOne();System.out.println(一);}}通配符类型的上限和下限有些地方也叫上限和下限,也叫有限通配符,意思是一样的。上限语法:class/interface这里的extends可以这样理解,,使用的时候,我们传入的实际参数类型需要小于等于类A,也就是需要是A的子类或者A本身,这个限制了通配符的上限,而你只能属于A类。下限语法:class/interface这里的super可以这样理解,,使用的时候,我们传入的实际参数类型需要大于等于classA,也就是需要是A的父类或者A本身,这个限制了通配符的下限,你至少只能是A。比如有A、B、C三个类,A是B的父类,B是C的父类。publicclassDemo{publicstaticupperLimit(Listlist){//类型实参通配符上限为类型B//...}publicstaticlowerLimit(Listlist){//类型实参引用通配符的下限是类型B//...}}调用这个方法Listl1=newArrayList<>();Listl2=newArrayList<>();Listl3=newArrayList<>();Demo.upperLimit(l1);//报错,这里传入了l1,上面设置了通配符的上限,超过了类别B,高于类别B。Demo.upperLimit(l2);//OKDemo.upperLimit(l3);//OKDemo.lowerLimit(l1);//OKDemo.lowerLimit(l2);//OKDemo.lowerLimit(l3);//错误,同样是低于B类,自然是错误的,需要低于B类class方面,只能超过B类,需要注意的是你设置了通配符的上限.在集合中,只能用来读取数据,不能存储数据。这个应该怎么理解?publicclassDemo{publicstaticupperLimit(Listlist){//类型参数通配符的上限为Btypelist.add(newB());//错误列表.add(newC());//报错//因为我们使用了上限通配符,所以不知道传入的是什么类型的List,可能是List,也可能是List//所以无法存储数据}publicstaticlowerLimit(Listlist){//类型参数通配符的下限是类型B//...}}下限呢?不用担心,下限没有这个问题,可以存储数据publicclassDemo{publicstaticupperLimit(Listlist){//类型参数通配符的上限是classB//...}publicstaticlowerLimit(Listlist){//类型实参通配符的下限为Btypelist.add(newB());list.add(新C());//因为有下限通配符,所以只限制了下限,而上限是没有限制的,也就是说可以看成上限是Object//上限是Object,那么任何类都默认继承Object,那么自然可以添加C类型的数据//即存储数据的类型没有限制。for(Objecto:list){System.out.println(o);}}}类型擦除泛型的局限只存在于编译期,一旦运行就消失,即类型被擦除。有两种情况:UnrestrictedTypeErasureRestrictedTypeErasureUnrestricted:Restricted:泛型方法上的类型擦除也是如此。还有一个知识点就是在泛型接口的类型擦除中,会出现一个“桥接方法”,它主要维护接口和类之间的实现关系。以上就是泛型的基本内容。面试题开始复习八股文了!!!什么是Java泛型?常用的通配符有哪些?泛型是JDK5引入的新特性,提供了编译时类型安全检测机制。这种机制可以在编译时检测非法数据类型。本质是参数化类型,即可以将操作的数据类型指定为特定的参数类型。常用的通配符有T(Type)、K(Key)、V(Value)、E(Element)、?(未知类型)Java的泛型是如何工作的?什么是类型擦除?(什么是泛型擦除?)Java的泛型是伪泛型,因为在Java运行时,这些泛型信息会被擦除,也就是所谓的类型擦除(genericerasure)。什么是泛型中合格和不合格的通配符?限定通配符,顾名思义,就是限制类型。Java中有两种有限的通配符。一个是,通过保证类型必须是T的子类来限制上界,即类型必须是T类型或者T的子类。另一个是,保证类型必须是T的父类,用于限制下一个类,即类型必须是T类型或者T的父类<?>代表一个非限制通配符,因为可以替换为任何类型。你在项目中的什么地方使用泛型?可用于定义公共返回结果类CommonResult。通过参数T,可以根据具体的返回类型动态指定结果的数据类型,用于构建采集工具类。参考Collections中的sort和binarySearch方法。最后部分受限于本人水平,难免有错误和不足之处。如果你发现了什么,请指出!最后,感谢您阅读本文,感谢您认真对待我的努力,希望这篇博客对您有所帮助!你轻轻竖起大拇指,那会为我心中的世界增添一颗璀璨耀眼的星!