Encapsulation in Java

Before I write about encapsulation, let me first build a background case, from which we can understand the need of encapsulation that would help to learn encapsulation in great depth.

Assume there is a class Student with 'id' and 'name' attributes. I want the Student object to store 'id's only greater than 0 and 'name' should be valid i.e. it should not contains numbers of special characters. Assume another class StudentTest that creates a Student object, initilize its attributes and then print the initilalized values. See the code sample here:


public class Student {
int id;
String name;
}


public class StudentTest {
public static void main(String[] args) {

Student student = new Student(); // object instantiation

// Initialize instance attributes
student.id = -100;
student.name = "Alice23";

// Lets just print the instance attribute values
System.out.println("ID : " + student.id);
System.out.println("Name : " + student.name);
}
}

You see, we set the id to -100 and name to Alice23. Both attributes values are incorrect. But if we compile the code and run StudentTest class, it would be compiled and executed successfully and generates following output.

ID : -100
Name : Alice23

So the Student object allowed to store invalid data. The reason is, the Student class exposed its attributes to outer world (i.e. other classes) and did not enforced any rule about attribute values. When other classes can read/write the attributes directly (using dot operator), we can not impose rules about validity of data. So protecting the instance attributes from direct read/write access and providing methods to access them is called encapsulation. Encapsulation is also called data or information hiding. Before we discuss how to encapsulate attributes, you must know what are access modifiers. Click Here to read about Access Modifiers in Java.

Another benefit of encapsulation is, a class can change its attributes or data storage mechanism without effecting the client-code (I would try to cover this point in a separate article). For the time being, lets focus on main objective discussed earlier.

Protecting Attributes from Other Classes

To restrict access to object attributes, we define those attributes with 'private' access modifier. In this way, the user-classes (i.e. where the Student object is created e.g. StudentTest class) can not access the 'id' and 'name' attribute of the Student using dot operator on object refernece.

public class Student {
private int id;
private String name;
}
public class StudentTest {

public static void main(String[] args) {
Student student = new Student();
student.id = 1; // INVALID CODE, becasue 'id' is private, it can't be accessed from outside the Student class
}
}

How to Change the Private Attributes

So by making the attributes private, we have stopped other classes to access the object attributes directly. Encapsulation not only hides the data but also expose it in a way that ensure the data validity when its update. Its done by making the a public method that is used to set the private attribute. Such method is caller setter method, see below code, I have added the setter method for the id attribute in our Student class (same can be done for 'name' attribute too).

public class Student {
private int id;
private String name;

public void setId(int id) {
this.id = id;
}
}

The 'setId' method can access the private attributes because its in same class. All attributes are directly accessible from any method in the class no matter the attribute access modifiers is private, public or default. 'this' keyword is used in setId method, 'setId' receives the id as argument and we have named the argument 'id', so it is local variable of 'setId' method. But 'id' name is also used for instance variable, we must differentiate when doing the assignment. 'this.id' means the 'instance attribute id' and simply 'id' means, the 'local variable id'. So we have received value in local variable and assigned it to instance variable of the object.

As we have encapsulated the id attribute and exposed it via setId method, so other classes would use this setter to change the attribute, as shown below:
public class StudentTest {

public static void main(String[] args) {
Student student = new Student();
student.setId(1);
}
}

So when the attributes are encapsulated, they are accessed using setter method. Now the benefit is, we can define rules about data validty are central place. For example, if we want to store 'id' that must be greater than 0, we can change 'setId' method to following:
public class Student {
private int id;

public void setId(int id) {
if(id > 0 )
this.id = id;
else
System.out.println("Invalid value for 'id', so its not changed");
}
}

By doing so, even if there are thousands of Studennt object in RAM, no object can have an id that is less than 0. If you think, we can also add this condition in user-class i.e. where object is created, to acheive same functionality. Its correct, but it has two isses: 1. we have the write the condition everytime the id need to change, if there hundreds of places in our software where we need to change id, we have to define if/else there. which is not good solution. But placing the condition inside setter and enforcing other classes to use setter whenever they need to change the attribute value, we have placed the code at only one central location and it would be called each time the 'id' is changed.

How to Read Private Attributes

private attributes can't be read by other classes, so if we want to allow other classes to read the object attribute values. We must define a public method that returns the value. Such methods are called getter methods. So encapsulation hides the attributes from direct access and provides set and get methods to write or read values. Let see the code of the getter (I have done it for 'id', same can be done for 'name' attribute too):

public class Student {
private int id;
private String name;

public void setId(int id) {
if(id > 0 )
this.id = id;
else
System.out.println("Invalid value for 'id', so its not changed");
}

public int getId() {
return id;
}
}

'getId' is public, it receive nothing and returns the value of private attribute value. It can be called like this:

public class StudentTest {

public static void main(String[] args) {
Student student = new Student();
student.setId(1);
System.out.println("ID = " + student.getId());
}
}

Some Rules or Convention for Get and Set Methods

We must define get and set method for each attribute we want to expose to other classes. Each attribute should have its own get/set methods. Defining a get or set method that get or set more than one attributes is incorrect.

Its not a requirement, that one must have to define get/set methods for the private attributes. If for some reason, you don't want to expose the attribute to other classes, you can skip making the get/set methods. If you want to allow only read access to other classes but not the permission to change the values of private attribute, you can make the getter method public and skip the setter method.

If you are defining a method, either get or set; the name, argument and return type of the mothod must be as per convention.

  1. The name is prefixed with get or set word and the attribute name using the camel notation. e.g. if the attribute name is address, the set/get name would be setAddress and getAddress. If the attribute name is firstName, the methods names would be getFirstName and setFirstName.
  2. The get method's return type must be same as the type of the attribute that would be returned. and it must not receive any argument.
  3. The set method's return type is always void and it must receive only one argument whose type must be same as the type of the attribute.
  4. Access modifier for the get and set methods is generally public, but its not a requirement or convention. It can be changed as per application requirements.

Comments