一、编译期的泛型
’java泛型可能表示着没有任何意义的事物。
比如:下面的ArrayMaker 中的属性kind,事实上会被存储为 Class,没有任何参数。因此,
Array.newInstance() 并没有kind 蕴含的类型信息,不会产生具体的结果,会生成 unchecked call 的警告
1、数组泛型
public class ArrayMaker<T> {
private Class<T> kind;
public ArrayMaker(Class<T> kind) {
this.kind = kind;
}
/**
* 强转时提示了 unchecked call!
*
* 即使我们利用kind 指定了类型为 String,但运行时,kind 仍然只是被存储为Class ,没有任何参数。
* Array.newInstance()也并没有 kind 拥有的 类型信息,不会产生具体的结果。
*
* Array.newInstance() 只是产生了一个具有 长度的数组,但是元素全部为null。
*
* 注意: 假如要在泛型里创建数组, Array.newInstance()是比较推荐的方式!!!
*/
@SuppressWarnings("unchecked")
public T[] create(int size) {
return (T[]) Array.newInstance(kind, size);
}
public static void main(String[] args) {
ArrayMaker<String> maker = new ArrayMaker<>(String.class);
System.out.println(Arrays.toString(maker.create(10))); //[null, null, null, null, null, null, null, null, null, null]
String[] strings = (String[]) (Array.newInstance(String.class, 2));
System.out.println(Arrays.toString(strings)); // [null, null]
}
}
2、容器泛型
class ListMaker<T> {
List<T> create() {
return new ArrayList<>();
}
public static void main(String[] args) {
List<String> list1 = new ListMaker<String>().create(); // 无unchecked 警告
List<String> list2 = new ListMaker().create(); // 有unchecked 警告
}
}
class FilledListMaker<T> {
List<T> create(T t, int n) {
// 编译器其其实不知道create()中T的类型信息,但是在编译器可以确保放到list中的对象都是T类型
List<T> list = new ArrayList<>();
IntStream.range(0, n).forEach(e -> list.add(t));
return list;
}
public static void main(String[] args) {
FilledListMaker<String> filledListMaker = new FilledListMaker<>();
List<String> list = filledListMaker.create("hello", 10);
System.out.println(list);
}
}
2、3其实说明:
即使编译器不知道有关 create() 中T的信息,但是仍旧可以在编译期确保容器中对象具有正确的类型。因此,即使擦除在方法或类内部移除了有关实际类型的信息,编译器可以确保在方法或类中使用的类型的内部一致性。换言之:泛型,将原本在运行期才暴露的错误提前到了编译期暴露出来。
二、泛型边界
方法体中移除了类型信息,在运行时对象进入、离开的地点就是边界–> 编译器在编译期执行类型检查并插入转型代码的地点。
public class SimpleHolder {
private Object object;
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
public static void main(String[] args) {
SimpleHolder simpleHolder = new SimpleHolder();
simpleHolder.setObject("yes");
String yes = (String) simpleHolder.getObject(); // getXX需要强转,从字节码角度看,就是在此接受检查的
System.out.println(yes);
}
}
class GenericHolder<T> {
private T obj;
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
public static void main(String[] args) {
GenericHolder<String> holder = new GenericHolder<>();
holder.setObj("no");
String no = holder.getObj(); // 产生的字节码显示,这里也是有强转检查的
System.out.println(no);
}
}
实际上,这里两个类型产生的字节码是一致的。对进入set()的检查是不需要的,编译器会进行。但从get()返回的值的强转却是需要的,只不过:在 SimpleHolder,强转需要手动进行,而在GenericHolder中 强转是由编译器自动插入的。
可见:泛型中所有动作都是发生在边界处—–对传进来的值进行额外编译期检查,并插入对传递出去的值的转型。这里的边界就是指的发生 “传进来”(set)、 “传出去“”(get)动作的地方。注意:不要泛型的 继承 混淆。