getter和setter在Java中得到了广泛的应用。它看起来很简单,但并不是每个程序员都能正确地理解和实现这种方法。因此,在本文中,我想深入讨论Java中的getter和setter方法-从基础到常见错误和最佳实践。

如果您已经很好地掌握了基础知识,那么直接跳到第4节,在其中我将讨论常见的错误和最佳实践。

1.getter和Setter是什么?

在Java中,getter和setter是用于检索和更新变量值的两种传统方法。

下面的代码是一个带有私有变量的简单类和两个getter/setter方法的示例:

public class SimpleGetterAndSetter {
    private int number;
    public int getNumber() {
        return this.number;
    }
    public void setNumber(int num) {
        this.number = num;
    }
}

该类声明一个私有变量Number。由于Number是私有的,该类外部的代码不能直接访问变量,如下所示:

SimpleGetterAndSetter obj = new SimpleGetterAndSetter();
obj.number = 10;    // compile error, since number is private
int num = obj.number; // same as above

相反,外部代码必须调用getter,getNumber()和策划人,setNumber(),以便读取或更新变量,例如:

SimpleGetterAndSetter obj = new SimpleGetterAndSetter();
obj.setNumber(10);  // OK
int num = obj.getNumber();  // fine

因此,setter是一个更新变量值的方法。getter是读取变量值的方法。getter和setter也称为存取者和突变体在JAVA。

2.为什么我们需要getter和Setter?

通过使用getter和setter,程序员可以控制如何以适当的方式访问和更新它们的重要变量,例如在指定的范围内更改变量的值。考虑一下setter方法的以下代码:

public void setNumber(int num) {
    if (num < 10 || num > 100) {
        throw new IllegalArgumentException();
    }
    this.number = num;
}

这确保了数字的值始终设置在10到100之间。假设变量号可以直接更新,调用方可以为其设置任意值:

obj.number = 3;

这违反了该变量的值范围为10到100的约束。当然,我们不希望这种情况发生。因此,将变量号隐藏为私有,然后使用setter来挽救。

另一方面,getter方法是外部世界读取变量值的唯一方法:

public int getNumber() {
    return this.number;
}

下图说明了情况:

getter java setter 简写 java中的getter和setter方法_System


到目前为止,setter和getter方法保护变量的值不受外部世界(调用方代码)意外变化的影响。

当变量被私有修饰符并且只能通过getter和setter访问,它是封装. 封装是面向对象编程(OOP)的基本原则之一,因此实现getter和setter是在程序代码中强制封装的方法之一。

一些框架,如冬眠, 春天,和支柱可以通过getter和setter检查信息或注入它们的实用程序代码。因此,在将代码与此类框架集成时,提供getter和setter是必要的。

3.getter和Setter命名公约

setter和getter的命名方案应遵循Javabean命名约定如getXxx()和setXxx(),在哪里Xxx变量的名称。例如,具有以下变量名:

private String name;

适当的设置者和getter将是:

public void setName(String name) { }
public String getName() { }

如果变量是布尔型的,那么getter的名称可以是isXXX()或getXXX(),但前者的命名是首选的。例如:

private boolean single;
public String isSingle() { }

4.在实现getter和Setter时常见的错误

人们经常犯错误,开发人员也不例外。本节描述了在Java中实现setter和getter时最常见的错误以及解决方法。

错误1:您有setter和getter,但是变量在一个限制较少的范围内声明。

考虑以下代码片段:

public String firstName;
public void setFirstName(String fname) {
    this.firstName = fname;
}
public String getFirstName() {
    return this.firstName;
}

变量firstName被宣布为公众,因此可以使用点(.)访问它。直接操作,使设置器和吸气器无用。解决此情况的方法是使用更多的受限访问修饰符,如受保护和私:

private String firstName;

错误2:在setter中直接分配对象引用

考虑到以下设置者方法:

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

以下代码演示了此问题:

int[] myScores = {5, 5, 4, 3, 2, 4};
setScores(myScores);
displayScores();   
myScores[1] = 1;
displayScores();

一个整数数组,myScores,则使用6个值(第1行)初始化数组,并将数组传递给setScores()方法(第2行)。方法displayScores()只需打印数组中的所有分数:

public void displayScores() {
    for (int i = 0; i < this.scores.length; i++) {
        System.out.print(this.scores[i] + " ");
    }
    System.out.println();
}

第3行将产生以下输出:

5 5 4 3 2 4

这些都是myScores阵列。现在,在第4行中,我们可以修改2的值。Nd元素中的myScores数组如下:

myScores[1] = 1;

如果我们调用这个方法会发生什么?displayScores() 又在5号线?那么,它将产生以下输出:

5 1 4 3 2 4

你知道2的价值Nd元素从5更改为1,这是第4行中赋值的结果。为什么这很重要?这意味着数据可以在setter方法的作用域之外进行修改,这破坏了setter的封装目的。为什么会发生这种事?让我们看看setScores()方法再次:

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

