泛型最常见的就是用于集合,例如 List Set Map 等,加入泛型一般就是加入了元素类型限制。 异构容器是指能容纳不同类型对象的容器,原生态的List Mpa 等时异构容器,但一旦使用了泛型参数,比如 List<String> Map<String,String> ,他们就不是异构容器了,因为里面限制了只能加入 String 类型。我们知道,原生态类型的集合是不安全的,我们无法确定里面的元素到底是什么类型,比较容易出错。怎么才能建立一个有泛型的异构容器呢? 书中给出了 Favorites 的 要求,要能添加和提供数据。那么我们
public class Favorites {
private Map<Class<?>, Object> favorites = new HashMap<>();
public <T> void putFavorite(Class<T> type, T instance) {
if (type == null)
throw new NullPointerException("Type is null");
favorites.put(type, instance);
}
public <T> T getFavorite(Class<T> type) {
return type.cast(favorites.get(type));
}
}
我们在 Favorites 里面添加一个 Map 集合,class 作为 key,这样,就能装进去各种不同类型的对象在这个 Favorites 对象里, 同时根据 class 获取对应的 value 值,我们再看一下 cast()方法的代码,在 Class 类中
public T cast(Object obj) {
if (obj != null && !isInstance(obj))
throw new ClassCastException(cannotCastMsg(obj));
return (T) obj;
}
type.cast(favorites.get(type)); 我们根据 class 的类型,从map集合中获取到对应的value,然后再代用Class 的 cast()方法,转换为自己class对应的类型,这样,一个异构容器就好了,我们调用一下,看看效果
Favorites favorites = new Favorites();
favorites.putFavorite(String.class, "Death");
favorites.putFavorite(Integer.class, 25);
String name = favorites.getFavorite(String.class);
Integer num = favorites.getFavorite(Integer.class);
System.out.println("name : " + name +" num : " + num);
数据能存储,也能正常取出来。但是里面有些问题, 第一就是 如果传入的 class 和 对象 不一致,在 getFavorite()方法容易类型异常,此时添加检测机制
public <T> void putFavorite(Class<T> type, T instance) {
if (type == null)
throw new NullPointerException("Type is null");
favorites.put(type, type.cast(instance));
}
调用 putFavorite 时,我们传入了A.class,但value值却是一个 A 的子类型 B ,这种情况是可以的,但如果想取出来,key 只能是 A.class,绝对不能是 B.class,这点要注意。有一些不能被泛型化的类型,比如 List<String>.class, 这种语法是错误的,不能当做 key 值, 这种暂时没好的解决方法。
题外话,比如一个 ArrayList<String> 类型的集合,如何把 int 类型的数据装进去? 如果是原生态的 ArrayList,很容易就add()进去了,但此时有了泛型限制。 我们知道泛型是编译时起作用,运行时擦除了泛型的痕迹;反射是编译时不起作用,运行时才执行代码功能。说到这,反应快的已经明白了,泛型和反射彼此错开了对方,这样,就给我们机会了
public static void test() {
ArrayList<String> list = new ArrayList<>();
list.add("death");
try {
Method method = list.getClass().getMethod("add", Object.class);
method.invoke(list, 34);
method.invoke(list, "zhangsan");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("list : " + list.toString());
}
打印结果是 list : [death, 34, zhangsan] 。 34被添加进去,以包装类的格式 Integer 。 这种方式仅仅添加数据成功,如果用迭代器遍历或者增强for遍历,不是所有的对象都能是String类型,需要做类型判断,防止类型异常。