Java PriorityQueue initElementsFromCollection 方法

发布于 2025-01-16 06:17:17 字数 1445 浏览 3 评论 0原文

我很难消化这个特定的代码块 java.util.PriorityQueue#initElementsFromCollection 方法。

/**
 * Initializes queue array with elements from the given Collection.
 *
 * @param c the collection
 */
private void initFromCollection(Collection<? extends E> c) {
    initElementsFromCollection(c);
    heapify();
}

private void initElementsFromCollection(Collection<? extends E> c) {
    Object[] es = c.toArray();
    int len = es.length;
    if (c.getClass() != ArrayList.class)
        es = Arrays.copyOf(es, len, Object[].class);
    if (len == 1 || this.comparator != null)
        for (Object e : es)
            if (e == null)
                throw new NullPointerException();
    this.queue = ensureNonEmpty(es);
    this.size = len;
}

我可以理解,这里的代码尝试从通过构造函数提供的集合元素构建堆,但为什么他们要针对 ArrayList 检查 Collection 参数的类类型,并再次复制元素,它已经使用 toArray 复制到 Object[] es<​​/code> 中吗?

    if (c.getClass() != ArrayList.class)
        es = Arrays.copyOf(es, len, Object[].class);

java.util.Arrays#copyOf(U[], int, java.lang.Class) 中发生了什么神奇的事情吗?

java-11

I'm having hard time digesting this particular code block from java.util.PriorityQueue#initElementsFromCollection method.

/**
 * Initializes queue array with elements from the given Collection.
 *
 * @param c the collection
 */
private void initFromCollection(Collection<? extends E> c) {
    initElementsFromCollection(c);
    heapify();
}

private void initElementsFromCollection(Collection<? extends E> c) {
    Object[] es = c.toArray();
    int len = es.length;
    if (c.getClass() != ArrayList.class)
        es = Arrays.copyOf(es, len, Object[].class);
    if (len == 1 || this.comparator != null)
        for (Object e : es)
            if (e == null)
                throw new NullPointerException();
    this.queue = ensureNonEmpty(es);
    this.size = len;
}

I can understand that the code here tries to build the Heap from the collection elements supplied via constructor but why are they checking the class type of Collection argument against ArrayList and again copying the elements, it has already been copied into Object[] es using toArray?

    if (c.getClass() != ArrayList.class)
        es = Arrays.copyOf(es, len, Object[].class);

Is anything magical happening in java.util.Arrays#copyOf(U[], int, java.lang.Class<? extends T[]>) ?

java-11

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

千纸鹤带着心事 2025-01-23 06:17:17

构造函数(或者更确切地说,它的作者)不信任传入的集合。该集合的 toArray 方法可能会违反约定并返回一个共享数组而不是创建一个新数组。这样,调用者就可以获取构造的 PriorityQueue 实例的内部使用的数组。

因此,构造函数会创建另一个防御性副本,除非传入的集合恰好是 ArrayList,即甚至不是它的子类。换句话说,它信任 ArrayListtoArray 实现遵守约定,从而在这种特定情况下跳过额外的复制步骤。这就是为什么甚至不接受 ArrayList 的子类,因为子类可能重写了 toArray 方法。

正如这个问题Stream.toList()的默认实现中也显示出类似的不信任。 a> 以及该问题下面的评论部分。

已指定默认实现,

default List<T> toList() {
    return (List<T>) Collections.unmodifiableList(new ArrayList<>(Arrays.asList(this.toArray())));
}

因为它不信任第 3 方 Stream 实现的 toArray() 实现(JDK 实现覆盖了 toList()无论如何方法)。

这种偏执的惩罚甚至要付出两次。过去,ArrayList 的构造函数确实信任任何传入集合的 toArray() 实现,但今天,它看起来

