泛型笔记
泛型
泛型类
定义形式 修饰符 class 类名 \< T,K,V… {} 其中T,K,V是自定义的随便写什么,但一般建议就写K、V、T等一个大写字母,毕竟本身含义就是泛型,没必要用特殊的标识符啥的定义。
两边的空格数没有规定,连在一起或者分开都行 一般就与类名挨着,与左大括号间隔一个空格。泛型只在编译阶段起作用,运行阶段不起作用
定义与使用
package ljb; |
类定义了泛型后,成员变量和成员方法就可以使用这个泛型了。在创建Test对象时,可以指定具体类型,上面的代码中就是指定T为String类型,那么Test类中的name也就是String类型了。Getter和Setter方法中的T也就是String类型了。但是如果没有指定,则T默认为Object类型
指定String类型
未指定类型
未指定具体类型时,Getter得到的也是Object类型对象,使用获取到的返回对象时就要进行强转。一般使用泛型类时是需要指定类型的,你不指定,就没有必要用泛型,我直接把类的成员变量和成员方法返回值全定义成Object类型不就是了。
泛型方法
定义形式 修饰符
返回类型 方法名(参数列表){}
定义与使用
package ljb; |
主要看上面的19-20行 首先要明白泛型方法和非泛型方法的区别,泛型方法有一个
注意:
在上面的代码中,我故意把泛型方法returnObj的返回类型也写成了T,但这个T和类上那个T不是同一个T。就是说创建对象时如果指定泛型为String,并不意味着我的泛型方法返回值就是String。但是如果我的泛型方法泛型列表中没有定义T,那么这个T就是类上的T。(在代码中ctrl+左键点过去)
总结
泛型类是在类上声明一个泛型列表 就是
泛型方法是在方法上声明一个泛型列表 就是
泛型接口
泛型接口和泛型类类似的。
有个要注意的点,泛型接口的实现类和实现接口时,如果没有声明泛型,实现接口的方法时,不管是泛型方法还是非泛型方法,泛型返回值会默认成Object类型,像下面这样 public class Test implements JK1
package ljb; |
实现接口时如果指定了接口泛型的具体类型,那么实现的方法类型就是指定的类型,注意接口中的泛型方法的泛型不一定是指定的类型。泛型方法的注意那里讲了。
package ljb; |
前面两种情况的代码,实现类并没有声明泛型列表,可以写也可以不写,随便写什么字母,并且与接口没关系。但如果实现接口时,还是将接口指定为泛型,那么实现类就必须要写泛型列表,并且列表中必须有一个与接口泛型相同的字母(泛型名)
package ljb; |
也就是说,在创建实现类对象时,确定接口泛型具体类型。
类型通配符 “ ?”
有一个需求,要求定义一个方法 要能处理以下两个种类型
/**
由于markdown识别不了尖括号<> 因此我写在代码块中
List<Integer>
List<Float>
**/
没学泛型之前,想到的办法可能就是进行重载,但是此处会有问题。
package ljb; |
可以看到,我对方法fun1进行了重载,我认为的是List < Integer>和List < Float> 是两个不同的类型,所以构成重载。但是实际上它俩的类型是一样的(类型擦除),代码会发生编译错误。那怎么办呢?可不可以试一下继承?Integer和Float都是Number的子类,我定义一个List< Number > 行不行?结果是并不行。(具体为啥我也不是很清楚,后期再补充吧。)
但是我们学了泛型啊,可以使用泛型来解决吗?答案是可以的
package ljb; |
但是有没有想过,但是我们在fun1方法中根本没办法通过引用变量t(13行)来调用对象的一些成员方法。就比如下面的代码
package ljb; |
第17行会出现编译报错。那操作也太局限了,我总不能在fun1中把t强转成Student然后调用getName方法吧,因为它的实际类型是根据传过来的实参确定的,并不一定是Student类型啊! 怎么办? 其实这里可以利用反射机制进行操作。我们可以获取t的Class对象,然后反射它的成员方法并调用。代码如下
package ljb; |
反射的内容有一篇笔记有介绍,这里不再多说。但是使用反射显然有点麻烦,最终还是要靠我们的通配符”?”来解决。
通配符的使用
通配符 “ ? “ 是在为作为形参的泛型类指定泛型时使用。两者具体差别用代码展示
package ljb; |
在单独使用类型通配符或者泛型时,两者的区别不大。通配符刚开始那里有提出过一个需求,那个需求也能通过通配符解决。但是泛型可以支持添加元素,通配符不行。说白了,通配符就是能够接受所有类型。
说明一下上面的代码14所谓的不能强转,我的意思是,在fun1方法中,并不知道o的具体类型,所以无法强转为o的具体类型。但是不代表我不能在里面这样写 String str=(String) o; 因为我显然知道传进去的是String类型,这样写肯定没问题,能用运行成功。
并且对于fun2而言,我知道o就是T类型,所以也没有必要用Object object = list.get(0); 直接就用T t = list.get(0);就行
通配上下限
乍一看,感觉通配符还不如泛型好用啊?但是jdk为通配符提供了定义通配上限和下限的操作,让操作变得更灵活了。
通配上限
/**
List<? extends Aniaml> list //假设有这个关系 MinCat extends Cat extends Aniaml
当形参定义为上述类型时,可以传入List<Aniaml及Aniaml子类>,但是仍然无法进行添加操作,你想一下,方法中只能确定传过来的集合元素类型范围是Aniaml及其子类,我要如果我要添加一个Cat类型的元素,可能集合元素类型是MinCat,那么必然报错啊(你可以写MinCat minCat = (MinCat) new Cat(); 虽然编译不报错,但运行就报错了),就多态的知识。
**/
通配下限
/**
List<? super MinCat> list
当形参定义为上述类型时,可以传入List<MinCat及MinCat父类>,可以进行添加元素操作,但只能添加MinCat及MinCat子类类型的元素。你想一下,不管List的元素是什么类型,至少是MinCat或MinCat的父类,根据多态,子类可以转换成父类
**/
区别
通配符更像是一个正则表达式符号,可以匹配所有的类型,并不能作为一种类型进行变量或方法返回值的声明。
而泛型就一种真正的类型,可以声明为变量和方法返回值的类型。只不过它的实际类型是要根据传过来的参数实际类型进行确定。
类型擦除
看下面的代码,输出都是ArrayList类型型,输出结果为真。这与泛型是String还是Integer无关。
类型擦除在编译阶段完成
补充
时间:2023/8/4 16:00
学习红黑树时遇到下面这种写法
static class RBNode<K extends Comparable<K>,V> {
}是一个静态内部类(不重要),整体的一个形式和我们之前定义泛型类时是一致的,就是声明一个泛型列表 < K, V,T … > 然后在类内部就能用这样字母声明变量和方法。只不过这里它给其中的泛型K做了一个约束,就是必须是Comparable接口的子类。
这里很奇怪,明明Comparable是一个接口,为什么用extends? 语法就是这样,没有为什么。这就是Java提供了一种语法来限制泛型的可实现类。它的语法规则就是下面这样
<T extends anyClass> //这里的anyClass可以是接口,也可以是类。反正T必须实现或者继承anyClass这可不要瞎用,不要与 通配符的上限写法搞混了
public void fun1(List<? extends Aniaml>); //通配符这样用
public <T> void fun2(List<T extends Animal>); //下面这个不可以这样乱搞 错误!!!!!!
//仿照用在类上时的写法
public <T extends Animal> void fun2(List<T>); //传给fun2的list列表的的元素就必须继承Animal类,否则编译报错时间:2023/8/4 19:00
静态方法中不能使用类定义时的泛型
网上的解答:因为静态方法属于类的信息,而不是实例对象的信息。静态方法的加载先于类的实例化,也就是说类中的泛型还没有传递真正的类型参数,静态的方法就已经加载完成了(方法的参数类型,返回值类型啥的都需要提前确定好)。所以静态方法不能使用泛型
对于网上的解答还是没让我弄明白,泛型不是在编译期就被擦除了吗,不是都为Object类型了吗?那类上面为啥就能使用泛型了?类不是也得先加载吗?还有静态方法为什么又可以定义为泛型方法。对于这些可能还得再看看JVM的知识。