Java泛型 动态类型安全的容器 java.util.Collections.checkedList()
大家都知道java的数组是动态类型安全的,因为数组在创建时就已经记住了其数组元素的类型,所以不管数组的引用怎么协变,如果插入不符合正确类型的对象了,数组就会报错。
而java的泛型容器相比就显得很脆弱,因为插入元素时的检查仅仅存在于编译期,其实就是根据引用的类型来的,要是List
这样就根本不检查了,要是List<Integer>
这样的就只会针对于这个引用上调用的方法进行检查。换句话说,普通的泛型容器根本没记住创建时你给定的类型参数。
除了java.util.Collections.checkedList()
方法外,还有checkedCollection
、checkedMap
、checkedSet
、checkedSortedSet
、checkedSortedMap
,也可以返回动态类型安全的容器。
checkedList()返回动态类型安全的容器
import java.util.*;
class Pet{}
class Dog extends Pet {}
class Cat extends Pet {}
public class CheckedList {
@SuppressWarnings("unchecked")
static void oldStyleMethod(List probablyDogs) {
probablyDogs.add(new Cat());
}
public static void main(String[] args) {
List<Dog> dogs1 = new ArrayList<Dog>();
oldStyleMethod(dogs1); // Quietly accepts a Cat这里会插入一个错误类型的元素进入容器,插入时不会报错
List<Dog> dogs2 = Collections.checkedList(
new ArrayList<Dog>(), Dog.class);
try {
oldStyleMethod(dogs2); // Throws an exception这里插入错误类型元素进入容器时,会抛出运行时异常
} catch(Exception e) {
System.out.println(e);
}
// Derived types work fine:子类对象放入父类的容器里,是可以的
List<Pet> pets = Collections.checkedList(
new ArrayList<Pet>(), Pet.class);
pets.add(new Dog());
pets.add(new Cat());
}
} /* Output:
java.lang.ClassCastException: Attempt to insert class typeinfo.pets.Cat element into collection with element type class typeinfo.pets.Dog
*///:~
- oldStyleMethod方法将传入的泛型容器作为原生类型使用,这时编译器不会进行编译期的检查。
List<Dog> dogs2 = Collections.checkedList(new ArrayList<Dog>(), Dog.class);
通过checkedList方法返回了一个动态类型安全的List<Dog>
,此时,oldStyleMethod(dogs2);
处理时,依靠dogs2对象自身的方法,便可以在插入元素时判断元素类型正确与否。oldStyleMethod(dogs1);
这里会插入一个错误类型的元素进入容器,插入时不会报错;oldStyleMethod(dogs2);
这里插入错误类型元素进入容器时,会抛出运行时异常。相比之下,dogs2更加安全,因为错误类型元素在插入就报错,而不是推迟到取出元素强转类型再报错。
checkedList()源码分析
下面是checkedList方法体:
//java.lang.Collections
public static <E> List<E> checkedList(List<E> list, Class<E> type) {
return (list instanceof RandomAccess ?
new CheckedRandomAccessList<>(list, type) :
new CheckedList<>(list, type));
}
发现方法逻辑这句list instanceof RandomAccess
,要先去判断list形参是否为RandomAccess的实例,这个RandomAccess是个啥呢:
/**
* Marker interface used by <tt>List</tt> implementations to indicate that
* they support fast (generally constant time) random access. The primary
* purpose of this interface is to allow generic algorithms to alter their
* behavior to provide good performance when applied to either random or
* sequential access lists.
*/
public interface RandomAccess {
}
原来RandomAccess只是一个用作标记的接口,代表容器可以随机存取,类似于数组可以用下标取元素一样,有以下具体类实现了RandomAccess接口:
果然,实现了RandomAccess接口的容器们基本都可以通过索引index来存或者取,除了Stack。回到checkedList的代码逻辑,由于本文例子是ArrayList,所以三目表达式会判断为真,然后调用CheckedRandomAccessList的构造器:
static class CheckedRandomAccessList<E> extends CheckedList<E>
implements RandomAccess
{
private static final long serialVersionUID = 1638200125423088369L;
CheckedRandomAccessList(List<E> list, Class<E> type) {
super(list, type);
}
public List<E> subList(int fromIndex, int toIndex) {
return new CheckedRandomAccessList<>(
list.subList(fromIndex, toIndex), type);
}
}
原来CheckedRandomAccessList是Collections里的静态内部类呀(其实java集合源码经常这么搞静态内部类),看到它构造时把参数传给了父类CheckedList的构造器:
static class CheckedList<E>
extends CheckedCollection<E>
implements List<E>
{
private static final long serialVersionUID = 65247728283967356L;
final List<E> list;
CheckedList(List<E> list, Class<E> type) {
super(list, type);
this.list = list;
}
//省略
}
CheckedList又是一个静态内部类,它构造时又把参数传给了父类CheckedCollection的构造器:
static class CheckedCollection<E> implements Collection<E>, Serializable {
private static final long serialVersionUID = 1578914078182001775L;
final Collection<E> c;
final Class<E> type;
@SuppressWarnings("unchecked")
E typeCheck(Object o) {
if (o != null && !type.isInstance(o))
throw new ClassCastException(badElementMsg(o));
return (E) o;
}
private String badElementMsg(Object o) {
return "Attempt to insert " + o.getClass() +
" element into collection with element type " + type;
}
CheckedCollection(Collection<E> c, Class<E> type) {
this.c = Objects.requireNonNull(c, "c");
this.type = Objects.requireNonNull(type, "type");
}
CheckedCollection又是一个静态内部类,构造器里面检查了两个参数是否为null,至此,构造器终于构造好了。看到这里,你应该发现了,直到构造完成,它也没有检查传入的容器是否传入时已经有了错误类型的元素,所以它只能保证你checkedList()返回后的容器是插入安全。