Java泛型 动态类型安全的容器 java.util.Collections.checkedList()

大家都知道java的数组是动态类型安全的,因为数组在创建时就已经记住了其数组元素的类型,所以不管数组的引用怎么协变,如果插入不符合正确类型的对象了,数组就会报错。
而java的泛型容器相比就显得很脆弱,因为插入元素时的检查仅仅存在于编译期,其实就是根据引用的类型来的,要是List这样就根本不检查了,要是List<Integer>这样的就只会针对于这个引用上调用的方法进行检查。换句话说,普通的泛型容器根本没记住创建时你给定的类型参数。
除了java.util.Collections.checkedList()方法外,还有checkedCollectioncheckedMapcheckedSetcheckedSortedSetcheckedSortedMap,也可以返回动态类型安全的容器。

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()返回后的容器是插入安全。

posted @ 2019-10-13 17:32  allMayMight  阅读(518)  评论(0编辑  收藏  举报