再次关于Java中的ImmutableList

在我以前的文章“ 用Java掩盖ImmutableList”中,我提出了一个解决方案,解决了Java中不存在不可变列表的问题,在Java中无论现在还是现在都不是固定的。


然后,仅在“有这样一个想法”的级别上制定解决方案,并且代码中的实现歪曲了,因此一切都被怀疑了。 在本文中,我提出了一种改进的解决方案。 使用的逻辑和API已达到可接受的水平。 在代码中的实现达到beta级。


问题陈述


我们将使用原始文章中的定义。 特别是,这意味着ImmutableList是对某些对象的引用的不可变列表。 如果事实证明这些对象不是不可变的,那么尽管名称如此,列表也将不是一个不可变的对象。 实际上,这不太可能伤害任何人,但是为了避免不合理的期望,有必要提一下。


同样很明显,可以通过反射或在同一包中创建自己的类,然后弹出列表的受保护字段或类似的方式来“破坏”列表的不变性。


与原始文章不同,我们不会坚持“全有或全无”的原则:那里的作者似乎认为,如果不能在JDK级别解决问题,那么就什么也不做。 (实际上,还有另一个问题,“无法解决”或“ Java作者不希望解决它。”在我看来,通过添加其他接口,类和方法来使现有集合更接近,还是有可能的。所需的外观,虽然比您立即想到的外观要漂亮,但现在并不是这样。)


我们将创建一个可以与Java现有集合成功共存的库。


图书馆的主要思想:


  • ImmutableListMutableList 。 通过强制转换类型,不可能从另一个中获取一个。
  • 在我们要使用库进行改进的项目中,我们用这两个接口之一替换了所有List 。 如果在某个时候您不能没有List ,那么我们将在第一个机会将List从/转换为两个接口之一。 这同样适用于使用List接收/传输数据到第三方库的时刻。
  • ImmutableListMutableListList之间的相互转换应尽快执行(也就是说,如果可能的话,不复制列表)。 没有“便宜的”往返转换,整个想法就开始变得可疑。

应该注意的是,仅考虑列表,因为目前仅在库中实现它们。 但是,没有什么可以阻止库对SetMap补充。


API


不可变列表


ImmutableListReadOnlyList的后继者(与上一篇文章一样, ReadOnlyList是一个复制的List接口,从中抛出所有变异方法)。 添加的方法:


 List<E> toList(); MutableList<E> mutable(); boolean contentEquals(Iterable<? extends E> iterable); 

toList方法提供了将ImmutableList传递给等待List的代码段的功能。 返回一个包装器,所有修改方法都返回一个UnsupportedOperationException ,然后将其余方法重定向到原始ImmutableList


mutable方法将ImmutableList转换为MutableList 。 返回包装器,在该包装器中,所有方法都将重定向到原始ImmutableList直到进行第​​一次更改。 更改之前,将包装与原始ImmutableList ,将其内容复制到内部ArrayList ,然后将所有操作重定向到该内部。


contentEquals方法用于将列表的内容与传递的任意Iterable的内容进行比较(当然,此操作仅对具有某些不同元素顺序的Iterable实现有意义。)


请注意,在我们的ReadOnlyList实现中, iteratorlistIterator返回标准java.util.Iterator / java.util.ListIterator 。 这些迭代器包含修改方法,必须通过抛出UnsupportedOperationException来抑制这些修改方法。 最好使用ReadOnlyIterator ,但是在这种情况下,我们无法编写for (Object item : immutableList) ,这将立即破坏使用该库的所有乐趣。


可变列表


MutableList是常规List的后代。 添加的方法:


 ImmutableList<E> snapshot(); void releaseSnapshot(); boolean contentEquals(Iterable<? extends E> iterable); 

snapshot方法旨在将MutableList当前状态的“快照”作为ImmutableList 。 “快照”保存在MutableList ,如果在下一次方法调用时状态未更改, ImmutableList的相同实例。 第一次调用任何修改方法时或调用releaseSnapshot时,将丢弃存储在其中的“快照”。 如果您确定没有人需要“快照”,则可以使用releaseSnapshot方法来节省内存,但是不会很快调用修改方法。


Mutabor


Mutabor类提供了一组静态方法,这些方法是库的“入口”。


是的,该项目现在被称为“ mutabor”(与“ mutable”相辅相成,在翻译中意为“我将转型”,这与将某些类型的馆藏快速“转化”为其他类型的想法非常一致)。


 public static <E> ImmutableList<E> copyToImmutableList(E[] original); public static <E> ImmutableList<E> copyToImmutableList(Collection<? extends E> original); public static <E> ImmutableList<E> convertToImmutableList(Collection<? extends E> original); public static <E> MutableList<E> copyToMutableList(Collection<? extends E> original); public static <E> MutableList<E> convertToMutableList(List<E> original); 

