我读了文章“ Java中将不会有不可变的集合-现在或现在都不会”,并认为Java中缺少不可变列表的问题在一定程度上是可以解决的,这使作者感到难过。 我提供有关此主题的想法和代码。
(这是答案文章,请先阅读原始文章。)
UnmodifiableList与ImmutableList
出现的第一个问题是:如果有ImmutableList
,为什么我需要一个UnmodifiableList
? 讨论的结果是,在原始文章的注释中看到了有关UnmodifiableList
含义的两种想法:
- 该方法收到一个
UnmodifiableList
,它本身不能更改,但是知道内容可以被另一个线程更改(并且知道如何正确处理它) - 其他线程
ImmutableList
, UnmodifiableList
和ImmutableList
等效于一种方法,但UnmodifiableList
用作更“轻量”的方法。
在实践中,第一种选择似乎太少了。 因此,如果您可以使ImmutableList
的“轻松”实现,则UnmodifiableList
变得不是非常必要。 因此,将来我们会忘记它,只会实现ImmutableList
。
问题陈述
我们将实现ImmutableList
选项:
- 该API应该与“阅读”部分中的常规
List
API相同。 应该没有“写作”部分。 ImmutableList
和List
不应通过继承关系进行关联。 为什么这样-了解原始文章。- 用类
ArrayList
实现是有意义的。 这是最简单的选择。 - 该实现应尽可能避免复制数组。
ImmutableList实现
首先,我们处理API。 我们检查了Collection
和List
接口,并将其中的“阅读”部分复制到我们的新接口中。
public interface ReadOnlyCollection<E> extends Iterable<E> { int size(); boolean isEmpty(); boolean contains(Object o); Object[] toArray(); <T> T[] toArray(T[] a); boolean containsAll(Collection<?> c); } public interface ReadOnlyList<E> extends ReadOnlyCollection<E> { E get(int index); int indexOf(Object o); int lastIndexOf(Object o); ListIterator<E> listIterator(); ListIterator<E> listIterator(int index); ReadOnlyList<E> subList(int fromIndex, int toIndex); }
接下来,创建ImmutableList
类。 签名类似于ArrayList
(但实现ReadOnlyList
接口而不是List
)。
public class ImmutableList<E> implements ReadOnlyList<E>, RandomAccess, Cloneable, Serializable
我们从ArrayList
复制该类的实现并进行牢固的重构,丢弃与“编写”部分相关的所有内容,检查并发修改等。
构造函数如下:
public ImmutableList() public ImmutableList(E[] original) public ImmutableList(Collection<? extends E> original)
第一个创建一个空列表。 第二个通过复制数组创建列表。 如果我们想实现不可变,就离不开复制。 第三个更有趣。 类似的ArrayList
构造函数也从集合中复制数据。 除非orginal
是ArrayList
或Arrays$ArrayList
的实例(这是Arrays.asList()
方法返回的Arrays.asList()
,否则我们将执行相同的操作。 我们可以安全地假设这些情况将覆盖90%的构造函数调用。
在这些情况下,我们将通过反射来“窃取” original
阵列(希望这比复制千兆字节阵列要快)。 “盗窃”的实质:
- 我们进入私有字段
original
,它存储数组( ArrayList.elementData
) - 将链接复制到我们自己的数组
- 放入源字段null
protected static final Field data_ArrayList; static { try { data_ArrayList = ArrayList.class.getDeclaredField("elementData"); data_ArrayList.setAccessible(true); } catch (NoSuchFieldException | SecurityException e) { throw new IllegalStateException(e); } } public ImmutableList(Collection<? extends E> original) { Object[] arr = null; if (original instanceof ArrayList) { try { arr = (Object[]) data_ArrayList.get(original); data_ArrayList.set(original, null); } catch (@SuppressWarnings("unused") IllegalArgumentException | IllegalAccessException e) { arr = null; } } if (arr == null) {
作为一种约定,我们假设在调用构造函数时,可变列表将ImmutableList
为ImmutableList
。 此后无法使用原始列表。 尝试使用时,将到达NullPointerException
。 这样可以确保“被盗”的数组不会发生变化,并且我们的列表将是真正不变的(除非有人通过反射进入数组)。
其他班
假设我们决定在实际项目中使用ImmutableList
。
该项目与库进行交互:从库接收并向它们发送各种列表。 在大多数情况下,这些列表将是ArrayList
。 所描述的ImmutableList
实现可让ImmutableList
快速将生成的ArrayList
转换为ImmutableList
。 还需要实现发送到库的列表的转换: ImmutableList
到List
。 为了快速转换,您需要一个实现List
的ImmutableList
包装器,在尝试写入列表时会抛出异常(类似于Collections.unmodifiableList
)。
同样,项目本身以某种方式处理列表。 通过基于ArrayList
的实现,创建一个表示可变列表的MutableList
类是有意义的。 在这种情况下,可以通过为所有ArrayList
替换ArrayList
明确声明其意图ArrayList
类来重构项目: ImmutableList
或MutableList
。
需要从ImmutableList
到MutableList
的快速转换,反之亦然。 同时,与ArrayList
到ImmutableList
的转换不同,我们不能再破坏原始列表。
复制数组通常会很慢。 但是,对于接收到的MutableList
并非总是更改的情况,您可以进行包装: MutableList
,它将保存对ImmutableList
的引用,并将其用于“读取”方法,如果调用了“写入”方法,则在复制其内容之后只会忘记ImmutableList
数组本身,然后它已经可以使用它的数组了(在CopyOnWriteArrayList
有点相似)。
转换“返回”意味着在调用该方法时接收MutableList
内容的快照。 同样,在大多数情况下,您不能不复制数组而做,但是可以进行包装以优化多次转换的情况,在MutableList
转换之间MutableList
的内容不会改变。 转换“返回”的另一个选项: MutableList
收集了一些数据,当数据收集完成后,需要将MutableList
永久转换为ImmutableList
。 另一个包装程序也可以顺利实现。
合计
实验结果以代码形式发布在此处
ImmutableList
本身是已ImmutableList
,这在“其他类”部分中进行了描述(不是吗?)。
我们可以假设原始文章“ Java中的不可变集合将不会”的前提是错误的。
如果有需求,那么很有可能使用类似的方法。 是的,with着拐杖。 是的,不在整个系统中,而仅在他们的项目中(尽管如果有很多渗透的话,它将逐渐进入库中)。
一件事:如果有欲望... (塔希提岛,塔希提岛……我们不在塔希提岛!他们在这里很好地养活了我们。)