笔记源代码地址:B站 库米豪斯巴达锅铲祖师 空间
泛型先讲因为经常用到 B站笔记没东西是因为动力节点就教我这么点,要不是我到处翻源码我都不知道原来泛型还有这么难的qwq 集合的源码详解肯定没完,讲的时候会顺便把数据结构讲了所以将会很难
纯理论就不放图了,放这么多图会使up操作量巨大
泛型的理解
目前我们最常见的泛型是集合的泛型,以后也是。 那你想一下,你有一个集合,是这样存对象的:{String, Integer, String, Thread, Amazing}
这样的不仅看起来烦,而且使用时也烦,于是我们就要拿出泛型。
泛型可以将集合中的存储类型限定为某一个特定类型及其子类,使集合中的元素更统一,集合的功能更明显,而且避免了很多麻烦的向下转型,总而言之就是操作非常方便
同时,限定元素类型也成为了它的缺点,元素缺乏多样性,这个缺点要往后学,见过几个特定的例子才能理解
理论上,任何类或接口被定义时都可以指定泛型。可以指定一个,也可以指定多个(如后面接触的HashMap
会有两个泛型)
泛型的使用
创建带泛型的类对象
对于定义过泛型的类,我们可以这样使用泛型。拿ArrayList来举例:
List<String> strs = new ArrayList<String>();
然后我宣布,这个ArrayList从现在开始只能存String及其子类了
我们观察一下使用的过程: 我们的目的是让这个List只能存字符串,所以在声明类型的时候,用尖括号声明一下泛型String
但是这样好像还不够,因为ArrayList还不知道他爹的泛型是什么,所以我们还要在ArrayList构造器的类名后面告诉他泛型是String,但其实Java里面有一种语法叫做钻石表达式,可以自动推断泛型。上面那句代码还要更一般的写法:
List<String> strs = new ArrayList<>();
这里的ArrayList有一个泛型标记,尽管里面没有任何东西,但是实际运行时,Java会往前找到泛型是String,所以这个ArrayList的泛型就是String
<?> 标记的作用
这里的?
可以表示任意一种引用数据类型,且在它的作用范围内代表同一个类
它还有一个专业的名字,叫做:类型通配符
举一个常见的例子:你有3个集合:
List<String> ls1, List<Number> ls2, List<Integer> ls3
你要写个方法遍历这3个集合,这时候我们可能会想到方法重载的机制,于是你会这么搞(只展示其中一个方法):
public static void iterateList (List<String> ls) {
Iterator<String> it = ls.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
但是你这样用了3个泛型写完3个重载方法的时候,你发现又出现两个不同泛型的集合,也需要遍历,然后你烦死了不想写了,然后你把项目删掉了(啊这。。。)
Java为了避免这种情况,避免Java程序员越来越少,想出了一个办法:我们可以把方法中的泛型改为?
,就只要写一个方法。就像这样
public static void iterateList (List<?> ls) {
Iterator<?> it = ls.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
这里的?
可以表示任何数据类型,取决于你传入的集合的泛型是什么。于是这些需要重载的方法通过一个方法全都解决了而且不用强转
泛型中指定子类或父类
现在我们需要一个方法,需要保证集合中的每一个元素都是数字类型(为了举例方便,假设需求算法还是迭代),然后我们根据上一小模块的办法写出了这个方法:
public static void addList (List<?> ls) {
Iterator<?> it = ls.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
然后有人使用时,传入了一个List<Thread> ls
,需求就出错了,所以我们要规范一下,这里集合的泛型只能是数字。了解到Java中包装类的父类是Number
之后,我们只需要保证传入的集合的泛型都是它的的子类,就有了这种语法:
public static void addList (List<? extends Number> ls) {
Iterator<?> it = ls.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
这样写的话,如果我们传入Object,是会给我们报错的。但传入一个Integer,就没有问题。如果要使用Number的父类,可以将extends改成super:
public static void addList (List<? super Number> ls) {
Iterator<?> it = ls.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
此时传入Integer出错,而Object正常运行
泛型方法
与 <?>
相似,另一种使用泛型的办法是:在方法中指定泛型标记,然后整个方法看到这个标记的类就会认定为泛型。这种带泛型标记的方法也有专有名词,叫做:泛型方法
这么说可能难以理解,直接表演
public static <T> void addList (List<T> ls) {
Iterator<T> it = ls.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
如果不加泛型标记,大家都可以看出来它会报错,因为你没有定义T这个类,所以在修饰符列表和返回值之间 加一个泛型标记,就可以识别了
这个泛型标记和 <?>
标记总体是相似的,但是泛型标记多了一个功能,它可以foreach,比如这段代码就是不报错的:
public static <T> void addList (List<T> ls) {
for (T t : ls) {
System.out.println(t);
}
}
另外,泛型方法也可以指定子类父类关系
拓展
考虑到一些初学者可能想不到,这里举个例子来说明
我们都知道,泛型可以指代任何一个引用数据类型,不管用在哪里,怎么用,所以我们可以这样:将上面的例子中的集合改为数组,然后遍历数组。
让我看看会不会有人先去把数组转换成集合或者又开始写重载方法了?ovo
首先定义一个数组(注意是引用数据类型)
Integer[] ints = {1, 2, 3, 4, 5}
简单地大改一下上面的方法,就可以遍历这种数组了
public static <T> void addList (T[] arr) {
for (T t : arr) {
System.out.println(t);
}
}
当然现在改成 <?>
就不可以了,因为这个标记一定会带两个尖括号,然后我们现在找不到可以加尖括号的地方
自定义泛型类
作为一个标准的程序员,我们肯定要知道,除了使用泛型之外,自己还要会造泛型。接下来的操作分两步走,然后我会直接把成果放上来(其实好像就是重新学一遍面向对象因为真的很简单)
定义泛型类
我们按常规定义出一个类,并且想要它变成一个泛型类。这个时候我们需要用到泛型标记,此处的泛型标记应当放在类名后面
class MyGenericity <T> {
}
当然对接口也是适用的,抽象类不用想也可以
需要注意的是,自定义泛型是有命名规范的。这里没有具体要求所以使用T,其他的名称及推荐使用场合在下面:
- E:表示元素(Elements),通常用在集合里,一般程序员接触最少
- T:表示类型(Type),如果发现其他几个名称都不合适,这个就可以用上了
- K:表示键(Key),通常与V联用,通常用在Map集合里,接触较少
- V:表示值(Value),通常与K联用,通常用在Map集合里,接触较少
- N:表示数值(Number),与字面量类似,在需要计算的场合可能用到
Java源码中还用到过的名称有:
- X:可能表示异常,因为原文是
<X extends RuntimeException>
,而且读音与Exception头部相似 - U:根据大量网上的解释,可能与T用法相同,源码中也有注释备注:T和U分别表示第一个元素和第二个元素
- S:没有在源码中找到,根据大量网上的解释,可能与T用法相同
- R:可能表示返回值,因为注释中有写返回值,也正好是return的首字母
这里备注一条很好的查找路径,就是之前讲ArrayList时叫你们自己看的抛出异常的方法:ArrayList的get方法 -> Objects类的checkIndex方法 -> 可以一直往下点,会看到很多泛型,其中有一个叫BiFunction的接口是个三泛型的接口
现在我们只需要以为自己定义了一个类叫做T,然后正常使用就可以了
完善剩下部分
现在就直接放结果了,因为这种东西讲了肯定会有人说我水文章长度
class MyGenericity <T> {
// 变量
private T field;
// setter
public void setField (T field) {
this.field = field;
}
// getter
public T getField () {
return this.field;
}
// 无参构造器
public MyGenericity () {
}
// 泛型类型参数的有参构造器
public MyGenericity (T field) {
this.field = field;
}
// 使用了泛型的泛型方法
public <U> void method (T InnerMethod, U another) {
Object o = another;
System.out.println(InnerMethod);
System.out.println(o);
}
}
finally——
对不起每天拖更qwq最近up感冒了,学考两天都不知道自己在想什么,看资料也看不进去,死人一样
萌新程序员的思想要多加训练,灵活一点,看到一个特性咱们要能自己推断出来它可能还可以怎么用。
最有用的训练方法当然是看源码啦ovo自己做练习项目也是很有必要的