成员变量分数分配给方法的参数变量scr直接。这意味着这两个变量都指向内存中的同一个对象-myScores数组对象。因此,对scores或myScores变量实际上是在同一个对象上创建的。

此情况的解决方法是从scr数组到scores阵列,一个接一个。修改后的setter版本如下所示:

public void setScores(int[] scr) {
    this.scores = new int[scr.length];
    System.arraycopy(scr, 0, this.scores, 0, scr.length);
}

有什么关系呢?成员变量scores不再引用scr变量。相反,数组scores初始化为大小等于数组大小的新数组。scr…然后,我们从数组中复制所有元素。scr到数组scores,使用System.arraycopy()方法。

再次运行以下示例,它将为我们提供以下输出:

5 5 4 3 2 4
5 5 4 3 2 4

现在,两个调用displayScores()生产同样的输出。这意味着数组scores是独立的,并且与数组不同。scr传递给策划人,这样我们就有了任务:

myScores[1] = 1;

这不影响数组。scores.

因此,经验法则是:如果将对象引用传递给setter方法,则不要将该引用直接复制到内部变量中。相反,您应该找到一些方法将传递的对象的值复制到内部对象中,就像我们使用System.arraycopy()方法。

错误3:在getter中直接返回对象引用

考虑以下getter方法:

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

然后查看以下代码片段:

int[] myScores = {5, 5, 4, 3, 2, 4};
setScores(myScores);
displayScores();
int[] copyScores = getScores();
copyScores[1] = 1;
displayScores();

它将产生以下产出:

5 5 4 3 2 4
5 1 4 3 2 4

正如你注意到的,2Nd数组的元素scores由于getter方法直接返回内部变量的引用,外部代码可以获得此引用并对内部对象进行更改。

解决这个问题的方法是,我们应该返回对象的副本,而不是直接返回getter中的引用。这是为了使外部代码只能获得副本,而不是内部对象。因此,我们将上述getter修改如下:

public int[] getScores() {
    int[] copy = new int[this.scores.length];
    System.arraycopy(this.scores, 0, copy, 0, copy.length);
    return copy;
}

因此,经验法则是:不要在getter方法中返回原始对象的引用。相反,它应该返回原始对象的副本。

5.为基本类型实现getter和Setter

与原语类型(int, float, double, boolean, char(…),您可以在setter/getter中直接分配/返回值,因为Java将一个原语的值复制到另一个原语,而不是复制对象引用。因此,错误#2和#3是很容易避免的。

例如,以下代码是安全的,因为setter和getter包含在float:

private float amount;
public void setAmount(float amount) {
    this.amount = amount;
}
public float getAmount() {
    return this.amount;
}

因此,对于基本类型,没有正确实现getter和setter的特殊技巧。

6.为公共对象类型实现getter和Setter

字符串对象的getter和Setters:
String是一个对象类型,但它是不可变的,这意味着一旦创建了一个字符串对象,它的字符串文本就不能被更改。换句话说,对该字符串对象的每次更改都将导致新创建的String对象。因此,就像基本类型一样,您可以安全地为字符串变量实现getter和setter,如下所示:

private String address;
public void setAddress(String addr) {
    this.address = addr;
}
public String getAddress() {
    return this.address;
}

日期对象的getter和Setter:
这个java.util.Date类实现clone()方法的Object班级,等级。方法clone()返回对象的副本,因此我们可以将其用于getter和setter,如下面的示例所示:

private Date birthDate;
public void setBirthDate(Date date) {
    this.birthDate = (Date) date.clone();
}
public Date getBirthDate() {
    return (Date) this.birthDate.clone();
}

7.为集合类型实现getter和Setter

正如错误#2和#3所描述的那样,设置setter和getter方法是不好的:

rivate 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.println()语句应该产生相同的结果。但是,当运行上述程序时,它会产生以下输出:

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]

注*上面的构造函数方法仅用于弦,但它不适用于集合对象…的集合,请考虑下面的示例。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]

因为与字符串不同,每当复制字符串对象时,都会为字符串创建新对象,其他Object类型不是。只有引用被复制,所以这就是为什么两个集合是不同的,但是它们包含相同的对象。换句话说,这是因为我们没有提供任何复制对象的方法。

查看Collection API;我们发现ArrayList, HashMap, HashSet等实施自己的clone()方法。这些方法返回浅拷贝,这些副本不将元素从源集合复制到目标。根据Javadocclone()方法ArrayList班级:
因此,我们不能使用clone()这些集合类的方法。解决方案是实现clone()方法为我们自己定义的对象-Person在上面的示例中初始化。我们实现clone()方法中的Person类,如下所示:

public Object clone() {
    Person aClone = new Person(this.name);
    return aClone;
}

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;
    }
}

因此,为自定义对象类型实现getter和setter的规则如下:

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

结语

Java getter和setter看起来很简单,但是如果简单地实现它可能会很危险。它甚至可能是导致代码行为不当的问题的根源。或者更糟糕的是,人们可以很容易地利用您的程序,通过隐秘地将参数操作到getter和setter并从其返回对象。因此,要小心,并考虑实现上述最佳实践。