java 泛型通配符和边界
1. 通配符
泛型中常用的通配符:
我们在定义泛型类,泛型方法,泛型接口的时候经常会碰见很多不同的通配符,比如 T,E,K,V ,?等等,这些通配符又都是什么意思呢?
- ? 表示不确定的 java 类型
- T (type) 表示具体的一个java类型
- K V (key value) 分别代表java键值中的Key Value
- E (element) 代表Element
比如在不确定泛型参数的具体类型时,可以使用?代替,比如下方的例子:
比如public void set(List<?> list)
在调用方法时,参数类型可以是任何参数,既可以是List<String>也可以是List<Integer>。
2. 边界
A<? extends B>是带通配符的参数化类型,它可以作为普通类型那样用于变量和方法声明等中。
用法例如下方的样式:
1. A<? extends B> a = ...;
2. public void foo(A<? extends B> a) { ... }
3. List<? extends Number> l;
<? extends Number> 表示上边界限定,泛型参数只能是Number及其子类,一旦实例化其他参数类型则会报错。
<? super Number> 表示下边界限定,泛型参数只能是Number和它的父类,如果传入的参数类型不是 Number 或者 Number 的子类,无法编译成功。
<?> 表示无界通配符,可以表示任意的参数类型。
image.png
3. T和?的区别
?和 T 都表示不确定的类型,区别在于我们可以对 T 进行操作,但是对 ? 不行,比如如下这种 :
// 可以操作
T t = operate();
// 不能操作
? car = operate();
T 是一个确定的类型(确定是因为通过 T 来确保泛型参数的一致性这方面来说的),通常用于泛型类和泛型方法的定义,?是一个 不确定 的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。
3.1. 通过 T 来确保多个泛型参数的一致性
// 通过 T 来确保泛型参数类型的一致性
public <T extends Number> void Hello(List<T> dest, List<T> src)
// 通配符类型是不确定的,所以这个方法不能保证两个 List 具有相同的参数类型
public void Hello(List<? extends Number> dest, List<? extends Number> src)
下面的例子由于泛型指定的数据类型是Number,但是在赋值时给出的数据类型是String,所以 发生数据类型无法相互转换的错误。改正错误的办法是赋值时使用Number的数据类型进行赋值。
import java.util.ArrayList;
import java.util.List;
class Generic {
public <T extends Number> void genericTest(List<T> dest, List<T> src) {
}
}
public class GenericLearn {
public static void main(String[] args) {
List<String> a = new ArrayList<>();
List<String> b = new ArrayList<>();
// List<String> a = new ArrayList<String>();
// List<String> b = new ArrayList<String>();
Generic generic = new Generic();
generic.genericTest(a, b);
}
}
----------------------------------------------------
Exception in thread "main" java.lang.RuntimeException:
Uncompilable source code - Erroneous sym type: com.zhbi.source.Generic.genericTest
at com.zhbi.source.GenericLearn.main(GenericLearn.java:22)
Java Result: 1
下方为改正错误后的代码:
import java.util.ArrayList;
import java.util.List;
class Generic {
public <T extends Number> void genericTest(List<T> dest, List<T> src) {
}
}
public class GenericLearn {
public static void main(String[] args) {
List<Number> a = new ArrayList<>();
List<Number> b = new ArrayList<>();
// List<Number> a = new ArrayList<Number>();
// List<Number> b = new ArrayList<Number>();
Generic generic = new Generic();
generic.genericTest(a, b);
}
}
3.2. 类型参数可以多重限定而通配符不行
使用 & 符号设定多重边界(Multi Bounds),指定泛型类型 T 必须是 A 和 B 的共有子类型,此时被修饰的变量就具有了所有限定的方法和属性。对于通配符?来说,因为它不是一个确定的类型,所以不能用于多重限定。
class Generic {
public <T extends A & B> void genericTest(List<T> dest, List<T> src) {
}
}
但是这两种数据类型必须为接口(interface)类型,比如在这里定义了两个接口A和B,同时在类中也实现了这两个接口,具体看下方的代码。
import java.util.ArrayList;
import java.util.List;
interface A {
public void a();
}
interface B {
public void b();
}
class Generic implements A, B {
public <T extends A & B> void genericTest(List<T> dest, List<T> src) {
}
@Override
public void a() {
}
@Override
public void b() {
}
}
public class GenericLearn {
public static void main(String[] args) {
List<Generic> a = new ArrayList<>();
List<Generic> b = new ArrayList<>();
Generic generic = new Generic();
generic.genericTest(a, b);
}
}
3.3. 通配符可以使用超类限定而类型参数不行
类型参数 T 只具有 一种 类型限定方式:
T extends A
但是通配符 ? 可以进行 两种限定:
? extends A
? super A
Class<T>在实例化的时候,T 要替换成具体类。Class<?>它是个通配泛型,? 可以代表任何类型,所以主要用于声明时的限制情况。比如,我们可以这样做申明:
// 可以
public Class<?> clazz;
// 不可以,因为 T 需要指定类型
public Class<T> clazzT;
所以当不知道定声明什么类型的 Class 的时候可以定义一 个Class<?>。
public class Test3 {
public Class<?> clazz;
// 不会报错
public Class<T> clazzT;
}
那如果也想 public Class<T> clazzT; 这样的话,就必须让当前的类也指定 T :
public class Test3<T> {
public Class<?> clazz;
// 不会报错
public Class<T> clazzT;
}