菜鸟话Java---Java泛型

定义泛型类

示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

public class Pair<T> {//泛型类可以有多个类型变量,例如 Pair<T,U>

private T first;//可以用具体的类型替换

private T second;

public Pair(){
first = null;
second = null;
}

public Pair(T first, T second) {
this.first = first;
this.second = second;
}

public T getFirst() {
return first;
}

public void setFirst(T first) {
this.first = first;
}

public T getSecond() {
return second;
}

public void setSecond(T second) {
this.second = second;
}
}


public static void main(String[] args) {
Pair<String> pair = new Pair<>();//使用 String 来替换泛型类型,Pair中的方法就变成
pair.setFirst(String);
pair.setSecond(String);
....
}

在Java中,使用变量E表示集合的元素类型,KV分别表示表的关键字与值的类型。
T(需要时好i可以用临近的字母US)表示”任意类型”

泛型方法

定义泛型方法

1
2
3
4
5
6
7
8
9
10
public class Util {
//获取中间值
public static <T>T getMiddle(T... t){
...
}

public <T>void get(T t){

}
}

使用泛型方法

1
2
3
String information = Util.<String>getMiddle("12345","qww","sss");

String information = Util.getMiddle("12345","qww","sss");//一般编译器可以推断,所以可以省略

注意:例如double d = Util.getMiddle(1.0,1234,0),编译器会先找到DoubleInteger的超类,但是
它们的超类和接口,都是泛型类型。这种情况下,会报错,唯一的解决办法就是把所有参数写成double

类型变量的限定

语法:

1
<T extends 类型>

示例:

1
2
3
public class Util<T extends Comparable>{

}

注意:在Java继承中,可以根据需要拥有多个接口类型,但限定中至多有一个类。如果用
类限定,它必须是限定列表中的第一个。

类型擦除

Java虚拟机中没有泛型对象,所有的对象都属于普通类。无论何时定义一个泛型类型,在Java虚拟机
中都会擦除类型变量,并替换为限定类型(无限定的变量用Object)

例如,Pair<T>的原始类型为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Pair<Object> {//泛型类可以有多个类型变量,例如 Pair<T,U>

private Object first;//可以用具体的类型替换

private Object second;

public Pair(){
first = null;
second = null;
}

public Pair(Object first, Object second) {
this.first = first;
this.second = second;
}

public Object getFirst() {
return first;
}

public void setFirst(Object first) {
this.first = first;
}

public Object getSecond() {
return second;
}

public void setSecond(Object second) {
this.second = second;
}
}

由于T是一个无限定的变量,所以直接用Object替代。

在程序中可以包含不同类型的Pair,例如,Pair<String>Pair<LocalDate>.而
擦除类型后就变成原始的Pair类型。

如果有限定,例如:

1
2
3
public class Util<T extends Serializable&Comparable>{

}

原始类型会用第一个限定类型来替代。上例中就为Serializable,编译器只在必要的时候
插入Comparable强制类型转换。为了提高效率应该将标记接口(即没有方法的接口,比如
Serializable)放在后面

有返回值的泛型方法

当调用有返回值的泛型方法时,编译器会插入强制类型转换,例如:

1
2
Pair<String> pair = ....;
String a = pair.getFirst();

转换为:

1
2
Pair<String> pair = ....;
String a = (String)pair.getFirst();

桥方法

1
2
3
4
5
6
public class Test extends Pair<LocalDate> {

public void setSecond(LocalDate second) {
super.setSecond(second);
}
}

由上面的示例,当你继承Pair<LocalDate>时,由于类型擦除,
Pair<LocalDate>中只存在setSecond(Object sencod)方法,当我们
想通过Pair<LocalDate> pair = new Test()来调用setSencod时,就
与多态发生冲突。

为了解决这个问题,编译器会在Test中生成桥方法,例如

1
2
3
public void setSecond(Object second){
setSecond((LocalDate)second)
}

如果我们覆盖getSecond()方法,则存在两个getSecond()方法

1
2
LocalDate getSecond()
Object getSecond()//桥方法,用来覆盖父类的方法

注意:具有相同的参数类型的两个方法是不合法的,不能写这样的Java代码。
但是,在虚拟机中,用参数类型和返回值类型来确定一个方法。因此,编译器可
产生两个仅返回值类型不同的方法

Java泛型转换的总结

  • 虚拟机中没有泛型,只有普通的类和方法
  • 所有的类型参数都用它们的限定类型替代
  • 桥方法被合成保持多态
  • 为保持类型的安全性,必要时插入强制类型转换

泛型的约束

不能用基本类型来实例化类型参数

例如Pair<double>是不被允许的,不过可以使用基本类型的包装类

运行时类型查询只适用于原始类型

例如:

1
2
3
4
5
6
7
a instanceof Pair<String> //编译器报错

a instanceof Pair<T>//编译器报错

Pair<String> s = ...;
Pair<Integer> i = ...;
if(s.getClass() == i.getClass())//返回true,因为两次调用都会返回Pair.class

不能创建参数化类型数组

例如

1
2
Pair<String>[] p = new Pair<String>[10];//编译器报错
`

不过可以声明通配符(后面介绍)类型的数组,然后进行类型转换

1
Pair<String>[] p = (Pair<String>[])new Pair<?>[10];//结果不安全,由于数组储存只会检查擦除后的类型

注意:对于可变参数(本质是数组),虽然违法了上面的规则,但是你只会收到一个警告

不能直接实例化类型变量

例如:

1
T t = new T();//编译器报错

实例化类型变量的方法

1.方法1,使用反射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class C<T> {

T value;

public C(T value){
this.value = value;
}

public static <T> C<T> make(Class<T> cl){
try {
return new C<>(cl.newInstance());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}

}

//调用
C<String> c1 = C.make(String.class);
System.out.println(c1.value.getClass());

//输出为
class java.lang.String

2.方法2,使用构造器表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class C<T> {

T value;

public C(T value){
this.value = value;
}


public static <T> C<T> make(Supplier<T> supplier){
return new C<>(supplier.get());
}

}

//调用
C<String> c1 = C.make(String::new);
System.out.println(c1.value.getClass());

//输出为
class java.lang.String

### 不能直接构造泛型数组

### 泛型类的静态变量和静态方法的类型变量无效

```java
public class Pair<T> {

private static T first;//报错

public static void setFirst(T first) {//报错
this.first = first;
}
...

不能抛出或捕获泛型类的实例

可以消除对受查异常的检查

注意擦除后的冲突

泛型类型的继承规则

注意:无论TS是什么关系(比如继承),通常,C<T>C<S>没有什么联系

永远可以将一个参数化类型转换为一个原始类型,例如,C<String>是原始类型C的子类型

通配符

1
2
3
List<A> list = new ArrayList();
list.add(new A());
list.add(new B());//尽管B是A的子类,但是不能这样做

为了解决这个问题,就可以使用通配符,例如

1
2
3
List<? extends A) list = new ArrayList();
list.add(new A());
list.add(new B());