1.泛型通配符
?:可以通配任意类型
观察下面代码:
public void test1(Collection<Integer> c) {
}
public void test2(Collection<String> c) {
}
public void test3(Collection<Double> c) {
}
test1方法【只能】接收泛型是Integer类型的集合对象
test2方法【只能】接收泛型是String类型的集合对象
test3方法【只能】接收泛型是Double类型的集合对象
原因:由于泛型的类型之间没有多态,所以=号俩边的泛型类型必须一致
在这种情况下,就可以使用通配符(?)来表示泛型的父类型:
public void test(Collection<?> c) {
}
注意,这时候test方法中的参数类型,使用了泛型,并且使用问号来表示这个泛型的类型,这个问号就是通配符,可以匹配所有的泛型类型
test方法可以接收 泛型是任意引用类型的 Collection集合对象
public static void main(String[] args) {
Test t = new Test();
t.test(new ArrayList<String>());
t.test(new ArrayList<Integer>());
t.test(new ArrayList<Double>());
t.test(new ArrayList<任意引用类型>());
}
使用通配符(?)所带来的问题:
Collection<?> c;
c = new ArrayList<String>();
//编译报错
//因为变量c所声明的类型是Collection,同时泛型类型是通配符(?)
//那么编译器也不知道这个?将来会是什么类型,因为这个?只是一个通配符
//所以,编译器不允许使用变量c来向集合中添加新数据。
c.add("hello");
//编译通过
//但是有一个值是可以添加到集合中的,null
//集合中一定存的是引用类型,null是所有引用类型共同的一个值,所以一定可以添加进去。
c.add(null);
虽然使用通配符(?)的集合,不能再往其中添加数据了,但是可以遍历集合取出数据:
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("hello1");
list.add("hello2");
list.add("hello3");
list.add("hello4");
Collection<?> c = list;
//编译报错
//c.add("hello5");
for(Object obj : c) {
System.out.println(obj);
}
}
2. 泛型边界
在默认情况下,泛型的类型是可以任意设置的,只要是引用类型就可以。
如果在泛型中使用extends
和super
关键字,就可以对泛型的类型进行限制。即:规定泛型的上限和下限。
泛型的上限:
- 格式:
类型名<? extends 类型 > 对象名称
- 意义:
只能接收该类型及其子类型
泛型的下限:
- 格式:
类型名<? super 类型 > 对象名称
- 意义:
只能接收该类型及其父类型
1)泛型上限
-
例如:
List<? extends Number> list
-
将来引用list就可以接收泛型是
Number
或者Number
子类型的List集合对象
public static void main(String[] args) {
List<? extends Number> list;
//list可以指向泛型是Number或者Number【子】类型的集合对象
list = new ArrayList<Number>();
list = new ArrayList<Integer>();
list = new ArrayList<Double>();
//编译报错,因为String不是Number类型,也不是Number的子类型
//list = new ArrayList<String>();
}
能表示数字的类型都是Number类型的子类型,例如Byte Short Integer Long等
2)泛型下限
-
例如:
List<? super Number> list
-
将来引用list就可以接收泛型是
Number
或者Number
父类型的List集合对象
public static void main(String[] args) {
List<? super Number> list;
//list可以指向泛型是Number或者Number【父】类型的集合对象
list = new ArrayList<Number>();
list = new ArrayList<Serializable>();
list = new ArrayList<Object>();
//编译报错,因为String不是Number类型,也不是Number的父类型
//list = new ArrayList<String>();
//编译报错,因为Integer不是Number类型,也不是Number的父类型
//list = new ArrayList<Integer>();
}
泛型中extends
和super
对比:
- 使用extends可以定义泛型的【上限】,这个就表示将来泛型所接收的类型【最大】是什么类型。可以是这个最大类型或者它的【子类型】。
- 使用super可以定义泛型的【下限】,这个就表示将来泛型所接收的类型【最小】是什么类型。可以是这个【最小类型】或者它的【父类型】。
3. 类型擦除
泛型类型仅存在于编译期间,编译后的字节码和运行时不包含泛型信息,所有的泛型类型映射到同一份字节码。
由于泛型是JDK1.5才加入到Java语言特性的,Java让编译器擦除掉关于泛型类型的信息,这样使得Java可以向后兼容之前没有使用泛型的类库和代码,因为在字节码(class)层面是没有泛型概念的。
例如,定义一个泛型类Generic
是这样的:
class Generic<T> {
private T obj;
public Generic(T o) {
obj = o;
}
public T getObj() {
return obj;
}
}
那么,Java编译后的字节码中Generic
相当于这样的:(类型擦除,变为原始类型Object)
class Generic {
private Object obj;
public Generic(Object o) {
obj = o;
}
public Object getObj() {
return obj;
}
}
例如,
public static void main(String[] args) {
//编译报错
//ArrayList<Integer>和new ArrayList<Long>在编译期间是不同的类型
//ArrayList<Integer> list = new ArrayList<Long>();
//但是编译完成后,它们对应的是同一份class文件:ArrayList.class
ArrayList<Integer> list1 = new ArrayList<Integer>();
ArrayList<Long> list2 = new ArrayList<Long>();
// 大家大致能看懂即可,后续反射章节会补充
System.out.println(list1.getClass() == list2.getClass()); //true
}
注意,泛型信息被擦除后,所有的泛型类型都会统一变为原始类型:Object
例如,
//编译报错
//因为在编译后,泛型信息会被擦除
//所以下面两个run方法不会构成重载,本质上都是 public void run(List list)
public class Test {
public void run(List<String> list){
}
public void run(List<Integer> list){
}
}
可以看出,Java的泛型只存在于编译时期,泛型使编译器可以在编译期间对类型进行检查以提高类型安全,减少运行时由于对象类型不匹配引发的异常。
但是在编译成功后,所有泛型信息会被擦除,变为原始类型Object。
4. 泛型小结
概念 | 描述 | 示例 |
---|---|---|
泛型类(Generic Class) | 使用泛型参数化的类,可以在实例化时指定具体类型。 | class MyClass<T> { ... } |
泛型接口(Generic Interface) | 使用泛型参数化的接口,可以在实现时指定具体类型。 | interface MyInterface<T> { ... } |
泛型方法(Generic Method) | 使用泛型参数化的方法,可以在调用时指定具体类型,与所属类的泛型参数可以不同。 | public <T> void myMethod(T data) { ... } |
类型参数(Type Parameter) | 在泛型中使用的占位符类型,用于指定参数化类型。 | <T> 、<K, V> |
通配符(Wildcard) | 用于表示未知类型的通配符,用于灵活处理不确定类型的泛型。 | List<?> 、List<? extends Number> 、List<? super Integer> |
类型限定(Type Bounds) | 限定泛型的类型范围,可以是具体类型、接口或类。 | <T extends Number> 、<T extends Comparable<T>> |
类型擦除(Type Erasure) | 泛型在编译时会进行类型擦除,生成原始类型的字节码,运行时无法获取泛型类型的具体信息。 | - |