Hello, people..! Now we are going to look at the most basic and the first major topic in Object Oriented Programming, Encapsulation. For those who don’t know OOP, it is very important that you spend a lot of time trying to understand these concepts. Because once you learn these concepts, it will be exactly the same in any other OOP language (like C++, C#, etc.), only the syntax changes.
Code without Encapsulation
Let us first understand what happens when we don’t follow encapsulation. Let’s say you created the Person class where all the instance variables were public. The code would look like –
class Person { public String name; public int age; }
Now, since it was public, somebody used it in this way in his main() method –
public static void main(String[] args) { Person p = new Person(); p.name = "Vamsi Sangam"; p.age = -1; }
Okay, so doing that is legal. Neither the compiler nor the runtime complaints anything about it. But we all know that it’s not sane, right? Seriously, some values can’t be negative, like age, or length, or size etc. It’s like we created this class which everybody is using… Which we are happy about… But they can misuse it, or do stuff not meant for that class. So, we don’t have control.
Encapsulation
So how do get control over things? By implementing encapsulation. So, what’s encapsulation really? It is a set of rules, or a protocol, which we must follow to get more control over our data. Now, what are those rules which we must follow? They are –
- Keep instance variables private.
- Make public accessor methods.
So, let’s look at it one-by-one. Firstly, we must keep the instance variables private. What does that mean? Well, we just have to write “private” in the place of “public” before we write the data type of our instance variable. That restricts the scope of the instance variable. Now, the instance variable has scope only inside the class. It cannot be accessed by making an object and referencing it. So, if we implement our rule 1 to Person class, it would look like –
class Person { private String name; private int age; }
So, if you tried to use this class from another class’ main() method, and try to access those variables, it will give you a compilation error, saying “age has private access in Person”. The code is –
class Person { private String name; private int age; } class TestPerson { public static void main(String[] args) { Person p = new Person(); p.age = -1; // error } }
Great! So it looks like things went from bad to worse! We can’t even use those variables. Well, just wait till we implement the second rule. 😉
The second rules ask us to implement accessor methods. As the name suggests, accessor methods, are meant to access the variables which were marked private. These are also called Getter and Setter methods, or sometimes called as Mutator methods. Oracle thought the name “mutate” sounds bad, so we’ll stick to Getter and Setter methods. So, we will implement Getter and Setter methods for our Person class. As the name suggests the Getter method gets the value of the variable and the Setter method sets the variable to some value. The Person class with Getter and Setter methods would look like –
class Person { private String name; private int age; // Getter method for variable name public String getName() { return name; } // Setter method for variable name public void setName(String newName) { name = newName; } // Getter method for variable age public int getAge() { return age; } // Setter method for variable age public void setAge(int newAge) { age = newAge; } }
As you can see that is the format in which we write the getter and setter methods for a variable. What you write inside the function is your wish, but the access modifier “public”, return type, name, or arguments must be in that format. We’ll talk a little about the naming conventions in a few minutes.
Well, you could ask, what’s changed, you could still do the same cruel stuff we did above to your Person class! 😛 Well, look closely, things have changed! You can do the validation stuff in the setter method of ‘age’ variable. To avoid negative values you could do something like –
// Setter method for variable age public void setAge(int newAge) { if (newAge > 0) { age = newAge; } }
So, if the input is a valid one you set the ‘age’ variable to the new value, otherwise you ignore. Not just this, you can do any amount of validation you want. You could keep an upper cap of 125 years or anything like that. So you have complete control. Lastly, you would use the encapsulated class like this –
public static void main(String[] args) { Person p = new Person(); p.setName("Vamsi Sangam"); p.setAge(-1); // Ignored, so still 0 p.setAge(19); }
You can print the values of the variables using the getter methods if you want to. 🙂
JavaBeans Naming Convention
JavaBeans is a standard set by Oracle. It defines a few rules. We will only concern ourselves with the rules regarding the naming of getter and setter methods, commonly known as JavaBeans Naming Convention. It defines the naming conventions we must follow while naming the getter and setter methods. All classes in the Java library follow this convention strictly. JavaBeans calls an instance variables as a “property”. So the rules are –
Rule | Example |
---|---|
Properties are private. |
private int age; private boolean happy; private int numberOfChildren; |
Getter methods begin with is if the property is boolean. |
public boolean isHappy() { return happy; } |
Setter methods begin with set. |
public void setAge(int newAge) { age = newAge; } |
The method name must have a prefix of set/get/is, followed by the first letter of the property in uppercase, followed by the rest of the property name |
public int getNumberOfChildren() { return numberOfChildren; } |
Well, that was the convention rules. The first rule isn’t a naming rule, but it’s a part of JavaBeans so I saw it fit to keep it there.
Encapsulation with Mutable Classes
This is a little advanced topic in Encapsulation. Feel free to skip this part if you are new to OOP. But do check this later because it is an important pitfall! Now, mutable classes are those classes whose data can be changed after the object is created. Example, we saw that String objects are not mutable, whereas the objects of StringBuilder are. We must be careful when encapsulating mutable objects. Check out the class below. Do you think it is perfectly encapsulated?
class Person { private StringBuilder name; public Person(StringBuilder name) { this.name = name; } // Getter method for variable name public StringBuilder getName() { return name; } }
So, let’s say we have this class. We don’t have a setter here, but that’s fine, the class is still considered to be encapsulated (for now :P). So, it pretty much looks like we can’t change the value of the variable ‘name’ after we initialize it with a constructor. Now, check this out –
class TestPerson { public static void main(String[] args) { Person p = new Person(new StringBuilder("Vamsi Sangam")); // variable name will hold Vamsi Sangam System.out.println(p.getName()); // Storing the return value StringBuilder var = p.getName(); // Changing the object returned var.append("! Are you confused?"); // Printing the object inside encapsulated class System.out.println(p.getName()); // Prints "Vamsi Sangam! Are you confused?" } }
We’ve somehow managed to change the object held by the encapsulated field which doesn’t even have a getter method! This happens because, the variable name, is a reference to an object. In the getter method, we are returning the same object which name is pointing to. So if you return the reference to that object, you can actually manipulate it as you want, because it’s a mutable object. Changes made to the object returned by the getter method will reflect in the object held by name because they are exactly the same object.
I hope this made sense. Please go through what I said once more if you didn’t understand it. It’s a fairly simple concept. So what do we do in such a situation? We clone that property and return the clone. Cloning means creating a new object of the same data type as the property which has the same content.
So, a proper getter method for name would be –
class Person { private StringBuilder name; public Person(StringBuilder name) { this.name = name; } // Getter method for variable name public StringBuilder getName() { return new StringBuilder(name); } } class TestPerson { public static void main(String[] args) { Person p = new Person(new StringBuilder("Vamsi Sangam")); // variable name will hold Vamsi Sangam System.out.println(p.getName()); // Storing the return value StringBuilder var = p.getName(); // Changing the object returned var.append("! Are you confused?"); // Printing the object inside encapsulated class System.out.println(p.getName()); // Prints "Vamsi Sangam" } }
All this nuisance is because the property is of a mutable class. If it were something like a String, we wouldn’t have this problem. Because even if two references point to the same object, we cannot change the content of that object they are pointing to.
So, that was Encapsulation! It is a matter of 2 simple rules. Easy! Isn’t it..?! Keep practicing… Happy Coding..!! 😀