Java Getter and Setter Tutorial - from Basics to Best Practices
- Details
- Written by Nam Ha Minh
- Last Updated on 30 September 2019   |   Print Email
Getter and setter are widely used in Java programming. However, not every programmer understands and implements these methods properly. So in this Java tutorial I would like to discuss deeply about getter and setter in Java.
Table of content:
1. What are getter and setter?
2. Why getter and setter?
3. Naming convention for getter and setter
4. Common mistakes when implementing getter and setter
5. Implementing getters and setters for primitive types
6. Implementing getters and setters for common object types
7. Implementing getters and setters for collection type
8. Implementing getters and setters for your own type
1. What are getter and setter?
In Java, getter and setter are two conventional methods that are used for retrieving and updating value of a variable.
The following code is an example of simple class with a private variable and a couple of getter/setter methods:
public class SimpleGetterAndSetter { private int number; public int getNumber() { return this.number; } public void setNumber(int num) { this.number = num; } }
The class declares a private variable, number. Since number is private, code from outside this class cannot access the variable directly, like this:
SimpleGetterAndSetter obj = new SimpleGetterAndSetter(); obj.number = 10; // compile error, since number is private int num = obj.number; // same as above
Instead, the outside code have to invoke the getter, getNumber() and the setter, setNumber() in order to read or update the variable, for example:
SimpleGetterAndSetter obj = new SimpleGetterAndSetter(); obj.setNumber(10); // OK int num = obj.getNumber(); // fine
So, a setter is a method that updates value of a variable. And a getter is a method that reads value of a variable.
Getter and setter are also known as accessor and mutator in Java.
2. Why getter and setter?
By using getter and setter, the programmer can control how his important variables are accessed and updated in a correct manner, such as changing value of a variable within a specified range. Consider the following code of a setter method:
public void setNumber(int num) { if (num < 10 || num > 100) { throw new IllegalArgumentException(); } this.number = num; }
That ensures the value of number is always set between 10 and 100. Suppose the variable number can be updated directly, the caller can set any arbitrary value to it:
obj.number = 3;
And that violates the constraint for values ranging from 10 to 100 for that variable. Of course we don’t expect that happens. Thus hiding the variable number as private and using a setter comes to rescue.
On the other hand, a getter method is the only way for the outside world reads the variable’s value:
public int getNumber() { return this.number; }
The following picture illustrates the situation:
So far, setter and getter methods protect a variable’s value from unexpected changes by outside world - the caller code.
When a variable is hidden by private modifier and can be accessed only through getter and setter, it is encapsulated. Encapsulation is one of the fundamental principles in object-oriented programming (OOP), thus implementing getter and setter is one of ways to enforce encapsulation in program’s code.
Some frameworks such as Hiberate, Spring, Struts… can inspect information or inject their utility code through getter and setter. So providing getter and setter is necessary when integrating your code with such frameworks.
3. Naming convention for getter and setter
The naming scheme of setter and getter should follow Java bean naming convention as follows:
getXXX() and setXXX()
where XXX is name of the variable. For example with the following variable name:
private String name;
then the appropriate setter and getter will be:
public void setName(String name) { } public String getName() { }
If the variable is of type boolean, then the getter’s name can be either isXXX() or getXXX(), but the former naming is preferred. For example:
private boolean single; public String isSingle() { }
The following table shows some examples of getters and setters which are qualified for naming convention:
Variable declaration | Getter method | Setter method |
int quantity
|
int getQuantity() |
void setQuantity(int qty) |
string firstName
|
String getFirstName() |
void setFirstName(String fname) |
Date birthday
|
Date getBirthday() |
void setBirthday(Date bornDate) |
boolean rich
|
boolean isRich() boolean getRich() |
void setRich(Boolean rich) |
4. Common mistakes when implementing getter and setter
People often make mistakes, so do developers. This section describes the most common mistakes when implementing setter and getter in Java, and workarounds.
Mistake #1: Have setter and getter, but the variable is declared in less restricted scope.
Consider the following code snippet:
public String firstName; public void setFirstName(String fname) { this.firstName = fname; } public String getFirstName() { return this.firstName; }
The variable firstName is declared as public, so it can be accessed using dot (.) operator directly, making the setter and getter useless. Workaround for this case is using more restricted access modifier such as protected and private:
private String firstName;
In the book Effective Java, Joshua Bloch points out this problem in the item 14: In public classes, use accessor methods, not public fields.
Mistake #2: Assign object reference directly in setter
Considering the following setter method:
private int[] scores; public void setScores(int[] scr) { this.scores = scr; }
And following is code that demonstrates the problem:
int[] myScores = {5, 5, 4, 3, 2, 4}; setScores(myScores); displayScores(); myScores[1] = 1; displayScores();
An array of integer numbers myScores is initialized with 6 values (line 1) and the array is passed to the setScores() method (line 2). The method displayScores() simply prints out all scores from the array:
public void displayScores() { for (int i = 0; i < this.scores.length; i++) { System.out.print(this.scores[i] + " "); } System.out.println(); }
Line 3 will produce the following output:
5 5 4 3 2 4
That are exactly all elements of the myScores array.
Now at line 4, we can modify the value of the 2nd element in the myScores array as follow:
myScores[1] = 1;
What will happen if we call the method displayScores() again at line 5? Well, it will produce the following output:
5 1 4 3 2 4
You can realize that the value of 2nd element is changed from 5 to 1, as a result of the assignment in line 4. Why does it matter? Well, that means the data can be modified outside scope of the setter method which breaks encapsulation purpose of the setter. And why that happens? Let’s look at the setScores() method again:
public void setScores(int[] scr) { this.scores = scr; }
The member variable scores is assigned to the method’s parameter variable scr directly. That means both the variables are referring the same object in memory - the myScores array object. So changes made to either scores variable or myScores variable are actually made on the same object.
Workaround for this situation is to copy elements from scr array to scores array, one by one. The modified version of the setter would be like this:
public void setScores(int[] scr) { this.scores = new int[scr.length]; System.arraycopy(scr, 0, this.scores, 0, scr.length); }
What’s difference? Well, the member variable scores is no longer referring to the object referred by scr variable. Instead, the array scores is initialized to a new one with size equals to the size of the array scr. Then we copy all elements from the array scr to the array scores, using System.arraycopy() method.
Run the example again and it will give the following output:
5 5 4 3 2 4 5 5 4 3 2 4
Now the two invocation of displayScores()produce the same output. That means the array scores is independent and different than the array scr passed into the setter, thus the assignment:
myScores[1] = 1;
does not affect the array scores.
So, the rule of thumb is, if you pass an object reference into a setter method, then don’t copy that reference into the internal variable directly. Instead, you should find some ways to copy values of the passed object into the internal object, like we have copied elements from one array to another using System.arraycopy() method.
Mistake #3: Return object reference directly in getter
Consider the following getter method:
private int[] scores; public int[] getScores() { return this.scores; }
And the following code snippet:
int[] myScores = {5, 5, 4, 3, 2, 4}; setScores(myScores); displayScores(); int[] copyScores = getScores(); copyScores[1] = 1; displayScores();
it will produce the following output:
5 5 4 3 2 4 5 1 4 3 2 4
As you notice, the 2nd element of the array scores is modified outside the setter, at line 5. Because the getter method returns reference of the internal variable scores directly, so the outside code can obtain this reference and makes change to the internal object.
Workaround for this case is that, instead of returning the reference directly in the getter, we should return a copy of the object, so the outside code can obtain only a copy, not the internal object. Therefore we modify the above getter as follows:
public int[] getScores() { int[] copy = new int[this.scores.length]; System.arraycopy(this.scores, 0, copy, 0, copy.length); return copy; }
So the rule of thumb is: do not return reference of the original object in getter method. Instead, it should return a copy of the original object.
5. Implementing getters and setters for primitive types
With primitive types (int, float, double, boolean, char…), you can freely assign/return values directly in setter/getter because Java copies value of one primitive to another instead of copying object reference. So the mistakes #2 and #3 can be avoided.
For example, the following code is safe because the setter and getter involve in a primitive type of float:
private float amount; public void setAmount(float amount) { this.amount = amount; } public float getAmount() { return this.amount; }
So, for primitive types, there is no special trick to correctly implement the getter and setter.
6. Implementing getters and setters for common object types
Getters and Setters for String objects
String is an object type, but it is immutable which means once a String object is created, its String literal cannot be changed. In other words, every change on that String object will result in a new String object created. So, like primitive types, you can safely implement getter and setter for a String variable like this:
private String address; public void setAddress(String addr) { this.address = addr; } public String getAddress() { return this.address; }
Getters and Setters for Date objects
The java.util.Date class implements clone() method from the Object class. The method clone() returns a copy of the object, so we can use it for the getter and setter, like the following example:
private Date birthDate; public void setBirthDate(Date date) { this.birthDate = (Date) date.clone(); } public Date getBirthDate() { return (Date) this.birthDate.clone(); }
The clone() method returns an Object, so we must cast it to Date type.
You can learn more about this in the item 39: Make defensive copies when needed in this well-known Java book.
7. Implementing getters and setters for collection type
As described in mistake #2 and mistake #3, it’s not good to have setter and getter methods like this:
private List<String> listTitles; public void setListTitles(List<String> titles) { this.listTitles = titles; } public List<String> getListTitles() { return this.listTitles; }
Consider the following program:
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); } }
According to the rules for implementing getter and setter, the three System.out.println() statements should produce the same result. However, when running the above program it produces the following output:
Titles 1: [Name, Address, Email, Job] Titles 2: [Name, Address, Habilitation, Job] Titles 3: [Full name, Address, Habilitation, Job]
That means the collection can be modified from code outside of the getter and setter.
For a collection of Strings, one solution is to use the constructor that takes another collection as argument, for example we can change code of the above getter and setter as follows:
public void setListTitles(List<String> titles) { this.listTitles = new ArrayList<String>(titles); } public List<String> getListTitles() { return new ArrayList<String>(this.listTitles); }
Re-compile and run the CollectionGetterSetter program, it will produce desired output:
Titles 1: [Name, Address, Email, Job] Titles 2: [Name, Address, Email, Job] Titles 3: [Name, Address, Email, Job]
NOTE: The constructor approach above is only working with collections of Strings, but it will not work for collections objects. Consider the following example for a collection of Person object:
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; } }
It produces the following output when running:
List 1: [Peter, Alice, Mary] List 2: [Peter, Alice, Maryland] List 3: [Peter Crouch, Alice, Maryland]
Because unlike String which new objects will be created whenever a String object is copied, other Object types are not. Only references are copied, so that’s why two collections are distinct but they contain same objects. In other words, it is because we haven’t provided any mean for copying objects.
Look at the collection API we found that ArrayList, HashMap, HashSet, … implement their own clone() methods, these methods return shallow copies which do not copy elements from the source collection to the destination. According to Javadoc of the clone() method of ArrayList class:
public Object clone() Returns a shallow copy of this ArrayList instance. (The elements themselves are not copied.)
Thus we cannot use the clone() method of these collection classes. The solution is to implement the clone() method for our own defined object – the Person class in the above example. We implement the clone() method in the Person class as follows:
public Object clone() { Person aClone = new Person(this.name); return aClone; }
The setter for listPeople is modified as follows:
public void setListPeople(List<Person> list) { for (Person aPerson : list) { this.listPeople.add((Person) aPerson.clone()); } }
The corresponding getter is modified as follows:
public List<Person> getListPeople() { List<Person> listReturn = new ArrayList<Person>(); for (Person aPerson : this.listPeople) { listReturn.add((Person) aPerson.clone()); } return listReturn; }
That results in a new version of the class CollectionGetterSetterObject as follows:
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); } }
Compile and run the new version of CollectionGetterSetterObject, it will produce the desired output:
List 1: [Peter, Alice, Mary] List 2: [Peter, Alice, Mary] List 3: [Peter, Alice, Mary
So key points for implementing getter and setter for collection type are:
- For collection of String objects: does not need any special tweak since String objects are immutable.
- For collection of custom types of object:
- Implement clone() method for the custom type.
- For the setter, add cloned items from source collection to the destination one.
- For the getter, create a new collection which is being returned. Add cloned items from the original collection to the new one.
8. Implementing getters and setters for your own type
If you define a custom type of object, you should implement clone() method for your own type. For example:
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; } }
As we can see, the class Person implements its clone() method to return a cloned version of itself. Then the setter method should be implemented as follows:
public void setFriend(Person person) { this.friend = (Person) person.clone(); }
And for the getter method:
public Person getFriend() { return (Person) this.friend.clone(); }
So the rules for implementing getter and setter for a custom object type are:
- Implement clone() method for the custom type.
- Return a cloned object from the getter.
- Assign a cloned object in the setter.
To conclude, I recommend you to follow Java best practices and if you have time and budget, consider to learn more from this Java course.
Other Java Coding Tutorials:
- 10 Common Mistakes Every Beginner Java Programmer Makes
- 10 Java Core Best Practices Every Java Programmer Should Know
- How to become a good programmer? 13 tasks you should practice now
- How to calculate MD5 and SHA hash values in Java
- How to generate random numbers in Java
- Java File Encryption and Decryption Example
Comments
for example you often use the accessor
public String getName() ...
So would it be myEmployeeInstance.getName; or myEmployeeInstance.getName(); There is a significant distinction here. One is syntactically more a method, the other more a property/attribute/field...
Just to prove your point??
You should have used getter method first and tried add or whatever, to say it is mutable or immutable.