Getter/Setter 在 Java 中被普遍使用。看似简单,但并不是每一个 Java 开发人员都能很好理解并正确实现 Getter/Setter 方法。所以,在这篇文章里,我想深刻讨论 Java 中的 getter 和 setter 方法,请跟随我一块儿来看看吧。git
下面的代码展现了 Getter/Setter 方法的基本使用。github
public class GetterAndSetterExample {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
复制代码
能够看到,咱们在类 GetterAndSetterExample
中声明了一个私有变量 name。由于 name 是私有的,因此咱们没法在类外部直接访问该变量。如下的代码将没法编译经过:编程
GetterAndSetterExample object = new GetterAndSetterExample();
object.name = "yanglbme"; // 编译出错
String name = object.name; // 编译出错
复制代码
正确的“姿式”是调用 getter getName()
和 setter setName()
来读取或更新变量:数组
GetterAndSetterExample object = new GetterAndSetterExample();
object.setName("yanglbme");
String name = object.getName();
复制代码
经过使用 Getter/Setter 方法,变量的访问(get)和更新(set)将变得可控。考虑如下 Setter 方法的代码:安全
public void setName(String name) {
if (name == null || "".equals(name)) {
throw new IllegalArgumentException();
}
this.name = name;
}
复制代码
这样能够确保 name 设置的值始终不为空。假若能够直接经过 .
操做符设置 name 的值,那么调用方能够随意为 name 设置任何值,这就违反了 name 变量的非空约束。bash
也就是说,Getter/Setter 方法能够确保变量的值免受外界(调用方代码)的意外更改。当变量被 private
修饰符隐藏而且只能经过 getter 和 setter 访问时,它就被“封装”起来了。封装是面向对象编程(OOP)的基本特性之一,实现 Getter/Setter 是在程序代码中强制执行封装的方法之一。函数
Setter 和 Getter 的命名须要遵循 Java bean 的命名约定,如 setXxx()
和 getXxx()
,其中 Xxx 是变量的名称:this
public void setName(String name) { }
public String getName() { } // getter
复制代码
而若是变量是 boolean 类型,那么 getter 方法能够命名为 isXxx() 或者 getXxx(),但首选使用前者进行命名:spa
private boolean single;
public boolean isSingle() { } // getter
复制代码
如如下代码片断所示:
public String name; // 使用public修饰
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
复制代码
变量 name 被声明为 public,所以咱们能够直接在类外部使用点 .
操做符对其进行访问,从而使 setter 和 getter 无效。这种状况的解决方法很简单,直接使用更加“严格”的访问修饰符,例如 protected 和 private。
考虑如下 Setter 方法:
public class Student {
private int[] scores;
// setter
public void setScores(int[] scores) {
this.scores = scores;
}
public void showScores() {
for (int score : scores) {
System.out.print(score + " ");
}
System.out.println();
}
}
复制代码
是否是感受没毛病?咱们再来看如下代码:
int[] myScores = {100, 97, 99, 88, 69};
Student yang = new Student();
yang.setScores(myScores);
yang.showScores();
复制代码
能够看到,整数数组 myScores 先进行了初始化并传递给 setScores()
方法,随后对 scores 进行了简单打印,产生了如下输出:
100 97 99 88 69
复制代码
如今,咱们修改 myScores 数组中第 2 个元素的值,并再次打印 scores:
myScores[1] = 101;
yang.showScores();
复制代码
程序将会输出以下:
100 101 99 88 69
复制代码
而这样就意味着咱们能够在 Setter 方法以外修改数据,这显然已经破坏了 Setter 封装的目的。为何会这样呢?咱们再来看一下 setScores()
方法:
public void setScores(int[] scores) {
this.scores = scores;
}
复制代码
成员变量 scores 直接引用了一个参数变量 scores
,这意味着两个变量都指向了内存中同一个对象,即 myScores
数组对象。所以,对 myScores 变量所作的变动将直接致使成员变量 scores 被同步修改。这种状况下,解决办法是:将方法参数 scores 拷贝一份赋值给成员变量 scores:
public void setScores(int[] scores) {
this.scores = new int[scores.length];
System.arraycopy(scores, 0, this.scores, 0, scores.length);
}
复制代码
经验总结:若是咱们是将对象做为参数传递给 setter 方法,不要直接使用简单引用赋值的方式。相反,咱们应该找到一些方法,将对象的值赋值到内部成员变量中,好比使用
System.arraycopy()
方法将元素从一个数组复制到另外一个数组中。
考虑如下 Getter 方法的实现:
private int[] scores;
public int[] getScores() {
return scores;
}
复制代码
在程序中,咱们调用 getScores() 方法,并修改其中某个元素的值:
int[] myScores = {100, 97, 99, 88, 69};
Student yang = new Student();
yang.setScores(myScores);
yang.showScores();
int[] copyScores = yang.getScores();
copyScores[3] = 520;
yang.showScores();
复制代码
将会产生如下输出:
100 97 99 88 69
100 97 99 520 69
复制代码
正如你所看到的,数组第 4 个元素已经被修改成 520。这是因为 Getter 方法直接返回了内部成员变量 scores 的引用,所以,外部代码能够获取到该引用并对元素进行修改。
这种状况的解决方法是:应该返回对象的副本,而不是直接返回引用:
public int[] getScores() {
int[] copy = new int[this.scores.length];
System.arraycopy(this.scores, 0, copy, 0, copy.length);
return copy; // 返回副本
}
复制代码
经验总结:不要在 Getter 方法中返回原始对象的引用。相反,它应该返回原始对象的副本。
在 Java 中,基本类型有 int, float, double, boolean, char...,你能够直接自由设置或者返回值,由于 Java 是将一个基本变量的值复制到另外一个变量中,而不是复制对象的引用,所以,错误2、三都可以轻松避免。
private float amount;
public void setAmount(float amount) {
this.amount = amount;
}
public float getAmount() {
return amount;
}
复制代码
也就是说,对于基本数据类型,用不着一些正确实现 Getter/Setter 的特殊技巧。
String 是一种对象类型,可是它是不可变的,这意味着咱们一旦建立了 String 对象,就没法更改其内容。换句话说,对 String 对象的每次更改都会致使新建立一个 String 对象。所以,像原始类型同样,咱们能够安全地为 String 变量实现 Getter/Setter,就像这样:
private String address;
public void setAddress(String address) {
this.address = address;
}
public String getAddress() {
return address;
}
复制代码
java.util.Date
类实现了 Object 类中的 clone() 方法。clone() 方法返回对象的副本,所以咱们能够将其用于 getter 和 setter,如如下代码所示:
private Date birthDate;
public void setBirthDate(Date birthDate) {
this.birthDate = (Date) birthDate.clone();
}
public Date getBirthDate() {
return (Date) this.birthDate.clone();
}
复制代码
clone()
方法返回一个 Object 类型的对象,所以咱们必须将其强制转换为 Date 类型。
对于 Collection 对象,正如上面错误2、三所描述,咱们不能这样简单实现 Getter/Setter 方法。
private List<String> listTitles;
public void setListTitles(List<String> titles) {
this.listTitles = titles;
}
public List<String> getListTitles() {
return listTitles;
}
复制代码
对于字符串集合,一种解决方法是使用一个构造函数,该构造函数接收另外一个集合做为参数。好比:
public void setListTitles(List<String> titles) {
// 将titles传递给ArrayList的构造函数
this.listTitles = new ArrayList<String>(titles);
}
public List<String> getListTitles() {
return new ArrayList<String>(this.listTitles);
}
复制代码
注意,上面的构造方法仅仅适用于字符串型的集合。但不适用于 Object 类型的集合。考虑如下示例,咱们定义了类 CollectionGetterSetterObject
和 Person
:
import java.util.*;
public class CollectionGetterSetterObject {
// 元素类型是Person的List集合
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);
}
}
class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return name;
}
}
复制代码
对于 String,每复制一个 String 对象都会为之建立新对象,而其余 Object 类型的对象则不会,它们仅复制引用,所以这就是两个集合不一样但它们包含相同对象的缘由。
查看 Collection API,咱们发现 ArrayList、HashMap、HashSet 等实现了本身的 clone() 方法。这些方法返回浅表副本,这些浅表副本不会将元素从源 Collection 复制到目标。
好比,ArrayList 类的 clone() 方法的 Javadoc 描述以下:
/** * Returns a shallow copy of this <tt>ArrayList</tt> instance. (The * elements themselves are not copied.) * * @return a clone of this <tt>ArrayList</tt> instance */
public Object clone() { }
复制代码
所以,咱们不能使用这些 Collection 类的 clone() 方法。解决方案是为咱们本身定义的对象(上例中的 Person 类)实现 clone() 方法。咱们在 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;
}
复制代码
所以,新的 CollectionGetterSetterObject
类代码应该是这样的:
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;
}
}
复制代码
小结一下,实现 Collection 类型的 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;
}
// 本身实现clone方法
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 的规则是:
Java 的 Getter/Setter 看起来很简单,可是若是实现不当,可能会很危险,它甚至多是致使你代码行为异常的问题的根源。或者更糟糕的是,别人能够经过隐式操纵 Getter 或者 Setter 的参数并从中获取对象来轻易地“蹂躏”你的程序。
请当心使用,避免踩坑。