4. 实现Getter/Setter方法常见错误
没有无过错的人,没有无bug的代码。本节将描述实现getter/setter时的常见错误,以及解决方案。
常见错误一
实现了getter/setter,但是变量声明访问类型不够严格。
考虑如下代码片段:
public String firstName;
public void setFirstName(String fname) {
this.firstName = fname;
}
public String getFirstName() {
return this.firstName;
}
变量firstName
被声明为public,所以可以使用.
运算符直接访问,因此getter/setter就没有实际意义了。一个解决方案是使用更加严格的访问修饰词,比如protected
和private
:
private String firstName;
在《Effective Java》一书中,Joshua Bloch在第14条中指出这个问题:
"In public classes, use accessor methods, not public fields."
常见错误二
在setter中直接分配对象引用。
考虑如下代码片段:
private int[] scores;
public void setScores(int[] scr) {
this.scores = scr;
}
如下示例代码说明了问题:
int[] myScores = {5, 5, 4, 3, 2, 4};
setScores(myScores);
displayScores();
myScores[1] = 1;
displayScores();
一个int类型的数组myScores
,使用6个元素初始化后执行第2行代码setScores()
方法。displayScores()
方法将数据元素简单打印出来:
public void displayScores() {
for (int i = 0; i < this.scores.length; i++) {
System.out.print(this.scores[i] + " ");
}
System.out.println();
}
第3行代码将打印如下结果:
5 5 4 3 2 4
数组myScores
所有元素被打印出来。现在,通过第4行代码,我们修改数组myScores
中的第二个元素:
myScores[1] = 1;
如果我们通过第5行代码再次调用displayScores()
方法会发生什么呢?好,让我们看下输出结果:
5 1 4 3 2 4
可以看到,通过第四行的赋值语句,第二个元素的值从5变成了1。这就意味着在setter方法以外的代码也可以修改数据,这违反了setter方法封装变量的目的。为什么会出现这种情况呢?让我们再看看setScores()
方法:
public void setScores(int[] scr) {
this.scores = scr;
}
变量scores
被直接赋值为方法入参变量scr
。这意味着这两个变量都指向内存中的同一个对象——myScores
数组。所以无论是对scores
还是myScores
变量的修改实际上都是在同一个对象上的。
这种情况的一个解决方案是逐一拷贝scr
数组中的元素到scores
数组中。修改后的setter方法如下:
public void setScores(int[] scr) {
this.scores = new int[scr.length];
System.arraycopy(scr, 0, this.scores, 0, scr.length);
}
这有什么不同呢?现在,变量scores
不再指向scr
变量所指向的对象了。而是scores
数组被初始化为与scr
数组相同大小的一个新数组。然后,我们使用System.arraycopy()
方法,将scr
数组中的元素都拷贝到scores
数组中。
再次运行示例代码,得到如下输出结果:
5 5 4 3 2 4
5 5 4 3 2 4
现在,两次displayScores()
方法调用产生了相同的结果。这就意味着scores
数组独立于通过setter方法赋值的scr
数组,因此我们调用如下语句:
myScores[1] = 1;
不会影响到scores
数组。
所以,拇指规则是:如果传递一个。对象引用到setter方法中,不要直接把引用拷贝给对象的内部变量。而是应该使用一些方法将传递过来的对象的值拷贝给对象内部变量,就像我们使用System.arraycopy()
方法将一个数组的元素拷贝给另外一个数组一样。
常见错误三
直接在getter方法中返回对象引用。
考虑如下的getter方法:
private int[] scores;
public int[] getScores() {
return this.scores;
}
然后再看下面一段示例代码:
int[] myScores = {5, 5, 4, 3, 2, 4};
setScores(myScores);
displayScores();
int[] copyScores = getScores();
copyScores[1] = 1;
displayScores();
将会产生如下输出结果:
5 5 4 3 2 4
5 1 4 3 2 4
可以看到,通过第5行示例代码,数组scores
的第二个元素被setter方法以外的代码修改了。因为getter方法直接返回了内部变量scores
的引用,外部代码就可以获取该变量引用并修改这个内部变量。
这个问题的一个解决方案是,在getter方法中不要直接返回对象的引用,而是应该返回对象的一个副本。这样外部代码只能获取到对象的副本,而不是内部变量对象本身。
我们将getter方法修改如下:
public int[] getScores() {
int[] copy = new int[this.scores.length];
System.arraycopy(this.scores, 0, copy, 0, copy.length);
return copy;
}
所以,拇指规则是:不要在getter方法中直接返回源对象的引用,而是应该返回其对象的副本。