Getters and setters are two of the most fundamental components for maintaining encapsulation. Being an OOP language, getters and setters are also available in Java. Despite being a very easy to learn feature, many Java developers do not properly understand them and have been implementing them incorrectly. This has resulted in repeated technical errors. We will be discussing the basics of Java getters and setters, how to implement them and what common mistakes are made by developers that should be avoided.
Table of Contents
Getters and setters, also known as accessor and mutator in java, are the conventional methods used to retrieve and update the values of a variable, respectively. See this basic example below demonstrating a class with a private variable as getter and setter methods are used to set and get that private value:
1. public class class01 { 2. private int num1; 3. public int getNum1() { 4. return this.num1; 5. } 6. public void setNum1(int num) { 7. this.num1 = num; 8. } 9. }
Class01 declares a private variable called num1. As num1 is a private variable, you cannot access the variable directly from the outside of the class, see the code below:
1. Class01 obj = new class01(); 2. obj.num1 = 10; // It will prompt a compiler error, because num1 is private 3. int num = obj.num1; // same as above
Now to get the value, you must invoke a getter, getNum1(), and the setter, setNum1(), to read or update the variable from outside, like this:
1. Class01 obj = new class01(); 2. obj.setNum1(50); // This will work fine 3. int num = obj.getNum1(); // This as well
As mentioned earlier, the primary use of setter and getter is to maintain encapsulation. We require a setter method to update the value of a private variable from outside of the class and a getter method to read the value of that variable. It allows developers to control how the main variables in the code are accessed and updated.
The variable can be set within a specific range or maybe selected from a list of values using a setter. See the following code of a setter method:
1. public void setNum1(int num) { 2. if (num < 50 || num > 500) { 3. throw new IllegalArgumentException(); 4. } 5. this.num1 = num; 6. }
The condition in line 2 ensures that the value of the variable num1 must always be set between 50 and 500. If this variable had been allowed to be updated directly, it would look like this:
obj.num1 = 100;
There was a possibility of violation of the rule set for values ranging from 50 to 500. To avoid that, the best option is to declare the variable number as private and then use a setter to change it.
A getter method on the other hand is the only possible way to read the value of the variable, outside the class:
1. public int getNum1() { 2. return this.num1; 3. }
The naming scheme of getters and setters in Java should follow the Java bean naming convention as get<variable name>() and set<variable name>().
See the code snippet below:
1. private int Quantity; // The variable name is Quantity
So, the appropriate Java setters and getters names will be:
1. public void setQuantity(int Quantity) {…} 2. public int getQuantity() {…}
In case, the variable is Boolean, the getter is commonly named like is<variable name>() but here get<variable name>() can also be used. For example:
1. private boolean flag; 2. public String isFlag() {…}
Java developers, whether they are beginners or have been coding for quite some time, often make mistakes in using Java getters and setters. To them, such mistakes may not seem too critical, but they cause errors in many different situations.
Following are the most common mistakes made by Java developers when implementing getters and setters in Java. The correct method is also mentioned along with each of them to know how such mistakes can be avoided.
1. Read this following code snippet: 2. public String phoneNumber; 3. public void setPhoneNumber (String pNum) { 4. this.phoneNumber = pNum; 5. } 6. public String getPhoneNumber() { 7. return this.phoneNumber; 8. }
Here, the variable phoneNumber is declared as public, so it can be directly accessed using the dot (.) operator, leaving the setter and getter totally useless. Any condition assigned in the setter will also be violated in case if the value is accessed using the dot operation.
The correct practice is to use a relatively more restricted access modifier like protected or more preferably, private:
private String phoneNumber;
Here, we have a simple setter for data:
1. private int[] data; 2. public void setData(int[] newData) { 3. this.data = newData; 4. }
The following code snippet demonstrates the problem:
1. int[] myData = {16, 47, 12, 13, 62}; 2. setData(myData); 3. displayData(); 4. myData[1] = 1; 5. displayData();
Here, an array of integers, myData, is initialized with 5 values in line 1. The array is then passed to the setData() method in line 2.
The method displayData() simply prints out all the data from the array:
1. public void displayData() { 2. for (int i = 0; i < this.myData.length; i++) { 3. System.out.print(this.myData[i] + " "); 4. } 5. System.out.println(); 6. }
Line 3 will output the following result:
16 47 12 13 62
Now, in line 4, we have assigned a new value to the second element at the first index in the myData array:
myData[1] = 34;
What happens next is if we call the method displayData() again at line 5, the output will be:
16 34 12 13 62
You can see that that the value of the second element is now changed from 47 to 34 due to the assignment in line 4. You must have understood the point of it by now. The data saved in the array can be modified from outside of the scope without using the setter method. This again makes the setter useless as well as breaks the encapsulation. But how does that happen?
1. public void setData(int[] newData) { 2. this.data = newData; 3. }
Here, the member variable data is directly assigned to the method’s parameter variable newData. This means that both variables are referring to the myData array object in memory, so any changes made to either the data or myData variables are made on the same object.
A better practice for this situation is to copy elements from the newData array to the data array, one at a time.
See the modified version of the setter implemented this way:
1. public void setData(int[] newData) { 2. this.data = new int[newData.length]; 3. System.arraycopy(newData, 0, this.data, 0, newData.length); 4. }
Now, the member variable data is not referring to the object which itself was referred to by the newData variable. Instead, the data is now initialized to a new array with the same size as the array newData. Then, we copy all elements from the array newData to the data array, using System.arraycopy() method.
It will now give us the following output:
16 47 12 13 62 16 47 12 13 62
Now, both invocations of displayData() get the same output. It indicates that array data is independent and different from the array newData passed into the setter, thus the assignment statement will not affect the array data.
To conclude, if you ever have to pass an object reference into a setter method, do not directly copy that reference into the internal variable. Instead, you should copy values of the passed object into the internal object, just like we copied elements from one array to another in the above example.
See this getter method below:
private int[] data; public int[] getData() { return this.data; }
Now, look at the following code snippet:
1. int[] myData = {13, 23, 14, 37, 12, 34}; 2. setScores(myData); 3. displayData(); 4. int[] copyData = getData(); 5. copyData[1] = 44; 6. displayData();
It will produce the following output:
13 23 14 37 12 34 13 44 14 37 12 34
As you notice, the second element of the array myData has been successfully modified outside the setter, violating encapsulation in line 5. This is because since the getter method directly returns the reference of the internal variable data, the outside code can now obtain this reference and make changes to the internal object.
A useful practice to avoid such result is that, instead of returning the reference directly in the getter, we should return a copy of the object so that the outside code can only obtain a copy of the internal object, like this:
1. public int[] getData() { 2. int[] copy = new int[this.data.length]; 3. System.arraycopy(this.data, 0, copy, 0, copy.length); 4. return copy; 5. }
In short, never return a reference of the original object in the getter method. Instead, just return a copy of the original object.
Java offers some mutable data types like Date and Calendar. If their values are reset using a getter method or a reference method, it transforms into a big maintainability issue which is very difficult to debug. The best practice is to always use defensive copy (deep cloning) when making getters and setters in java for mutable data types.
The java.util.Date class implements the clone() method from the Object class. The method clone() returns a copy of the object, so it can be used for the getter and setter, as shown in the following example:
1. public void setDateOfAdmission(Date date) { 2. this.dateOfadmission = (Date) date.clone(); } 3. public Date getDateOfAdmission() { 4. return (Date) this.dateOfAdmission.clone(); }
When a custom type of object is defined, it would not be a suitable option to make the getter and setter straight away as we do for primitive data types. You should always implement the clone() method for your custom type. See the code below:
1. class Student { 2. private String name; 3. public Person(String name) 4. { 5. this.name = name; 6. } 7. public String getName() 8. { 9. return this.name; 10. } 11. public void setName(String name) 12. { 13. this.name = name; 14. } 15. public String toString() 16. { 17. return this.name; 18. } 19. public Object clone() 20. { 21. Student clone01 = new Student(this.name); 22. return clone01; 23. } 24. }
Here, the class Student implements the clone() method to return a cloned version of itself. Now, the setter method should be implemented like the example below:
1. public void setTeacher(Student student) { 2. this.teacher = (Student) student.clone(); 3. }
And the getter method like this:
1. public Student getTeacher() { 2. return (Student) this.teacher.clone(); 3. }
This is the best practice to write getter and setter for your custom object type. Just follow the below rules for implementing getter and setter:
We have covered some basic mistakes made by java developers while using setters and getters as well as their workarounds.
See Also: 20 Useful Libraries Java Programmers Should Know
Getters and setters in Java may seem to be very straightforward and basic, therefore they’re usually not given much attention to. However, it is crucial to implement them correctly in practice to avoid huge mistakes. It could either cause the code to misbehave or in the worst-case scenario, your programs could easily be exploited by someone who discovered such vulnerabilities generated by incorrect use of Java getters and setters. Even if you are an experienced developer, try to understand the use of setters and getters from scratch and implement these useful practices mentioned above to develop a robust and error-free code.
If you are proficient with Java getters and setters and are looking to make an excellent career in programming, sign up with Xperti. With Xperti, you will become part of a wide network of elite engineers and will be able to explore some excellent job opportunities at Fortune 500 companies across the US.
Shaharyar Lalani is a developer with a strong interest in business analysis, project management, and UX design. He writes and teaches extensively on themes current in the world of web and app development, especially in Java technology.
Create a free profile and find your next great opportunity.
Sign up and find a perfect match for your team.
Xperti vets skilled professionals with its unique talent-matching process.
Connect and engage with technology enthusiasts.
© Xperti.io All Rights Reserved
Privacy
Terms of use