当前位置: 首页 > 面试题库 >

集合emptyList / singleton / singletonList / List / Set toArray

宿嘉庆
2023-03-14
问题内容

假设我有以下代码:

String[] left = { "1", "2" };
String[] leftNew = Collections.emptyList().toArray(left);
System.out.println(Arrays.toString(leftNew));

这将打印[null, 2]。这个 排序
是有道理的,因为我们有一个空列表它是某种假设来处理那些我们传递一个数组,它是更大的,并设置第一个元素为空的事实。这可能是说第一个元素在空列表中不存在,因此将其设置为null

但这仍然令人困惑,因为我们传递具有特定类型的数组只是为了帮助推断 返回 数组的类型。但是无论如何,这至少具有一定的逻辑。但是,如果我这样做:

String[] right = { "nonA", "b", "c" };
// or Collections.singletonList("a");
// or a plain List or Set; does not matter
String[] rightNew = Collections.singleton("a").toArray(right);
System.out.println(Arrays.toString(rightNew));

以前面的示例为参考,我希望这个示例可以显示:

["a", "b", "c"]

但是,它对我来说有点出乎意料,它会打印:

[a, null, c]

而且,当然,我去了明确表明这是预期的文档:

如果此集合适合于具有剩余空间的指定数组(即,数组中的元素多于该集合),则紧接该集合结尾的数组中的元素将设置为null。

好,很好,至少已记录在案。但后来说:

仅当调用者知道此集合不包含任何null元素时,这才对确定此集合的长度很有用。

这是文档中最让我感到困惑的部分:|

还有一个对我来说毫无意义的有趣示例:

String[] middle = { "nonZ", "y", "u", "m" };
List<String> list = new ArrayList<>();
list.add("z");
list.add(null);
list.add("z1");
System.out.println(list.size()); // 3

String[] middleNew = list.toArray(middle);
System.out.println(Arrays.toString(middleNew));

这将打印:

[z, null, z1, null]

因此,它清除了数组中的最后一个元素,但是为什么在第一个示例中不这样做呢?

有人可以在这里说些什么吗?


问题答案:

<T> T[] toArray(T[] a)Collection上的方法很奇怪,因为它试图同时实现两个目的。

首先,让我们看一下toArray()。这将从集合中获取元素,并将其返回到中Object[]。也就是说,返回数组的组件类型始终为Object。这很有用,但不能满足其他几个用例:

1)如果可能,调用者想 重用 现有的数组;和

2)调用者想指定返回数组的组件类型。

处理情况(1)原来是一个相当微妙的API问题。调用方想重用一个数组,因此显然需要传递它。与no-arg toArray()方法不同,no-arg
方法返回正确大小的数组,如果调用方的数组被重用,我们需要一种方法返回复制的元素数。好的,让我们有一个看起来像这样的API:

int toArray(T[] a)

调用者传入一个数组,该数组被重用,返回值是复制到其中的元素数。不需要返回该数组,因为调用者已经拥有对该数组的引用。但是,如果数组太小怎么办?好吧,也许抛出一个例外。实际上,这就是Vector.copyInto所做的。

void copyInto​(Object[] anArray)

这是一个糟糕的API。它不仅不返回复制的元素数,而且IndexOutOfBoundsException如果目标数组太短也会抛出该异常。由于Vector是并发集合,因此大小可能在调用之前随时更改,因此调用方
无法 保证目标数组具有足够的大小,也不知道复制的元素数。调用者唯一能做的就是将Vector锁定在整个序列周围:

synchronized (vec) {
    Object[] a = new Object[vec.size()];
    vec.copyInto(a);
}

啊!

Collections.toArray(T[])如果目标数组太小,API会通过采取不同的行为来避免此问题。不会抛出类似Vector.copyInto()的异常,而是分配一个大小合适的

数组。这样就折衷了阵列重用的情况,以获得更可靠的操作。现在的问题是,调用者无法确定其数组是被重用还是分配了新的数组。因此,的返回值toArray(T[])需要返回一个数组:参数数组(如果足够大)或新分配的数组。

