5. 实现原始数据类型变量的Getter/Setter
对于原始数据类型(int
, float
, double
, boolean
, char
…),我们可以轻松的直接在setter/getter方法中赋值/返回变量值,因为Java对原始类型变量是拷贝变量值而不是变量引用。所以,上述的常见错误#2和错误#3可以轻松避免。
例如,如下float
原始类型变量的getter/setter方法实现是安全的:
private float amount;
public void setAmount(float amount) {
this.amount = amount;
}
public float getAmount() {
return this.amount;
}
所以,对于原始类型,实现getter/setter方法没有什么特殊技巧。
6. 实现普通对象类型变量的Getter/Setter
String类型变量的Getter/Setter
String是一个对象类型,但是他是不可改变的,也就是说一旦一个String类型的对象被创建了,那么这个对象的字符就不会再被改变。换句话说,在String类型对象上的每个变化都会导致生成一个新的String对象。所以,就像原始类型一样,可以安全地实现String类型变量的getter/setter,如下:
private String address;
public void setAddress(String addr) {
this.address = addr;
}
public String getAddress() {
return this.address;
}
Date类型变量的Getter/Setter
java.util.Date
类从Object
类中继承实现了clone()
方法,clone()
方法返回对象的副本,所以我们用于实现getter/setter方法,如下:
private Date birthDate;
public void setBirthDate(Date date) {
this.birthDate = (Date) date.clone();
}
public Date getBirthDate() {
return (Date) this.birthDate.clone();
}
clone()
方法返回的是Object
类型,所以我们必须要做Date
类型转换。可以在 Joshua Bloch的《Effective Java》一书中第39条可以学习更多相关知识:
"Make defensive copies when needed."
7. 实现集合类型变量的Getter/Setter
如上常见错误#2和错误#3所述,我们使用如下的Getter/Setter实现方式:
private List<String> listTitles;
public void setListTitles(List<String> titles) {
this.listTitles = titles;
}
public List<String> getListTitles() {
return this.listTitles;
}
考虑如下程序:
import java.util.*;
public class CollectionGetterSetter {
private List<String> listTitles;
public void setListTitles(List<String> titles) {
this.listTitles = titles;
}
public List<String> getListTitles() {
return this.listTitles;
}
public static void main(String[] args) {
CollectionGetterSetter app = new CollectionGetterSetter();
List<String> titles1 = new ArrayList();
titles1.add("Name");
titles1.add("Address");
titles1.add("Email");
titles1.add("Job");
app.setListTitles(titles1);
System.out.println("Titles 1: " + titles1);
titles1.set(2, "Habilitation");
List<String> titles2 = app.getListTitles();
System.out.println("Titles 2: " + titles2);
titles2.set(0, "Full name");
List<String> titles3 = app.getListTitles();
System.out.println("Titles 3: " + titles3);
}
}
根据实现getter/setter方法的规则,三条System.out.pringln()
语句应该产生相同的结果。然而,当我们运行上面程序,讲得到如下输出结果:
Titles 1: [Name, Address, Email, Job]
Titles 2: [Name, Address, Habilitation, Job]
Titles 3: [Full name, Address, Habilitation, Job]
对于元素为String类型的集合,一个解决方案是使用另外一个集合作为入参的构造器。例如,我们以如下方式实现getter/setter:
public void setListTitles(List<String> titles) {
this.listTitles = new ArrayList<String>(titles);
}
public List<String> getListTitles() {
return new ArrayList<String>(this.listTitles);
}
重新编译并运行CollectionGetterSetter
程序,将会产生如下的输出结果:
Titles 1: [Name, Address, Email, Job]
Titles 2: [Name, Address, Email, Job]
Titles 3: [Name, Address, Email, Job]
注意:以上使用构造器的方法只对元素类型为String的集合有效,而对元素为对象的集合无效。考虑如下元素类型为Person
的集合:
import java.util.*;
public class CollectionGetterSetterObject {
private List<Person> listPeople;
public void setListPeople(List<Person> list) {
this.listPeople = new ArrayList<Person>(list);
}
public List<Person> getListPeople() {
return new ArrayList<Person>(this.listPeople);
}
public static void main(String[] args) {
CollectionGetterSetterObject app = new CollectionGetterSetterObject();
List<Person> list1 = new ArrayList<Person>();
list1.add(new Person("Peter"));
list1.add(new Person("Alice"));
list1.add(new Person("Mary"));
app.setListPeople(list1);
System.out.println("List 1: " + list1);
list1.get(2).setName("Maryland");
List<Person> list2 = app.getListPeople();
System.out.println("List 2: " + list2);
list1.get(0).setName("Peter Crouch");
List<Person> list3 = app.getListPeople();
System.out.println("List 3: " + list3);
}
}
class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return this.name;
}
}
运行程序将会产生如下输出结果:
List 1: [Peter, Alice, Mary]
List 2: [Peter, Alice, Maryland]
List 3: [Peter Crouch, Alice, Maryland]
不像String类型,只要拷贝String对象都会生成一个新的String对象,普通类型对象则不会。普通对象只会拷贝引用,所以即使是两个不同的集合,他们所包含的元素是相同的。换句话说,这是因为我们没有提供任何方法来拷贝对象的值。
通过集合类型的API,我们可以发现ArrayList
, HashMap
, HashSet
等等都实现了他们自己的clone()
方法。这些方法只是浅拷贝,并不会将源集合中的对象拷贝到目的集合中。根据Javadoc官方文档中对ArrayList
类型的clone()
方法描述:
public Object clone()
Returns a shallow copy of this ArrayList instance.
(The elements themselves are not copied.)
因此,我们无法直接使用这些集合类型的clone()
方法。解决办法是对我们自己定义的类型实现clone()
方法——比如上述例子中的Person
类型。如下是我们实现了Person
类型的clone()
方法:
public Object clone() {
Person aClone = new Person(this.name);
return aClone;
}
listPeople
变量的setter方法修改如下:
public void setListPeople(List<Person> list) {
for (Person aPerson : list) {
this.listPeople.add((Person) aPerson.clone());
}
}
相应地getter方法修改如下:
public List<Person> getListPeople() {
List<Person> listReturn = new ArrayList<Person>();
for (Person aPerson : this.listPeople) {
listReturn.add((Person) aPerson.clone());
}
return listReturn;
}
修改后的CollectionGetterSetterObejct
类如下:
import java.util.*;
public class CollectionGetterSetterObject {
private List<Person> listPeople = new ArrayList<Person>();
public void setListPeople(List<Person> list) {
for (Person aPerson : list) {
this.listPeople.add((Person) aPerson.clone());
}
}
public List<Person> getListPeople() {
List<Person> listReturn = new ArrayList<Person>();
for (Person aPerson : this.listPeople) {
listReturn.add((Person) aPerson.clone());
}
return listReturn;
}
public static void main(String[] args) {
CollectionGetterSetterObject app = new CollectionGetterSetterObject();
List<Person> list1 = new ArrayList<Person>();
list1.add(new Person("Peter"));
list1.add(new Person("Alice"));
list1.add(new Person("Mary"));
app.setListPeople(list1);
System.out.println("List 1: " + list1);
list1.get(2).setName("Maryland");
List<Person> list2 = app.getListPeople();
System.out.println("List 2: " + list2);
list1.get(0).setName("Peter Crouch");
List<Person> list3 = app.getListPeople();
System.out.println("List 3: " + list3);
}
}
编译并运行修改后新的CollectionGetterSetterObejct
类,将会产生如下的输出结果:
List 1: [Peter, Alice, Mary]
List 2: [Peter, Alice, Mary]
List 3: [Peter, Alice, Mary]
因此,实现Collection类型的getter/setter方法的关键点就是:
- 对于集合元素类型为String的,没有特别需要注意的地方,因为String对象是不可变的。
- 对于集合元素类型为自定义类型的:
- 对自定义的类型实现其
clone()
方法。 - 对于setter方法,将源集合中的每个元素拷贝到目标集合中。
- 对于getter方法,创建一个新集合作为返回值,然后将源集合中的每个元素拷贝到该新建集合中。
- 对自定义的类型实现其
8. 实现自定义类型变量的Getter/Setter
如果是自定义类型的对象,则需要实现该类型的clone()
方法,例如:
class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return this.name;
}
public Object clone() {
Person aClone = new Person(this.name);
return aClone;
}
}
如上代码所示,Person
类实现了clone()
方法,返回其对象本身的一个克隆副本。
然后,需要将setter方法实现如下:
public void setFriend(Person person) {
this.friend = (Person) person.clone();
}
getter方法实现如下:
public Person getFriend() {
return (Person) this.friend.clone();
}
所以,实现自定义类型变量的getter/setter方法的规则如下:
- 实现自定义类型的
clone()
方法。 - 在getter方法中返回对象的克隆副本。
- 在setter方法中赋值对象的克隆副本。
总结
Java中的getter/setter方法看似简单,但是如果实现不够仔细就可能会出错,甚至会让我们的代码产生奇怪的行为。更甚者,别人可能会滥用我们的代码,通过getter/setter而操作变量。所以,基于本文所述的最佳实践,细心实现getter/setter方法。