我最近遇到了如下代码:
public List<Item> getItems() {
if (items == null) {
items = new ArrayList<Item>();
}
return this.items;
}
Run Code Online (Sandbox Code Playgroud)
而且至关重要的是,没有特定的方法.
因此,如果您想添加到arrayList,则必须这样做
foo.getItems().add(...)
Run Code Online (Sandbox Code Playgroud)
而不是
foo.setItems(myArrayList)
Run Code Online (Sandbox Code Playgroud)
我之前没有见过这个成语,我不能说我喜欢它,但是当我使用mapstruct.org(顺便说一句很棒的工具)生成一些映射代码时,mapstruct处理得很好并正确生成使用getter的代码作为一个制定者.
我只是想知道 - 这是一个我不知何故错过的常见习语吗?这对我来说似乎毫无意义,但也许背后有一些智慧,我没有看到?
问题不在于懒惰的初始化(那部分是好的),而是暴露应该是一个实现细节.一旦暴露了存储在字段中的实际可变(!)列表对象,代码的调用者就可以对列表执行任何操作,甚至是您不期望的事情.
例如,他们可以在你想要的时候删除对象add().他们可以从不同的线程修改它,使您的代码以有趣和令人沮丧的方式中断.他们甚至可以将它转换为raw List,并用完全不同类型的对象填充它,使你的代码抛出ClassCastException.
换句话说,它使得无法强制执行类不变量.
请注意,有两件事情共同导致此问题:
如果这两个中的任何一个都不正确,那就没问题了.所以这很好:
public String getFoo() {
return this.foo;
}
Run Code Online (Sandbox Code Playgroud)
因为String是不可改变的.这也很好:
public List<String> getFooList() {
return new ArrayList<>( this.fooList );
}
Run Code Online (Sandbox Code Playgroud)
因为现在你正在返回防御性副本而不是实际对象.(但是,如果列表中的元素是可变的,那么你会再遇到麻烦.)
想象一下这种情况:
public class Foo {
private List<String> list;
public Foo( List<String> list ) {
this.list = list; // Don't do this
}
...
}
Run Code Online (Sandbox Code Playgroud)
这看起来完全无害,你可以在很多地方看到它.然而,这里也有一个隐藏的问题:通过在存储列表之前不复制,你处于完全相同的情况.你不能阻止别人这样做:
List<String> list = new ArrayList<>();
list.add( "nice item" );
Foo foo = new Foo( list );
list.add( "hahahaha" );
list.add( "i've just added more items to your list and you don't know about it." );
list.add( "i'm an evil genius" );
Run Code Online (Sandbox Code Playgroud)
因此,在将可变对象分配给字段和返回字段之前,您应该制作防御性副本.
除了根本不知道为什么这不是一个好主意之外,还有两大类的借口.
您的代码中有两件事: