当前位置: 首页 > 科技观察

精通Java,不懂泛型?

时间:2023-03-17 18:21:43 科技观察

本文转载自微信公众号《小菜两集》,作者蔡不才。转载本文请联系小菜良记公众号。既然我们在上面提到了泛型,那么让我们在这篇文章中回顾一下泛型!一、认识泛型在泛型出现之前,我们通常使用Object类型的元素对象。比如我们可以构建一个Object类型的集合,它可以存储任何数据类型的对象,但是当我们从集合中取出元素时,我们需要清楚的知道存储的每个元素的数据类型,这样才能进行元素转换执行,否则将发生ClassCastException。一、什么是泛型泛型是Java1.5之后引入的一个新特性,它提供了一种编译时类型安全的监控机制,可以让我们在编译时检测到非法类型的数据结构。它的本质是参数化类型,即将要操作的数据类型指定为参数。这种参数类型可以用在类、接口和方法中,分别称为泛型类、泛型接口和泛型方法。在运行时不会有ClassCastException异常。消除强制类型转换从泛型集合中取出元素,我们不需要进行类型转换,可读性更强,可以直接看到集合中存放的是什么数据类型的元素。2.泛型的使用1.使用场景1)泛型类型类基本语法privateTdata;}注:Java1.7之后,types可以推断<>中的具体数据类型可以省略:类名<具体数据类型>对象名=新类名<>();如果我们不使用<>来制定数据类型,那么操作类型就是Object。泛型中的类型参数只能是类类型,不能是基本数据类型,比如int、double、float……当我们传入不同的数据类型构造对象时,逻辑上可以看出是一个数不同的数据类型,但实际上它们都是相同的类型。以上就是泛型类的简单用法。我们要使用哪种类型,我们在创建的时候指定类型。当我们使用它的时候,类会自动转换成用户想要使用的类型。那么如果我们定义了一个泛型类,但是在构造对象的时候没有声明数据类型,那么默认就是Object类型,取出数据的时候需要进行类型转换:ResultobjectRes=newResult("testObejct");Stringstr=(String)objectRes.getData();System.out.println(str);规则:子类也是泛型类,那么子类和父类的泛型类型必须一致publicclassResultChildextendsResult{}子类不是泛型Type类,那么父类必须指定数据类型publicclassResultChildextendsResult{}2)泛型接口基本语法publicinterfacename{genericidentifiermethodname();...}使用示例publicinterfaceResultInterface{TgetData();}与泛型类一样,泛型接口有如下规则:实现类不是泛型类,接口必须指定数据类型。实现类也是泛型类,实现类和接口3)泛型方法在Java中,泛型类和泛型接口的定义比较简单,但是泛型方法的定义比较复杂。泛型类是在实例化类时指定泛型类型的具体类型泛型方法是在调用方法时指定泛型类型的具体类型基本语法修饰符返回值类型方法名称(参数列表){}修饰符和返回值类型之间用于将此方法声明为泛型方法。只有声明了的方法才是泛型方法,即使返回值类型中的泛型方法类型类的泛型成员方法不是泛型方法表明该方法会使用泛型类型T,只有这样才能在方法privateResultgetResult(Tdata){returnnewResult(data);}中使用泛型类型T泛型方法和可变参数:privatevoidprintData(T...data){for(Tt:data){System.out.println(t);}}注意:泛型方法可以使方法的改变独立于类。static(静态)方法如果要使用泛型能力,必须做成泛型方法2.类型通配符1)什么是类型通配符类型通配符一般用“?”代替具体的实参类型,类型通配符是实际参数类型,而不是形式参数类型。类型通配符分为类型通配符上限和类型通配符下限2)基本语法类型通配符上限:class/interface实参类型>class/interface注:需要的泛型可以只能是实参类型,或者是实参类型的子类类型。类型通配符的下限:class/interface注意:这个是必须的泛型只能是实参类型,或者是实参类型的父类型2)使用上限示例类型通配符:如果我们想打印列表的值,我们可以这样做:privatevoidprintData(Listlist){for(inti=0;ilist){for(Objecto:list){System.out.println(o);}}但是这个定义太宽泛了,Object都是所有的父类类型,如果我觉得这个方法只能对number类型的元素进行操作,那么我可以使用类型通配符的上限来解决这个问题:privatevoidprintData(ListnumList){for(Numbernumber:numList){System.out.println(number);}}但是当我们需要用到这个方法的时候,我们可以直观的看到这个方法传递的实际参数只能是Number的子类。printData(Arrays.asList(1,2,3));printData(Arrays.asList(1L,2L,3L));类型通配符的下限:上面我们学习了类型通配符上限的使用,那么如何使用上限类型通配符的上限呢?typewildcard在我们平时的开发中用的比较少。编译器只知道集合元素是下限的超类型,但不确定是哪个超类型。因此,从集合中取元素只能作为Object类型处理(编译器无法确定取的是哪个父类对象)。我们可以自定义一个复制集合的函数:首先定义两个类:publicclassAnimal{}publicclassPigextendsAnimal{}定义一个复制函数:privatestaticCollectioncopy(Collectionparent,Collectionchild){for(Tt:child){parent.add(t);}returnparent;}使用:Listanimals=newArrayList<>();Listpigs=newArrayList<>();pigs.add(newPig());pigs.add(newPig());copy(animals,pigs);System.out.println(animals);3.类型擦除因为泛型信息只存在于代码编译阶段,在进入JVM之前,泛型相关的信息都会被擦除,称为类型擦除1)无限类型擦除类型擦除前:publicclassResult{privateTdata;}后类型擦除:publicclassResult{privateObjectdata;}2)受限类型擦除类型擦除前:publicclassResult{privateTdata;}类型擦除后:publicclassResult{privateNumberdata;}3)类型擦除前擦除方法中定义的参数类型:privateTgetValue(Tvalue){returnvalue;}类型擦除后:privateNumbergetValue(Numbervalue){returnvalue;}