Java Getter&Setter 系列之三:最佳实践

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方法。

系列文章

  1. Java Getter&Setter 系列之一:基础
  2. Java Getter&Setter 系列之二:常见错误
  3. Java Getter&Setter 系列之三:最佳实践

扩展阅读

  1. Why Should I Write Getters and Setters?
  2. Getter/Setter: The Most Hated Practice in Java
  3. Setters, Method Handles, and Java 11
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 本文由 yanglbme 原创,首发于公众号“Doocs开源社区”,欢迎转载。 Getter/Setter 在 J...
    yanglbme阅读 1,779评论 0 1
  • 这是16年5月份编辑的一份比较杂乱适合自己观看的学习记录文档,今天18年5月份再次想写文章,发现简书还为我保存起的...
    Jenaral阅读 2,859评论 2 9
  • 307、setValue:forKey和setObject:forKey的区别是什么? 答:1, setObjec...
    AlanGe阅读 1,587评论 0 1
  • 每天的坚持 只不过是为了在那个曾经 相约路口的邂逅 每天在路口的回眸 ...
    幻海独云阅读 270评论 0 1
  • 今夜是一个略微有些潮湿的夜。路上的行人大都撑着伞,也有些人不惧这从九天之上零落下来的珠子。大概是被这迷离的夜...
    正义的简爱006阅读 474评论 0 0