但是现在我们还有另一个问题。我们不再有办法告诉调用者从集合中复制到数组中的元素数量。如果目标数组是新分配的,或者数组恰好是正确的大小,则数组的长度就是复制的元素数。如果目标阵列比复制元件的数量较大,则该方法尝试进行通信,以复制的元素的数目的呼叫者,通过写null到阵列位置
一个超出
从集合中复制的最后一个元素。如果知道源集合没有空值,则调用者可以确定复制的元素数。调用之后,调用方可以搜索数组中的第一个空值。如果存在,则其位置确定要复制的元素数。如果数组中没有null,则知道复制的元素数等于数组的长度。

坦白说,这真是la脚。但是,考虑到当时对语言的限制,我承认我没有更好的选择。

我认为我从未见过任何重用数组或以这种方式检查null的代码。这可能是内存分配和垃圾回收昂贵的早期阶段的一个保留,因此人们希望尽可能多地重用内存。最近,使用此方法的惯用语已成为上述第二个用例,即按如下所述建立所需的数组组件类型:

MyType[] a = coll.toArray(new MyType[0]);

(为此目的分配一个零长度的数组似乎很浪费,但是事实证明,JIT编译器可以优化这种分配,并且明显的替代方案toArray(new MyType[coll.size()])实际上要慢一些。这是因为需要将数组初始化为,然后将其填充为集合的内容。有关此主题,请参见Alexey
Shipilev的文章 “古代智慧的数组” 。)

但是,许多人发现零长度数组是违反直觉的。在JDK 11中,有一个新的API,它允许人们使用数组构造函数引用:

MyType[] a = coll.toArray(MyType[]::new);

这使调用者可以指定数组的组件类型,但可以让集合提供大小信息。



 类似资料:
  • 问题内容: 我只是研究了通用编程,接口和,所以我可以理解下面的语句。 但是我不理解在浏览网络时看到的下一条语句。 什么啊 为什么不是还是? 为什么将方法名称放在前面? (对于Generic来说不正确吗?) 该声明是什么意思? 问题答案: 该行通过使用泛型类型参数调用静态方法来创建一个空字符串列表。 在类内部,有一个静态方法声明为: 这具有通用类型参数。我们使用以下方法调用此方法: 并被推断为是由于

  • 主要内容:ArrayList 类,LinkedList类,ArrayList 类和 LinkedList 类的区别List 是一个 有序、可重复的集合,集合中每个元素都有其对应的顺序索引。List 集合允许使用重复元素,可以通过索引来访问指定位置的集合元素。List 集合默认按元素的添加顺序设置元素的索引,第一个添加到 List 集合中的元素的索引为 0,第二个为 1,依此类推。 List 实现了 Collection 接口,它主要有两个常用的实现类: ArrayList 类和 LinkedLi

  • 问题内容: 我找不到使用此方法的任何示例,所有示例都给出了第二个参数“ null”。我听说此方法用于根据多个标准对类进行排序,但找不到示例。 对于本课程,如果我想根据学生的姓名和年龄对学生列表进行排序,如何使用方法Collections sort(List,Comparator) 问题答案: 在你现有的学生班级的基础上,这通常是我的工作方式,尤其是当我需要多个比较器时。 用法: 编辑 自Java

  • 本文向大家介绍java集合中的list详解,包括了java集合中的list详解的使用技巧和注意事项,需要的朋友参考一下 1、List接口 该接口定义的元素是有序的且可重复的。相当于数学里面的数列,有序可重复 booleanaddAll(intindex,Collection<?extendsE>c);将指定集合中所有元素,插入至本集合第index个元素之后defaultvoidreplaceAll

  • Groovy 为预定义的 List 和 Map 集合提供了一些操作捷径,这两个字面值都比较简单易懂,但是 Map 会有一些不同. 例如,当您使用 “apply” 方法使用插件时,apply 会自动加上 Map 的一个参数,当您这样写 “ apply plugin: ‘java’ “时,实际上使用的是 name 参数(name-value),只不过在 Groovy 中 使用 Map 没有 < > ,

  • 一、List字面量 List 是 Scala 中非常重要的一个数据结构,其与 Array(数组) 非常类似,但是 List 是不可变的,和 Java 中的 List 一样,其底层实现是链表。 scala> val list = List("hadoop", "spark", "storm") list: List[String] = List(hadoop, spark, storm) //