前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java Getter/Setter “防坑指南”

Java Getter/Setter “防坑指南”

作者头像
乔戈里
发布2019-10-12 15:59:37
1.3K0
发布2019-10-12 15:59:37
举报
文章被收录于专栏:Java那些事

Getter/Setter 在 Java 中被广泛使用,看似简单,但并非每个 Java 开发人员都能很好理解并正确实现 Getter/Setter 方法。因此,在这篇文章里,我想深入讨论 Java 中的 getter 和 setter 方法,请跟随我一起来看看吧。

一个简单的例子

下面的代码展示了 Getter/Setter 方法的基本使用。

代码语言:javascript
复制
public class GetterAndSetterExample {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

可以看到,我们在类 GetterAndSetterExample 中声明了一个私有变量 name。因为 name 是私有的,所以我们无法在类外部直接访问该变量。以下的代码将无法编译通过:

代码语言:javascript
复制
GetterAndSetterExample object = new GetterAndSetterExample();
object.name = "yanglbme"; // 编译出错

String name = object.name; // 编译出错

正确的“姿势”是调用 getter getName() 和 setter setName() 来读取或更新变量:

代码语言:javascript
复制
GetterAndSetterExample object = new GetterAndSetterExample();
object.setName("yanglbme");
String name = object.getName();

为什么我们需要 Getter/Setter ?

通过使用 Getter/Setter 方法,变量的访问(get)和更新(set)将变得可控。考虑以下 Setter 方法的代码:

代码语言:javascript
复制
public void setName(String name) {
    if (name == null || "".equals(name)) {
        throw new IllegalArgumentException();
    }
    this.name = name;
}

这样可以确保 name 设置的值始终不为空。倘若可以直接通过 . 操作符设置 name 的值,那么调用方可以随意为 name 设置任何值,这就违反了 name 变量的非空约束。

也就是说,Getter/Setter 方法可以确保变量的值免受外界(调用方代码)的意外更改。当变量被 private 修饰符隐藏并且只能通过 getter 和 setter 访问时,它就被“封装”起来了。封装是面向对象编程(OOP)的基本特性之一,实现 Getter/Setter 是在程序代码中强制执行封装的方法之一。

Getter/Setter 方法的命名约束

Setter 和 Getter 的命名需要遵循 Java bean 的命名约定,如 setXxx()getXxx(),其中 Xxx 是变量的名称:

代码语言:javascript
复制
public void setName(String name) { }

public String getName() { } // getter

而如果变量是 boolean 类型,那么 getter 方法可以命名为 isXxx() 或者 getXxx(),但首选使用前者进行命名:

代码语言:javascript
复制
private boolean single;

public boolean isSingle() { } // getter

Getter/Setter 的常见错误实现

错误一:实现了 Getter/Setter 方法,但变量不做严格的范围限制

如以下代码片段所示:

代码语言:javascript
复制
public String name; // 使用public修饰

public void setName(String name) {
    this.name = name;
}

public String getName() {
    return name;
}

变量 name 被声明为 public,因此我们可以直接在类外部使用点 . 操作符对其进行访问,从而使 setter 和 getter 无效。这种情况的解决方法很简单,直接使用更加“严格”的访问修饰符,例如 protected 和 private。

错误二:在 Setter 中直接赋值一个对象引用

考虑以下 Setter 方法:

代码语言:javascript
复制
public class Student {
    private int[] scores;

    public void setScores(int[] scores) {
        this.scores = scores;
    }

