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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容

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