How to get and set
Getters and setters are common in Java, they represent the access points to class members in your POJOs (Plain Old Java Objects) and are used to encapsulate the data of your objects. The thought is good, since it hides the internal data model and the members are only exposed through the public setter and getter methods. However, there are however a few gotchas and in this post, I will show what they are.
The normal way #
What you see on almost every how-to, every tutorial and every example out there is what you see below.
class Employee {
private String name;
private int employeeNumber;
private List<String> departments = new ArrayList<>();
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public int getEmployeeNumber() {
return this.employeeNumber;
}
public void setEmployeeNumber(int number) {
this.employeeNumber = number;
}
public List<String> getDepartments() {
return this.departments;
}
public void setDepartments(List<String> departments) {
this.departments = departments;
}
}
Why this is bad #
Remember when you studied Java? Most likely not, but at some point your teacher most likely told you that primitive data types are passed by value, while reference data types are passed by reference.
Consider the code below, what would be the output of running it with our current implementation of Employee?
public static void main (String[] args) {
Employee e = new Employee();
e.setName("John Doe");
e.setEmployeeNumber(1);
e.setDepartments(Arrays.asList("Accounting", "Human resources"));
System.out.println(e.getName());
System.out.println(e.getEmployeeNumber());
System.out.println(e.getDepartments().toString());
List<String> departments = e.getDepartments();
departments.set(0, "IT");
System.out.println(e.getName());
System.out.println(e.getEmployeeNumber());
System.out.println(e.getDepartments().toString());
}
The actual output would be:
John Doe
1
[Accounting, Human resources]
John Doe
1
[IT, Human resources]
WTF #
Reference, remember? In this case, the object in question is nothing more than a reference to a memory address. This means, that when you update you local object, you will also update the object in your POJO.
How to solve it #
public List<String> getDepartments() {
return Collections.unmodifiableList(this.departments);
}
public void setDepartments(List<String> departments) {
this.departments = new ArrayList<>(departments);
}
Modifying the getter to return an unmodifiable collection, will return a read-only collection that cannot be updated or used to sneak new values in, instantiating a new collection in the setter, will separate the input parameter from the data in your POJO, making it impossible to sneak in new values through it.
Conclusion #
Every time you create an instance of an object in a POJO, remember that such objects are passed by reference and as such, they can be updated outside your POJO, independently from your setters and getters. Treat POJO data as holy and immutable and make sure only the POJO have permissions to alter the data it owns.