Java中的ImmutableList周围的伪装

我读了文章“ Java中将不会有不可变的集合-现在或现在都不会”,并认为Java中缺少不可变列表的问题在一定程度上是可以解决的,这使作者感到难过。 我提供有关此主题的想法和代码。


(这是答案文章,请先阅读原始文章。)


UnmodifiableList与ImmutableList


出现的第一个问题是:如果有ImmutableList ,为什么我需要一个UnmodifiableList ? 讨论的结果是,在原始文章的注释中看到了有关UnmodifiableList含义的两种想法:


  • 该方法收到一个UnmodifiableList ,它本身不能更改,但是知道内容可以被另一个线程更改(并且知道如何正确处理它)
  • 其他线程ImmutableListUnmodifiableListImmutableList等效于一种方法,但UnmodifiableList用作更“轻量”的方法。

在实践中,第一种选择似乎太少了。 因此,如果您可以使ImmutableList的“轻松”实现,则UnmodifiableList变得不是非常必要。 因此,将来我们会忘记它,只会实现ImmutableList


问题陈述


我们将实现ImmutableList选项:


  • 该API应该与“阅读”部分中的常规List API相同。 应该没有“写作”部分。
  • ImmutableListList不应通过继承关系进行关联。 为什么这样-了解原始文章。
  • 用类ArrayList实现是有意义的。 这是最简单的选择。
  • 该实现应尽可能避免复制数组。

ImmutableList实现


首先,我们处理API。 我们检查了CollectionList接口,并将其中的“阅读”部分复制到我们的新接口中。


 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构造函数也从集合中复制数据。 除非orginalArrayListArrays$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) { //   ArrayList,      -  arr = original.toArray(); } this.data = arr; } 

作为一种约定,我们假设在调用构造函数时,可变列表将ImmutableListImmutableList 。 此后无法使用原始列表。 尝试使用时,将到达NullPointerException 。 这样可以确保“被盗”的数组不会发生变化,并且我们的列表将是真正不变的(除非有人通过反射进入数组)。


其他班


假设我们决定在实际项目中使用ImmutableList


该项目与库进行交互:从库接收并向它们发送各种列表。 在大多数情况下,这些列表将是ArrayList 。 所描述的ImmutableList实现可让ImmutableList快速将生成的ArrayList转换为ImmutableList 。 还需要实现发送到库的列表的转换: ImmutableListList 。 为了快速转换,您需要一个实现ListImmutableList包装器,在尝试写入列表时会抛出异常(类似于Collections.unmodifiableList )。


同样,项目本身以某种方式处理列表。 通过基于ArrayList的实现,创建一个表示可变列表的MutableList类是有意义的。 在这种情况下,可以通过为所有ArrayList替换ArrayList明确声明其意图ArrayList类来重构项目: ImmutableListMutableList


需要从ImmutableListMutableList的快速转换,反之亦然。 同时,与ArrayListImmutableList的转换不同,我们不能再破坏原始列表。


复制数组通常会很慢。 但是,对于接收到的MutableList并非总是更改的情况,您可以进行包装: MutableList ,它将保存对ImmutableList的引用,并将其用于“读取”方法,如果调用了“写入”方法,则在复制其内容之后只会忘记ImmutableList数组本身,然后它已经可以使用它的数组了(在CopyOnWriteArrayList有点相似)。


转换“返回”意味着在调用该方法时接收MutableList内容的快照。 同样,在大多数情况下,您不能不复制数组而做,但是可以进行包装以优化多次转换的情况,在MutableList转换之间MutableList的内容不会改变。 转换“返回”的另一个选项: MutableList收集了一些数据,当数据收集完成后,需要将MutableList永久转换为ImmutableList 。 另一个包装程序也可以顺利实现。


合计


实验结果以代码形式发布在此处


ImmutableList本身是已ImmutableList ,这在“其他类”部分中进行了描述(不是吗?)。


我们可以假设原始文章“ Java中的不可变集合将不会”的前提是错误的。


如果有需求,那么很有可能使用类似的方法。 是的,with着拐杖。 是的,不在整个系统中,而仅在他们的项目中(尽管如果有很多渗透的话,它将逐渐进入库中)。


一件事:如果有欲望... (塔希提岛,塔希提岛……我们不在塔希提岛!他们在这里很好地养活了我们。)

Source: https://habr.com/ru/post/zh-CN470257/


All Articles