在我以前的文章“ 用Java掩盖ImmutableList”中,我提出了一个解决方案,解决了Java中不存在不可变列表的问题,在Java中无论现在还是现在都不是固定的。
然后,仅在“有这样一个想法”的级别上制定解决方案,并且代码中的实现歪曲了,因此一切都被怀疑了。 在本文中,我提出了一种改进的解决方案。 使用的逻辑和API已达到可接受的水平。 在代码中的实现达到beta级。
问题陈述
我们将使用原始文章中的定义。 特别是,这意味着ImmutableList
是对某些对象的引用的不可变列表。 如果事实证明这些对象不是不可变的,那么尽管名称如此,列表也将不是一个不可变的对象。 实际上,这不太可能伤害任何人,但是为了避免不合理的期望,有必要提一下。
同样很明显,可以通过反射或在同一包中创建自己的类,然后弹出列表的受保护字段或类似的方式来“破坏”列表的不变性。
与原始文章不同,我们不会坚持“全有或全无”的原则:那里的作者似乎认为,如果不能在JDK级别解决问题,那么就什么也不做。 (实际上,还有另一个问题,“无法解决”或“ Java作者不希望解决它。”在我看来,通过添加其他接口,类和方法来使现有集合更接近,还是有可能的。所需的外观,虽然比您立即想到的外观要漂亮,但现在并不是这样。)
我们将创建一个可以与Java现有集合成功共存的库。
图书馆的主要思想:
- 有
ImmutableList
和MutableList
。 通过强制转换类型,不可能从另一个中获取一个。 - 在我们要使用库进行改进的项目中,我们用这两个接口之一替换了所有
List
。 如果在某个时候您不能没有List
,那么我们将在第一个机会将List
从/转换为两个接口之一。 这同样适用于使用List
接收/传输数据到第三方库的时刻。 ImmutableList
, MutableList
, List
之间的相互转换应尽快执行(也就是说,如果可能的话,不复制列表)。 没有“便宜的”往返转换,整个想法就开始变得可疑。
应该注意的是,仅考虑列表,因为目前仅在库中实现它们。 但是,没有什么可以阻止库对Set
和Map
补充。
API
不可变列表
ImmutableList
是ReadOnlyList
的后继者(与上一篇文章一样, 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
实现中, iterator
和listIterator
返回标准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
实现相对应,从中抛出所有修改方法和并发修改检查。
toList
和contentEquals
也很简单。 toList
方法返回一个包装器,该包装器将调用重定向到给定的ImmutableList
;不会发生数据的慢速复制。
mutable
方法返回基于此ImmutableList
创建的MutableListImpl
。 在接收到的MutableList
上调用任何修改方法之前,不会发生数据复制。
MutableListImpl
封装指向ImmutableList
和List
链接。 创建对象时,始终仅填充这两个链接之一,另一个保持为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) {
releaseSnapshot
和contentEquals
简单。
这种方法使您可以在“常规”使用期间最大程度地减少数据副本的数量,从而用快速转换替换副本。
快速列表转换
ArrayList
或Arrays$ArrayList
类( Arrays.asList()
方法的结果Arrays.asList()
可以进行快速转换。 实际上,在大多数情况下,碰巧就是这些类。
这些类中包含一个元素数组。 快速转换的本质是通过反射获取对此数组的引用(这是一个私有字段),并用对空数组的引用替换它。 这样可以确保对数组的唯一引用保留在我们的对象中,并且数组保持不变。
在该库的先前版本中,集合类型的快速转换通过调用构造函数来执行。 同时,原始的收集对象变坏了(它变得不适合进一步使用),这是您在不知不觉中不希望设计师看到的。 现在,使用了一种特殊的静态方法进行转换,原始集合不会变质,只是被清除了。 因此,消除了令人恐惧的异常行为。
equals / hashCode问题
Java集合使用一种非常奇怪的方法来实现equals
和hashCode
方法。
比较是根据内容执行的,这似乎是合乎逻辑的,但是未考虑列表本身的类别。 因此,例如,具有相同内容的ArrayList
和LinkedList
将equals
。
这是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)
不好的情况。 Set
和Map
情况与此类似。
在该库的应用程序中,这意味着equals
和hashCode
for MutableList
预定义的,并且在这种实现中,具有相同内容的ImmutableList
和MutableList
不能equals
(因为ImmutableList
不是List
)。 因此,添加了contentEquals
方法来比较内容。
使ImmutableList
的equals
和hashCode
方法的实现与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; }
祝大家好运! 发送错误报告!