什么是泛型
泛型即参数化类型,将类型由原来的具体类型参数化,类似方法中的变量参数。
提供编译期的安全检测机制,JDK 5 引入
泛型通配符
- 限定通配符:
- E:元素
- K:键
- N:数字
- T:类型
- V:值
- S、U、V 等:多参数情况中的第 2、3、4 个类型
- 非限定通配符: ?
某些情况下,编写指定未知类型的代码很有用。问号 (?) 通配符可用于使用泛型代码表示未知类型。通配符可用于参数、字段、局部变量和返回类型。但最好不要在返回类型中使用通配符,因为确切知道方法返回的类型更安全。
泛型通配符
? extends T
描述了通配符的上界,即具体的泛型参数必须是 T 类型或者 T 的子类型
List<? extends Number> numberArray = new ArrayList<Number>(); // Number 是 Number 类型的
List<? extends Number> numberArray = new ArrayList<Integer>(); // Integer 是 Number 的子类
List<? extends Number> numberArray = new ArrayList<Double>(); // Double 是 Number 的子类
读取:numberArray 中可以读到 Number 对象,因为里面存放的元素必须是 Number or Number 的子类型
写入:不能写入,因为 numberArray 可以是子类型的任一类型,编译器不知道写入哪种,Double 类型的元素不能写入到 ```List``
? super T
描述了通配符的下届,即具体的泛型参数必须是 T 类型或者它的父类
// 在这里, Integer 可以认为是 Integer 的 "父类"
List<? super Integer> array = new ArrayList<Integer>();
// Number 是 Integer 的 父类
List<? super Integer> array = new ArrayList<Number>();
// Object 是 Integer 的 父类
List<? super Integer> array = new ArrayList<Object>();
读取:只能保证从 array 读取到 Object 实例,并不能知道具体是哪个实例
写入:可以添加 Integer 或者 Integer 的子类对象到 array 中,若 T 是 Integer,那么可以无障碍地添加 Integer 或者 Integer 子类的对象到容器中,因为对象都可以向上转型为 Integer。若 T 是 Integer 的父类,那么添加的类型都可以向上转型为 Integer 的父类到容器中。
List<?>
与 List<Object>
区别
- List,原始类型,其引用变量可以添加任意类型的元素,但是编译器很难发现错误,不建议使用。
-
List<Object>
表示可以容纳任意类型的对象,万事万物皆为 Object -
List<?>
无限定通配符类型,只能包含某一种未知对象类型,?表示未知类型的泛型,它只有读的能力,并没有写的能力,因为编译器不知道放哪个子类型到集合中。
public void test(List<?> list) {
Object o = list.get(0);
//编译器不允许该操作
// list.add("jaljal");
}
泛型类
用于类的定义,通过泛型完成一组类的操作,并对外开放相同的接口,参考 容器类。
在实例化 Test 的时候,若指定 T 的具体类型那么该类型不能是基本数据类型。
public class Generic<T> {
private T key;
public Generic(T key){
this.key = key;
}
public T getKey(){
return key;
}
}
Generic generic = new Generic(123);
System.out.println(generic.getKey());
Generic generic1 = new Generic<Integer>(123);
System.out.println(generic.getKey());
泛型接口
与泛型类定义基本相同,泛型接口的实现类可以是普通的类,也可以是泛型类。
普通类必须定义的时候指定参数类型;泛型类实现泛型接口的时候,需要声明泛型参数
// 定义泛型接口
public interface Test<T> {
public T next();
}
// 普通类 指定参数类型
public class TestA implements Test<String>{
@Override
public String get() {
return null;
}
@Override
public void set(String s) {
}
}
// 泛型类 传递参数类型
public class TestB<T> implements Test<T>{
private T t;
public TestB(T t){
set(t);
}
@Override
public T get() {
return t;
}
@Override
public void set(T t) {
this.t = t;
}
}
// 也可声明多个参数
public class TestC<T,K,V> implements Test<T> {
private T t;
private K k;
private V v;
public TestC(T t,K k,V v){
this.t = t;
this.k = k;
this.v = v;
}
@Override
public T get() {
return t;
}
@Override
public void set(T t) {
this.t = t;
}
}
泛型方法
使用规则
- 在方法返回值前面,声明 类型参数,用尖括号括起来,将表示这是一个泛型方法
- 类型参数只能是引用类型,不能是基本数据类型
public static < E > void printArray( E[] inputArray ){}
public <E, C extends E> void testDependys(E e, C c) {} // 第二个参数必须是 E 类型或者 E 的子类型
若静态方法要使用泛型,那么静态方法必须为泛型方法
public static <T> void show(T t){}
泛型擦除
泛型值存在于编译期,代码在进入虚拟机后泛型就会会被擦除掉,这个者特性就叫做类型擦除。
当泛型被擦除后,他有两种转换方式,第一种是如果泛型没有设置类型上限,那么将泛型转化成Object 类型。
下面所示,get,set 方法是 Object,在操作的时候会 checkcast
public class ManipulatorA <T>{
private T t;
public void setT(T t) {
this.t = t;
}
public T getT() {
return t;
}
public static void main(String[] args) {
ManipulatorA<String> manipulatorA = new ManipulatorA<>();
manipulatorA.setT("123");
System.out.println(manipulatorA.getT());
}
}
Compiled from "ManipulatorA.java"
public class fanxing.ManipulatorA<T> {
public fanxing.ManipulatorA();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public void setT(T);
Code:
0: aload_0
1: aload_1
2: putfield #2 // Field t:Ljava/lang/Object;
5: return
public T getT();
Code:
0: aload_0
1: getfield #2 // Field t:Ljava/lang/Object;
4: areturn
public static void main(java.lang.String[]);
Code:
0: new #3 // class fanxing/ManipulatorA
3: dup
4: invokespecial #4 // Method "<init>":()V
7: astore_1
8: aload_1
9: ldc #5 // String 123
11: invokevirtual #6 // Method setT:(Ljava/lang/Object;)V
14: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
17: aload_1
18: invokevirtual #8 // Method getT:()Ljava/lang/Object;
21: checkcast #9 // class java/lang/String
24: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
27: return
}
第二种是如果设置了类型上限,那么将泛型转化成他的类型上限。下面的例子就只会擦除到 HasF 类型。
如下所示,get,set 都是 Number 类型,也不用转换
public class Manipulator <T extends Number>{
private T t;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
public static void main(String[] args) {
Manipulator manipulator = new Manipulator();
manipulator.setT(123);
System.out.println(manipulator.getT());
}
}
Compiled from "Manipulator.java"
public class fanxing.Manipulator<T extends java.lang.Number> {
public fanxing.Manipulator();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public T getT();
Code:
0: aload_0
1: getfield #2 // Field t:Ljava/lang/Number;
4: areturn
public void setT(T);
Code:
0: aload_0
1: aload_1
2: putfield #2 // Field t:Ljava/lang/Number;
5: return
public static void main(java.lang.String[]);
Code:
0: new #3 // class fanxing/Manipulator
3: dup
4: invokespecial #4 // Method "<init>":()V
7: astore_1
8: aload_1
9: bipush 123
11: invokestatic #5 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
14: invokevirtual #6 // Method setT:(Ljava/lang/Number;)V
17: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
20: aload_1
21: invokevirtual #8 // Method getT:()Ljava/lang/Number;
24: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
27: return
}