因此,我们创建了一个带有私有类成员的简单类,并为它自动生成getter。但是getter实际上返回了对该成员的引用,从而获得了对私有成员的完全访问。可以吗?下面是一个类的代码:
public class User {
private ArrayList<String> strings = new ArrayList(){ {
add("String1");
add("String2");
} };
public User() {
}
public ArrayList<String> getStrings() {
return strings;
}
public void setStrings(ArrayList<String> strings) {
this.strings = strings;
}
}
主方法代码:
public class Main {
public static void main(String[] args){
User user = new User();
System.out.println(user.getStrings());
user.getStrings().add("String3");
System.out.println(user.getStrings());
}
}
和输出:
[String1 String2相等)
[String1, String2, String3]
我把getter改成了这个
public ArrayList<String> getStrings() {
return (ArrayList<String>)strings.clone();
}
但问题仍然存在,如果不是为了安全,getter是用来干什么的?正确的写法是什么?
不行,因为它破坏了封装,因此类不能维护自己的不变量。构造函数也一样。
问题不在于getter/setter,而在于自动生成它们的代码。
长话短说:不要盲目地使用自动生成的访问器,如果它们处理的是可变结构,请创建防御性副本(或不可变等价)。
作为题外话,我不会有一个具有
ArrayList
返回类型的getter,即使它只是一个副本。返回什么样的列表通常与客户端无关,所以我的getter应该是这样的:
public List<String> getStrings() {
return new ArrayList<>(strings);
}
或者使用不可变视图:
public List<String> getStrings() {
return Collections.unmodifiableList(strings);
}
或者使用Guava的ImmutableList
类:
public List<String> getStrings() {
return ImmutableList.copyOf(strings);
}
这三种解决方案之间存在微妙的差异,因此最佳解决方案可能会有所不同。作为一般规则,我更喜欢返回不可变结构,因为这清楚地表明,对结构所做的更改不会被反映出来,即user.getStrings().add( "X" );
将异常失败。
您所展示的代码的另一个微妙问题是双括号初始化。想象一个这样的类:
public class Foo {
private List<String> strings = new ArrayList() {{ add("bar");}};
private Object veryLargeField; //the object stored here consumes a lot of memory
public List<String> getStrings() {
return strings;
}
}
现在想象我们在做这个:
private class Bar {
private List<String> fooStrings;
public Bar() {
this.fooStrings = new Foo().getStrings();
}
}
Bar
将消耗多少内存(或者使用确切的术语:保留)?嗯,事实证明,相当多,因为您使用双括号初始化所做的是创建一个匿名内部类,它将包含对其外部类(Foo
)的引用,因此,虽然返回的列表是可访问的,但Foo
的所有其他字段将不适合垃圾收集。
从我的角度来看,getter通常有两个目的:
- 首先他们应该保护实现细节。 第二,他们应该提供一种容易扩展的方法(例如验证或仪器)
如果你的例子违反了这些原则取决于上下文:
- 如果你的类应该拥有字符串,那么可能每个人都应该与容器对象交互来修改列表,而不是列表本身。要公开一个集合(例如,在需要一个集合的方法中进行处理),你可以使用collections . unmodiableelist()。另一方面,如果类只拥有字符串列表,那么拥有列表就不是一个实现细节。
- 使用getter而不是直接访问字段,可以让您轻松添加数据对话,跟踪仪表和其他东西,而无需更改所有使用字段的地方。