Guava的ImmutableCollection
子类ImmutableList
是(不可扩展的)抽象类,而不是接口。该文件说,这是为了防止外部亚型。另一方面,文档还说
应该被认为是每个重要意义上的接口
但是,在外部进行子类型化的功能不是一种重要的界面含义吗?例如,如果ImmutableList
是一个接口,我可以有一个方法签名,例如
public ImmutableList<String> getNames();
Run Code Online (Sandbox Code Playgroud)
(就像Guava的建议一样),然后可以灵活地ImmutableList
将来交换自定义实现。但是,由于实际上它是一个抽象类,所以我没有这种灵活性,因此必须绑定到Guava的实现。因此,如果我想保持这种灵活性,就不得不使用更通用的return类型List
,该类型不再向调用者传达有关不变性的有用信息:
public List<String> getNames();
Run Code Online (Sandbox Code Playgroud)
那么为什么不从外部进行子类型化很重要?一个答案可能是Guava设计者不信任外部实现者来适当地支持所需的语义,但List
实际上它本身具有相当广泛的约定,而且没人阻止这种定制实现。还是还有其他原因?
在Java Concurrency in Practice中,第106页,它说" Memoizer3
容易受到问题的影响[两个线程看到null并开始昂贵的计算]",因为在后备映射上执行了复合操作(如果不存在),这些操作无法成为原子使用锁定." 我不明白他们为什么说使用锁定不能成为原子.这是原始代码:
package net.jcip.examples;
import java.util.*;
import java.util.concurrent.*;
/**
* Memoizer3
* <p/>
* Memoizing wrapper using FutureTask
*
* @author Brian Goetz and Tim Peierls
*/
public class Memoizer3 <A, V> implements Computable<A, V> {
private final Map<A, Future<V>> cache
= new ConcurrentHashMap<A, Future<V>>();
private final Computable<A, V> c;
public Memoizer3(Computable<A, V> c) {
this.c = c;
}
public V compute(final A arg) throws InterruptedException {
Future<V> f = cache.get(arg);
if (f == null) …
Run Code Online (Sandbox Code Playgroud) 人们常常看到变量应该用某个接口声明的建议,而不是实现类.例如:
List<Integer> list = new ArrayList<>();
Run Code Online (Sandbox Code Playgroud)
但是,假设我使用此列表来实际依赖于ArrayList
(例如Fisher-Yates shuffling)的O(1)随机访问的算法.在这种情况下,ArrayList
代表我的关键抽象是它的数组性质,而不仅仅是它的List性质.换句话说,如果某人出现并更改list
为a LinkedList
,即使代码可以编译,也会出现问题.在这种情况下,声明是否可以使用实现类型?,例如:
ArrayList<Integer> list = new ArrayList<>();
Run Code Online (Sandbox Code Playgroud) 请考虑以下代码段:
List<String> list = new LinkedList<>();
list.add("Hello");
list.add("My");
list.add("Son");
for (String s: list){
if (s.equals("My")) list.remove(s);
System.out.printf("s=%s, list=%s\n",s,list.toString());
}
Run Code Online (Sandbox Code Playgroud)
这导致输出:
s = Hello,list = [Hello,My,Son]
s = My,list = [Hello,Son]
很明显,循环只输入两次,第三个元素"Son"永远不会被访问.从底层的库代码中,看起来发生的是hasNext()
迭代器中的方法不检查并发修改,只检查下一个索引的大小.由于remove()
调用后大小减少了1 ,因此循环不会再次输入,但不会抛出ConcurrentModificationException.
这似乎与迭代器的契约相矛盾:
list-iterator是快速失败的:如果在创建Iterator之后的任何时候对列表进行结构修改,除了通过list-iterator自己的
remove
或add
方法之外,list-iterator将抛出一个ConcurrentModificationException
.因此,在并发修改的情况下,迭代器快速而干净地失败,而不是在未来的未确定时间冒任意,非确定性行为的风险.
这是一个错误吗?同样,迭代器的契约在这里看起来肯定是不服从的 - 列表的结构在迭代过程中由迭代器之外的其他东西进行结构修改.
RandomAccess
是Java中的标记接口,用于List
表示它们可以快速随机访问其元素.由于它是专门为List
实现而设计的,为什么不在List
层次结构中呢?例如,请考虑以下方法,该方法需要RandomAccess
列表作为输入并返回随机元素:
public <E, L extends List<E> & RandomAccess> E getRandomElement(L list) {...}
Run Code Online (Sandbox Code Playgroud)
我必须将此方法传递给匿名列表或具有声明类型的列表ArrayList<E>
,具体类,而不是使用某些接口声明的列表List<E>
.但是,如果RandomAccess
参数化和扩展List<E>
,那么我的方法可能如下所示:
public <E, L extends RandomAccess<E>> E getRandomElement(L list) {...}
Run Code Online (Sandbox Code Playgroud)
我可以传递一个声明类型的列表RandomAccess<E>
,这是一个接口,而不是具体的类.ArrayList<E>
然后才会实施RandomAccess<E>
,而不是List<E>
.
java ×5
list ×2
concurrency ×1
declaration ×1
guava ×1
immutability ×1
inheritance ×1
linked-list ×1