copyTo*方法旨在通过复制提供的数据来创建适当的集合。 convertTo*方法convertTo*将传输的集合快速转换为所需的类型,如果无法快速转换,则执行慢速复制。 如果快速转换成功,则原始集合将被清除,并假定将来不会使用它(尽管可以,但这几乎没有道理)。


ImmutableList / MutableList实现ImmutableList的构造函数的调用MutableList隐藏的。 假定用户仅处理接口,他不创建此类对象,而是使用上述方法转换集合。


实施细节


ImmutableListImpl


封装对象数组。 该实现大致与ArrayList实现相对应,从中抛出所有修改方法和并发修改检查。


toListcontentEquals也很简单。 toList方法返回一个包装器,该包装器将调用重定向到给定的ImmutableList ;不会发生数据的慢速复制。


mutable方法返回基于此ImmutableList创建的MutableListImpl 。 在接收到的MutableList上调用任何修改方法之前,不会发生数据复制。


MutableListImpl


封装指向ImmutableListList链接。 创建对象时,始终仅填充这两个链接之一,另一个保持为null


 protected ImmutableList<E> immutable; protected List<E> list; 

不可变方法将调用重定向到ImmutableList如果它不为null ,否则重定向到List


初始化后,修改方法会将调用重定向到List


 protected void beforeChange() { if (list == null) { list = new ArrayList<>(immutable.toList()); } immutable = null; } 

snapshot方法如下所示:


 public ImmutableList<E> snapshot() { if (immutable != null) { return immutable; } immutable = InternalUtils.convertToImmutableList(list); if (immutable != null) { //    //   ,  . //     immutable     . list = null; return immutable; } immutable = InternalUtils.copyToImmutableList(list); return immutable; } 

releaseSnapshotcontentEquals简单。


这种方法使您可以在“常规”使用期间最大程度地减少数据副本的数量,从而用快速转换替换副本。


快速列表转换


ArrayListArrays$ArrayList类( Arrays.asList()方法的结果Arrays.asList()可以进行快速转换。 实际上,在大多数情况下,碰巧就是这些类。


这些类中包含一个元素数组。 快速转换的本质是通过反射获取对此数组的引用(这是一个私有字段),并用对空数组的引用替换它。 这样可以确保对数组的唯一引用保留在我们的对象中,并且数组保持不变。


在该库的先前版本中,集合类型的快速转换通过调用构造函数来执行。 同时,原始的收集对象变坏了(它变得不适合进一步使用),这是您在不知不觉中不希望设计师看到的。 现在,使用了一种特殊的静态方法进行转换,原始集合不会变质,只是被清除了。 因此,消除了令人恐惧的异常行为。


equals / hashCode问题


Java集合使用一种非常奇怪的方法来实现equalshashCode方法。


比较是根据内容执行的,这似乎是合乎逻辑的,但是未考虑列表本身的类别。 因此,例如,具有相同内容的ArrayListLinkedListequals


这是AbstractList的equals / hashCode实现(继承ArrayList)
 public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof List)) return false; ListIterator<E> e1 = listIterator(); ListIterator e2 = ((List) o).listIterator(); while (e1.hasNext() && e2.hasNext()) { E o1 = e1.next(); Object o2 = e2.next(); if (!(o1==null ? o2==null : o1.equals(o2))) return false; } return !(e1.hasNext() || e2.hasNext()); } public int hashCode() { int hashCode = 1; for (E e : this) hashCode = 31*hashCode + (e==null ? 0 : e.hashCode()); return hashCode; } 

因此,现在绝对需要所有List实现都具有相似的equals实现(因此,是hashCode )。 否则,您会遇到a.equals(b) && !b.equals(a)不好的情况。 SetMap情况与此类似。


在该库的应用程序中,这意味着equalshashCode for MutableList预定义的,并且在这种实现中,具有相同内容的ImmutableListMutableList不能equals (因为ImmutableList不是List )。 因此,添加了contentEquals方法来比较内容。


使ImmutableListequalshashCode方法的实现与AbstractList的版本完全相似,但用ReadOnlyList替换List


合计


库的源代码和测试以Maven项目的形式通过引用发布


如果有人要使用该库,他已经创建了一个“反馈” 联系人组


使用该库非常明显,这是一个简短的示例:


 private boolean myBusinessProcess() { List<Entity> tempFromDb = queryEntitiesFromDatabase("SELECT * FROM my_table"); ImmutableList<Entity> fromDb = Mutabor.convertToImmutableList(tempFromDb); if (fromDb.isEmpty() || !someChecksPassed(fromDb)) { return false; } //... MutableList<Entity> list = fromDb.mutable(); //time to change list.remove(1); ImmutableList<Entity> processed = list.snapshot(); //time to change ended //... if (!callSideLibraryExpectsListParameter(processed.toList())) { return false; } for (Entity entity : processed) { outputToUI(entity); } return true; } 

祝大家好运! 发送错误报告!

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


All Articles