public ArrayList(Collection<? extends E> c) {
    Object[] a = c.toArray();
    if ((size = a.length) != 0) {
        if (c.getClass() == ArrayList.class) {
            elementData = a;
        } else {
            elementData = Arrays.copyOf(a, size, Object[].class);
        }
    } else {
        // replace with empty array.
        elementData = EMPTY_ELEMENTDATA;
    }
}

与您在 中看到的完全相同>PriorityQueue 的构造函数,仅当集合恰好是 ArrayList 时才信任 toArray()

由于在 toList() 的情况下,传入的集合是 Arrays.asList(…) 的结果,即不是 ArrayList, toArray() 方法将创建一个副本,随后 ArrayList 的构造函数将创建另一个副本。所有这些,虽然原始数组无论如何都是一个新数组,但为了表现良好的 Stream 实现......


我们可以概括这个问题。集合的惯用初始化,例如 new ArrayList<>(Arrays.asList(...))new ArrayList<>(List.of(...)) 或类似对于PriorityQueue,尽管仅使用应该具有正确的toArray实现的JDK集合,但仍会受到防御性副本的惩罚。

我可能会使用 c.getClass().getClassLoader() != null 而不是 c.getClass() != ArrayList.class 来信任所有内置的集合,其实现已由引导加载程序加载。但也许,该测试会比 ArrayList 测试更昂贵,或者有太多不值得信赖的内置集合……

关键点是优化器是否足够好地理解该构造以预测其结果结果。

The constructor (or rather its author) does not trust the incoming collection. The collection’s toArray method could violate the contract and return a shared array rather than creating a new one. This way, the caller could get hands on the internally used array of the constructed PriorityQueue instance.

So, the constructor makes another defensive copy, except when the incoming collection is an ArrayList exactly, i.e. not even a subclass of it. In other words, it trusts the ArrayList’s toArray implementation to adhere to the contract, to skip the additional copying step in this specific case. That’s why not even a subclass of ArrayList will be accepted, as a subclass could have overridden the toArray method.

A similar mistrust has been displayed in the default implementation of Stream.toList() as discussed in this question and the comment section beneath that question.

The default implementation has been specified as

default List<T> toList() {
    return (List<T>) Collections.unmodifiableList(new ArrayList<>(Arrays.asList(this.toArray())));
}

because it distrust the toArray() implementation of 3rd party Stream implementations (the JDK implementation overrides the toList() method anyway).

The penalty of this paranoia is even paid twice. In the past, ArrayList’s constructor did trust any incoming collection’s toArray() implementation, but today, it looks like

public ArrayList(Collection<? extends E> c) {
    Object[] a = c.toArray();
    if ((size = a.length) != 0) {
        if (c.getClass() == ArrayList.class) {
            elementData = a;
        } else {
            elementData = Arrays.copyOf(a, size, Object[].class);
        }
    } else {
        // replace with empty array.
        elementData = EMPTY_ELEMENTDATA;
    }
}

doing exactly the same as you’ve seen in PriorityQueue’s constructor, only trust toArray() when the collection is exactly an ArrayList.

Since in the case of the toList(), the incoming collection is the result of Arrays.asList(…), i.e. not an ArrayList, the toArray() method will make a copy, followed by ArrayList’s constructor making another copy. All that, while the original array is a new array anyway, for well behaving Stream implementations…


We can generalize the problem. The idiomatic initialization of collections, like new ArrayList<>(Arrays.asList(…)) or new ArrayList<>(List.of(…)) or likewise for PriorityQueue, get the penalty of a defensive copy despite only using JDK collections which should have a correct toArray implementation.

I’d probably use c.getClass().getClassLoader() != null instead of c.getClass() != ArrayList.class, to trust all built-in collections, whose implementation has been loaded by the bootstrap loader. But perhaps, that test would turn out to be more expensive than the ArrayList test¹ or there are too many untrustworthy built-in collections…

¹ The key point is whether the optimizer understands the construct well enough to predict its outcome.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文