    public void showScores() {
        for (int score : scores) {
            System.out.print(score + " ");
        }
        System.out.println();
    }
}

是不是感觉没毛病?我们再来看以下代码:

代码语言:javascript
复制
int[] myScores = {100, 97, 99, 88, 69};
Student yang = new Student();

yang.setScores(myScores);
yang.showScores();

可以看到,整数数组 myScores 先进行了初始化并传递给 setScores() 方法,随后对 scores 进行了简单打印,产生了以下输出:

代码语言:javascript
复制
100 97 99 88 69 

现在,我们修改 myScores 数组中第 2 个元素的值,并再次打印 scores:

代码语言:javascript
复制
myScores[1] = 101;
yang.showScores();

程序将会输出如下:

代码语言:javascript
复制
100 101 99 88 69 

而这样就意味着我们可以在 Setter 方法之外修改数据,这显然已经破坏了 Setter 封装的目的。为什么会这样呢?我们再来看一下 setScores() 方法:

代码语言:javascript
复制
public void setScores(int[] scores) {
    this.scores = scores;
}

成员变量 scores 直接引用了一个参数变量 scores,这意味着两个变量都指向了内存中同一个对象,即 myScores 数组对象。因此,对 myScores 变量所做的变更将直接导致成员变量 scores 被同步修改。这种情况下,解决办法是:将方法参数 scores 拷贝一份赋值给成员变量 scores:

代码语言:javascript
复制
public void setScores(int[] scores) {
    this.scores = new int[scores.length];
    System.arraycopy(scores, 0, this.scores, 0, scores.length);
}

经验总结:如果我们是将对象作为参数传递给 setter 方法,不要直接使用简单引用赋值的方式。相反,我们应该找到一些方法,将对象的值赋值到内部成员变量中,比如使用 System.arraycopy() 方法将元素中一个数组复制到另一个数组中。

错误三:直接返回对象的引用

考虑以下 Getter 方法的实现:

代码语言:javascript
复制
private int[] scores;

public int[] getScores() {
    return scores;
}

在程序中,我们调用 getScores() 方法,并修改其中某个元素的值:

代码语言:javascript
复制
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();

将会产生以下输出:

代码语言:javascript
复制
100 97 99 88 69 
100 97 99 520 69 

正如你所看到的,数组第 4 个元素已经被修改为 520。这是由于 Getter 方法直接返回了内部成员变量 scores 的引用,因此,外部代码可以获取到该引用并对元素进行修改。

这种情况的解决方法是:应该返回对象的副本,而不是直接返回引用:

代码语言:javascript
复制
public int[] getScores() {
    int[] copy = new int[this.scores.length];
    System.arraycopy(this.scores, 0, copy, 0, copy.length);
    return copy; // 返回副本
}

经验总结:不要在 Getter 方法中返回原始对象的引用。相反,它应该返回原始对象的副本。

实现基本类型的 Getter/Setter 方法

在 Java 中,基本类型有 int, float, double, boolean, char...,你可以直接自由设置或者返回值,因为 Java 是将一个基本变量的值复制到另一个变量中,而不是复制对象的引用,因此,错误二、三都能够轻松避免。

代码语言:javascript
复制
private float amount;
public void setAmount(float amount) {
    this.amount = amount;
}
public float getAmount() {
    return amount;
}

也就是说,对于基本数据类型,用不着一些正确实现 Getter/Setter 的特殊技巧。

实现对象类型的 Getter/Setter 方法

String 对象的 Getter/Setter 方法

String 是一种对象类型,但是它是不可变的,这意味着我们一旦创建了 String 对象,就无法更改其内容。换句话说,对 String 对象的每次更改都会导致新创建一个 String 对象。因此,像原始类型一样,我们可以安全地为 String 变量实现 Getter/Setter,就像这样:

代码语言:javascript
复制
private String address;
public void setAddress(String address) {
    this.address = address;
}
public String getAddress() {
    return address;
}

Date 对象的 Getter/Setter 方法

java.util.Date 类实现了 Object 类中的 clone() 方法。clone() 方法返回对象的副本,因此我们可以将其用于 getter 和 setter,如以下代码所示:

代码语言:javascript
复制
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 对象的 Getter/Setter 方法

对于 Collection 对象,正如上面错误二、三所描述,我们不能这样简单实现 Getter/Setter 方法。

代码语言:javascript
复制
private List<String> listTitles;
public void setListTitles(List<String> titles) {
    this.listTitles = titles;
}
public List<String> getListTitles() {
    return listTitles;
}

对于字符串集合,一种解决方法是使用一个构造函数,该构造函数接收另一个集合作为参数。比如:

代码语言:javascript
复制
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 类型的集合。考虑以下示例,我们定义了类 CollectionGetterSetterObjectPerson

代码语言:javascript
复制
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 描述如下:

代码语言:javascript
复制
/**
 * 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() 方法,如下所示:

代码语言:javascript
复制
public Object clone() {
    Person aClone = new Person(this.name);
    return aClone;
}

listPeople 的 Setter 方法应该修改为如下:

代码语言:javascript
复制
public void setListPeople(List<Person> list) {
    for (Person aPerson : list) {
        this.listPeople.add((Person) aPerson.clone());
    }
}

而相应地,Getter 方法也应该被修改,如下所示:

代码语言:javascript
复制
public List<Person> getListPeople() {
    List<Person> listReturn = new ArrayList<Person>();
    for (Person aPerson : this.listPeople) {
        listReturn.add((Person) aPerson.clone());
    }
    return listReturn;
}

因此,新的 CollectionGetterSetterObject 类代码应该是这样的:

代码语言:javascript
复制
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 的关键点是:

•对于 String 对象的集合,由于 String 对象是不可变的,因此不需要任何特殊的调整。•对于对象的自定义类型的集合:•实现自定义类型的 clone() 方法。•对于 setter,将克隆的项目从源集合添加到目标集合。•对于 getter,创建一个新的 Collection,并将其返回。将原始集合中的克隆项添加到新集合中。

自定义对象的 Getter/Setter 方法

如果定义对象的自定义类型,则应针对自己的类型实现 clone() 方法。

代码语言:javascript
复制

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 方法应该实现如下:

代码语言:javascript
复制
public void setFriend(Person person) {
    this.friend = (Person) person.clone();
}

而对于 getter 方法:

代码语言:javascript
复制
public Person getFriend() {
    return (Person) this.friend.clone();
}

小结一下,为自定义对象类型实现 getter 和 setter 的规则是:

•为自定义类型实现一个 clone() 方法。•从 getter 返回一个克隆的对象。•在 setter 中分配一个克隆的对象。

总结

Java 的 Getter/Setter 看起来很简单,但是如果实现不当,可能会很危险,它甚至可能是导致你代码行为异常的问题的根源。或者更糟糕的是,别人可以通过隐式操纵 Getter 或者 Setter 的参数并从中获取对象来轻易地“蹂躏”你的程序。

请小心使用,避免踩坑。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-10-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员乔戈里 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一个简单的例子
  • 为什么我们需要 Getter/Setter ?
  • Getter/Setter 方法的命名约束
  • Getter/Setter 的常见错误实现
    • 错误一:实现了 Getter/Setter 方法,但变量不做严格的范围限制
      • 错误二:在 Setter 中直接赋值一个对象引用
        • 错误三:直接返回对象的引用
        • 实现基本类型的 Getter/Setter 方法
        • 实现对象类型的 Getter/Setter 方法
          • String 对象的 Getter/Setter 方法
            • Date 对象的 Getter/Setter 方法
              • Collection 对象的 Getter/Setter 方法
                • 自定义对象的 Getter/Setter 方法
                • 总结
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档