如果我想调用这样的方法:
List f(List l){
l.add(new Object());
return l;
}
一切都很好,除非我调用该方法,它实际上修改了它的参数,无论如何都在那附近?
// suppose l is instantiated at this point
log.info(l.count());// prints 0
f(l);
log.info(l.count());// prints 1
无论如何都要声明f以保持l在java中不变?
我知道我可以在l上执行深度克隆并传递它,但是在l非常大的情况下,此操作很昂贵。
答案 0 :(得分:6)
好吧,不要调用会修改它的方法。如果没有复制,你会期望这样的方法做什么?它要么必须表现不同(例如,在调用add
时没有做任何事情)或抛出异常。你可以通过将它包装在一个不可修改的列表中来使它抛出异常...但是如果方法的目的是更改集合,你可能不想要抛出异常..
我知道这听起来像是一个陈腐的答案,但我希望它真正触及你需要思考的内容:如果你有一个不应修改的集合,你想要调用一个尝试修改集合的方法,您应该考虑为什么要首先调用该方法。
我确实理解困难的部分是知道哪些方法将修改集合 - 这就是你可以防御性地创建一个不可修改的包装器,或者确保正确记录所有相关方法。< / p>
答案 1 :(得分:2)
log.info(l.count());
f(Collections.unmodifiableList(list));
log.info(l.count());
如果您尝试修改方法中的列表,您将获得UnsupportedOperationException
。
答案 2 :(得分:2)
如果您不想更改原始列表,请不要更改它。
您可以改为复制。
List f(List l){
l = new ArrayList(l); // the original will not be changed now.
l.add(new Object());
return l;
}
答案 3 :(得分:0)
这就是为什么你应该总是从编写规范开始,并注意提供给你的有关API的规范。这将列在方法的规范中。
如果您想强制不对列表进行任何更改,请忽略规范说它会尝试这样做(假设您没有自己编写方法),请将其作为Collections.unmodifiableList(l);
进行包装。并像其他人建议的那样处理抛出的异常。
如果您在另一边 - 编写方法并且您希望确保不更改列表的内容 - 只需不编写任何修改语句并确保在规范中提到。
答案 4 :(得分:0)
如果您知道原始列表本身没有变化,并且想要一个包含原始列表的所有内容的新List以及另外一个新元素,您可以考虑使用两者的包装器,如下所示:
/**
* an immutable wrapper around a list with an added element at the end.
*/
class ImmutableListWrapper<E> extends AbstractList<E> {
private final List<E> delegate;
private final E lastElement;
public ImmutableListWrapper(List<E> start, E last) {
this.delegate = start;
this.lastElement = last;
}
public E get(int index) {
if(index == delegate.size()) {
return lastElement;
}
return delegate.get(index);
}
public int size() {
return delegate.size() + 1;
}
}
public List<Object> f(List<Object> l) {
return new ImmutableListWrapper<Object>(l, new Object());
}
如果原始列表发生更改,新列表也会更改,这是设计使然。
如果您的原始列表是非随机访问列表,那么您最好从AbstractSequentialList继承并实现委托ListIterator而不是get-method。