java - 复制`ArrayList是否安全`以这种方式?
问题描述
这让我更加欣赏 Scala 中的不可变集合。
假设我们有一个 Java 静态类,它返回一个ArrayList<Integer>
介于 1 和某个指定截止值之间的素数。我从这个开始:
package org.oeis.primes;
import java.util.ArrayList;
public class PrimeLister {
private static final ArrayList<Integer> PRIMES = new ArrayList<>();
private static int currThresh;
static {
PRIMES.add(2);
PRIMES.add(3);
PRIMES.add(5);
PRIMES.add(7);
currThresh = 10;
}
// STUB TO FAIL THE FIRST TEST
public static ArrayList<Integer> listPrimes(int threshold) {
ArrayList<Integer> selPrimes = new ArrayList<>(PRIMES);
return selPrimes;
}
}
然后,经过几个 TDD 周期,listPrimes()
当threshold
大于currThresh
. 要通过此测试:
@Test
public void testPrimeListerCanTrim() {
int threshold = 80;
ArrayList<Integer> result = PrimeLister.listPrimes(threshold);
System.out.println("PrimeLister reports " + result.size()
+ " primes between 1 and " + threshold);
threshold = 20;
Integer[] smallPrimes = {2, 3, 5, 7, 11, 13, 17, 19};
ArrayList<Integer> expResult = new ArrayList<>(Arrays.asList(smallPrimes));
result = PrimeLister.listPrimes(threshold);
assertEquals(expResult, result);
}
listPrimes()
需要取 的一个子集PRIMES
。
// Not yet worried that threshold could be negative
if (threshold < currThresh) {
int trimIndex = PRIMES.size();
int p;
do {
trimIndex--;
p = PRIMES.get(trimIndex);
} while (p > threshold);
return new ArrayList<>(PRIMES.subList(0, trimIndex + 1));
}
使用素数定理肯定有一种更快的方法来找到正确trimIndex
的,但我目前并不担心。我担心的是subList()
Javadoc 中的这个花絮:
返回的列表由该列表支持,因此返回列表中的非结构性更改会反映在该列表中,反之亦然。...如果后备列表(即此列表)以除通过返回列表之外的任何方式进行结构修改,则此方法返回的列表的语义变得未定义。
我不太明白这意味着什么。我尝试更改listPrimes()
to的类型List<Integer>
并使其PRIMES
在threshold < currThresh
和threshold > currThresh
分支中都返回。然后我写了这个测试:
@Test
public void testModifyPrimeSubset() {
int threshold = 20;
List<Integer> subset = PrimeLister.listPrimes(threshold);
for (int i = 0; i < subset.size(); i++) {
int p = -subset.get(i);
subset.set(i, p);
}
threshold = 40;
Integer[] smallPrimes = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37};
ArrayList<Integer> expResult = new ArrayList<>(Arrays.asList(smallPrimes));
List<Integer> result = PrimeLister.listPrimes(threshold);
assertEquals(expResult, result);
}
正如预期PRIMES
的那样,直接返回允许调用者修改PrimeLister
私有质数存储。我的一个测试由于得到 -2、-3、-5 等而失败。其他测试导致IndexOutOfBoundsException
. 将副本恢复到新实例ArrayList<Integer>
会使所有测试再次通过。
我的问题:这是否足以防止对PrimeLister
的私有质数存储进行无意修改,还是我的想象力失败了?
解决方案
因此,您使用可变集合来保存不可变对象,但这些不可变对象包装原语,因此可能涉及一些装箱和拆箱。难怪你对此不确定。
但是看一下JDK源代码。您可以在 IntelliJ IDEA Ultimate Edition 中执行此操作,大概也可以在其他 IDE 中执行此操作。这是相关的构造函数:
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
由于多态性,有几种不同的可能性toArray()
被调用。但是,我们可能可以指望 JDK 的作者来维护某些保证。
public abstract Object[] toArray()
返回一个包含此集合中所有元素的数组。如果此集合对其迭代器返回其元素的顺序做出任何保证,则此方法必须以相同的顺序返回元素。
返回的数组将是“安全的”,因为此集合不维护对它的引用。(换句话说,即使此集合由数组支持,此方法也必须分配一个新数组)。因此,调用者可以自由修改返回的数组。[强调我的]
testModifyPrimeSubset()
因此,正如您已经ArrayList
在PrimeLister
.
即使整数没有被装箱和/或拆箱,支持返回的数组ArrayList
现在也独立于PrimeLister
的私有质数存储。
最后,我想有趣地指出,PrimeLister
如此嫉妒地保护本质上是公共领域的信息是多么荒谬。这是对 Java 的一般评论,而不是对您的程序的评论。
推荐阅读
- react-native - FlatList renderItem 被多次调用
- html - 如何在 HTML 中查找和删除意外的代码行
- python - 使用 mpi4py.futures.MPICommExecutor() 的 MPI python 程序未完成执行
- c# - 如何连接通过 aspx 重定向流中的重定向生成的应用程序日志?
- authentication - Windows 终端服务器登录失败 - RST SYN
- python - 如何将列表的列表转换为具有索引的数据框中的列?
- unit-testing - Jest - 如何重组代码以使其可测试?
- php - Laravel 和 Eloquent - 应用不同的过滤器
- android - 如何通过点击 Flutter 应用程序中的按钮,使用默认应用程序打开 PPT、PDF、文档等?
- ios - 如何使用 searchBar 在元组中搜索?