为什么Collections.synchronizedList(list)在内部使用instanceof检查?

时间:2017-12-13 10:55:37

标签: java collections java-8

我正在查看收藏集类的源代码。 我遇到了方法 Collections.synchronizedList(list)

public static <T> List<T> synchronizedList(List<T> list) {
    return (list instanceof RandomAccess ?
            new SynchronizedRandomAccessList<>(list) :
            new SynchronizedList<>(list));
}

我无法理解为什么我们要检查列表是否属于RandomAccess类型。 我知道ArrayList实现了这个接口,而LinkedList没有。

此外, SynchronizedRandomAccessList 会继承 SynchronizedList 。 那检查有什么意义呢? 请解释

2 个答案:

答案 0 :(得分:5)

你几乎就在那里。 Collections.synchronizedList(list)检查RandomAccess,因为有些列表会实施RandomAccess,而其他列表则不会。

RandomAccessmarker interface,就像Serializable一样。它告诉我们是否可以随机访问列表中的项目。即从ArrayList我们可以检索具有相同计算成本的任何项目。另一方面,我们需要遍历LinkedList才能到达第n个元素。

那么,Collections.synchronizedList(list)发生了什么?它将RandomAccess List - s包装到RandomAccess同步列表中,同时将非RandomAccess列表包装到非RandomAccess同步列表中。否则这些列表是相同的。以下是SynchronizedRandomAccessList的代码。这是programming by difference的一个很好的例子。这两个类几乎相同。

static class SynchronizedRandomAccessList<E>
    extends SynchronizedList<E>
    implements RandomAccess {

    SynchronizedRandomAccessList(List<E> list) {
        super(list);
    }

    SynchronizedRandomAccessList(List<E> list, Object mutex) {
        super(list, mutex);
    }

    public List<E> subList(int fromIndex, int toIndex) {
        synchronized (mutex) {
            return new SynchronizedRandomAccessList<>(
                list.subList(fromIndex, toIndex), mutex);
        }
    }

    private static final long serialVersionUID = 1530674583602358482L;

    /**
     * Allows instances to be deserialized in pre-1.4 JREs (which do
     * not have SynchronizedRandomAccessList).  SynchronizedList has
     * a readResolve method that inverts this transformation upon
     * deserialization.
     */
    private Object writeReplace() {
        return new SynchronizedList<>(list);
    }
}

您可能会问,RandomAccess界面的重点是什么?正如Holger所指出的,Collections.binarySearch()基于此接口做出决策。另一个例子是Collections.reverse()

答案 1 :(得分:3)

您必须回忆RandomAccess标记界面的原始用途。如果将List传递给另一个方法,它应该能够选择适合随机访问或顺序列表的算法。选择正确的算法需要通过list instanceof RandomAccess测试标记接口。

显示one example

public static <T>
int binarySearch(List<? extends Comparable<? super T>> list, T key) {
    if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
        return Collections.indexedBinarySearch(list, key);
    else
        return Collections.iteratorBinarySearch(list, key);
 }

(另请参阅reverseshufflecopyfill

现在,如果您只是将列表包装到另一个实现List接口的对象中,那么所有这些方法都会看到包装器对象,因此这些信息会丢失。但是,像同步列表这样的包装器不会改变随机访问方法(如get)的时间复杂度。因此,如果包装列表是随机访问列表,则期望包装器也应该实现RandomAccess,以便接收这种包装器的方法仍然能够检测快速随机访问是否可用。

如果查看implementation of SynchronizedRandomAccessList,您会看到它所做的全部工作是扩展SynchronizedList并实施RandomAccess,以继承行为并将自己标记为具有快速随机访问权限。它覆盖的唯一方法是subList,原因完全相同。如果列表具有有效的随机访问权限,则其子列表也具有,因此它们也应该实现RandomAccess

static class SynchronizedRandomAccessList<E>
    extends SynchronizedList<E>
    implements RandomAccess {

    SynchronizedRandomAccessList(List<E> list) {
        super(list);
    }

    SynchronizedRandomAccessList(List<E> list, Object mutex) {
        super(list, mutex);
    }

    public List<E> subList(int fromIndex, int toIndex) {
        synchronized (mutex) {
            return new SynchronizedRandomAccessList<>(
                list.subList(fromIndex, toIndex), mutex);
        }
    }

请注意,其他包装工厂(如checkedList)遵循相同的模式。所以这甚至可以在组合工厂时起作用:

System.out.println(
    Collections.synchronizedList(Collections.checkedList(new ArrayList<>(), String.class))
    instanceof RandomAccess);

→true

System.out.println(
    Collections.synchronizedList(Collections.checkedList(new LinkedList<>(), String.class))
    instanceof RandomAccess);

→false