Encapsulation in Java

Consider a case that can help you better understand the concept of encapsulation. Assume there is a class Student with 'id' and 'name' attributes. We want to enforce, the student id must be greater than 0. Assume another class StudentTest that creates a Student object, initilize its attributes and 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 is allowed to store invalid data. The reason is, the Student class exposed its instance attributes to outer world (i.e. other classes) and did not enforced any rule to update the attribute values. When other classes can read/write the attributes directly (using dot operator), we can not impose rules about data validation. 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' attributes 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 restrict the direct access and expose the attributes in a way that ensures the data validation when the attributes are updated. 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 modifier 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 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 memoy, no object can have an id that is less than 0. We can also add this condition in user-class i.e. where object is created and id field is initialized, to acheive same functionality. Its correct, but it has two issues: 1. we have to write the condition everytime the id need to be changed, if there are hundreds of places where we need to change id, we have to use data validation rules each time, that is definitely not a good solution fom software reusability perspective. Defining the validation logic in set method, we have placed the validation code at one central place 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 encapsulated field 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 required that you must define get and set methods for all private attributes. If for some reason, you don't want to expose the attribute to other classes, you can skip making the get and set methods. If you want to give read access only to other classes but not the permission to change the values of private attribute, you would define 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 the type of attribute it returns 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 being set.
  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