泛型解析
前言
整理了Java泛型的相关知识,算是比较基础的,希望大家一起学习进步。
一、什么是Java泛型
Java 泛型(generics)是 JDK 5 中引入的一个新特性,其本质是参数化类型,解决不确定具体对象类型的问题。其所操作的数据类型被指定为一个参数(type parameter)这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
泛型类
泛型类(generic class) 就是具有一个或多个类型变量的类。一个泛型类的简单例子如下:
1 | //常见的如T、E、K、V等形式的参数常用于表示泛型,编译时无法知道它们类型,实例化时需要指定。 |
运行结果如下:
1 | 泛型测试,first is 1 ,second is 第二 |
泛型接口
泛型也可以应用于接口。
1 | public interface Generator<T> { |
实现类去实现这个接口的时候,可以指定泛型T的具体类型。
指定具体类型为Integer的实现类:
1 | public class NumberGenerator implements Generator<Integer> { |
指定具体类型为String的实现类:
1 | public class StringGenerator implements Generator<String> { |
泛型方法
具有一个或多个类型变量的方法,称之为泛型方法。
1 | public class GenericMethods { |
运行结果:
1 | java.lang.String |
二、泛型的好处
Java语言引入泛型的好处是安全简单。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。
我们先来看看一个只能持有单个对象的类。
1 | public class Holder1 { |
我们可以发现,这个类的重用性不怎样。要使它持有其他类型的任何对象,在jdk1.5泛型之前,可以把类型设置为Object,如下:
1 | public class Holder2 { |
我们引入泛型,实现功能那个跟Holder2类一致的Holder3,如下:
1 | public class Holder3<T> { |
因此,泛型的好处很明显了:
- 不用强制转换,因此代码比较简洁;(简洁性)
- 代替Object来表示其他类型对象,与ClassCastException异常划清界限。(安全性)
- 泛型使代码可读性增强。(可读性)
三、泛型通配符
我们定义泛型时,经常碰见T,E,K,V,?等通配符。本质上这些都是通配符,是编码时一种约定俗成的东西。当然,你换个A-Z中另一个字母表示没有关系,但是为了可读性,一般有以下定义:
- ? 表示不确定的 java 类型
- T (type) 表示具体的一个java类型
- K V (key value) 分别代表java键值中的Key Value
- E (element) 代表Element
为什么需要引入通配符呢,我们先来看一个例子:
1 | class Fruit{ |
我们可以发现,Fruit[]与Apple[]是兼容的。List<Fruit>
与List<Apple>
不兼容的,集合List是不能协变的,会报错,而List
- 无边界通配符,如List<?>
- 上边界限定通配符,如<? extends E>;
- 下边界通配符,如<? super E>;
?无边界通配符
无边界通配符,它的使用形式是一个单独的问号:List<?>,也就是没有任何限定。
看个例子:
1 | public class GenericTest { |
无界通配符(>)可以适配任何引用类型,看起来与原生类型等价,但与原生类型还是有区别,使用 **无界通配符则表明在使用泛型** 。同时,List> list不可以添加任何类型,因为并不知道实际是哪种类型。但是List list因为持有的是Object类型对象,所以可以add任何类型的对象。
上边界限定通配符 < ? extends E>
使用 <? extends Fruit> 形式的通配符,就是上边界限定通配符。 extends关键字表示这个泛型中的参数必须是 E 或者 E 的子类,请看demo:
1 | class apple extends Fruit{} |
但是,以下这段代码是不可行的:
1 | static int sumWeight1(List<? extends Fruit> fruits){ |
- 在
List<Fruit>
里只能添加Fruit类对象及其子类对象(如Apple对象,Oragne对象),在List<Apple>
里只能添加Apple类和其子类对象。 - 我们知道
List<Fruit>、List<Apple>
等都是List<? extends Fruit>的子类型。假设一开始传参是List<Fruit> list
,两个添加没问题,那如果传来List<Apple> list
,添加就失败了,编译器为了保护自己,直接禁用添加功能了。 - 实际上,不能往List<? extends E> 添加任意对象,除了null。
下边界限定通配符 < ? super E>
使用 <? super E> 形式的通配符,就是下边界限定通配符。 super关键字表示这个泛型中的参数必须是所指定的类型E,或者是此类型的父类型,直至 Object。
1 | public class GenericTest { |
可以发现,List<? super E>添加是没有问题的,因为子类是可以指向父类的,它添加并不像List<? extends E>会出现安全性问题,所以可行。
四、泛型擦除
什么是类型擦除
什么是Java泛型擦除呢?
先来看demo:
1 | Class c1 = new ArrayList<Integer>().getClass(); |
1 |
|
public class GenericTest
private T t;
public T get() {
return t;
}
public void set(T t) {
this.t = t;
}
public static void main(String[] args) {
GenericTest<String> test = new GenericTest<String>();
test.set("jay@huaxiao");
String s = test.get();
System.out.println(s);
}
}
/* Output
jay@huaxiao
*/
1 |
|
public class generic.GenericTest
public generic.GenericTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object.”
4: return
public T get();
Code:
0: aload_0
1: getfield #2 // Field t:Ljava/lang/Object;
4: areturn
public void set(T);
Code:
0: aload_0
1: aload_1
2: putfield #2 // Field t:Ljava/lang/Object;
5: return
public static void main(java.lang.String[]);
Code:
0: new #3 // class generic/GenericTest
3: dup
4: invokespecial #4 // Method “
7: astore_1
8: aload_1
9: ldc #5 // String jay@huaxiao
11: invokevirtual #6 // Method set:(Ljava/lang/Object;)V
14: aload_1
15: invokevirtual #7 // Method get:()Ljava/lang/Object;
18: checkcast #8 // class java/lang/String
21: astore_2
22: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
25: aload_2
26: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
29: return
}
1 | - 看第11,set进去的是原始类型Object(#6); |
if(a instanceof Pair
if(a instanceof Pair
Pair
Pair
Pair
if(stringPair.getClass() == employeePair.getClass()) //会得到true,因为两次调用getClass都将返回Pair.class
1 |
|
Pair
1 |
|
public Pair() { first = new T(); second = new T(); } // Error
1 | #### 使用泛型接口时,需要避免重复实现同一个接口 |
interface Swim
class Duck implements Swim
class UglyDuck extends Duck implements Swim
1 | #### 可以消除对受查异常的检查 |
@SuppressWamings(“unchecked”)
public static <T extends Throwable〉void throwAs(Throwable e) throws T { throw (T) e; }
1 | #### 定义API返回报文时,尽量使用泛型; |
public class Response
private static final long serialVersionUID = -xxx;
private T data;
private String code;
public Response() {
}
public T getData() {
return this.data;
}
public void setData(T data,String code ) {
this.data = data;
this.code = code;
}
}
### 六、Java泛型常见面试题
Java泛型常见几道面试题
- Java中的泛型是什么 ? 使用泛型的好处是什么?(第一,第二小节可答)
- Java的泛型是如何工作的 ? 什么是类型擦除 ? (第四小节可答)
- 什么是泛型中的限定通配符和非限定通配符 ? (第三小节可答)
- List<? extends T>和List <? super T>之间有什么区别 ?(第三小节可答)
- 你了解泛型通配符与上下界吗?(第三小节可答)
### 参考与感谢
- 《Java编程思想》
- 《Java核心技术》
- [聊一聊-JAVA 泛型中的通配符 T,E,K,V,?](https://juejin.im/post/5d5789d26fb9a06ad0056bd9)
- [Java泛型的局限和使用经验](https://www.jianshu.com/p/a58da9011f85)
- [Java泛型之类型擦除](https://zhuanlan.zhihu.com/p/31741402)
### 个人公众号

- 如果你是个爱学习的好孩子,可以关注我公众号,一起学习讨论。
- 如果你觉得本文有哪些不正确的地方,可以评论,也可以关注我公众号,私聊我,大家一起学习进步哈。