Why we have private data and public getters setters to access them

It is common norm in Object oriented languages like Java to keep data which belongs to class as private and provide a public interface in the form of getter and setter method to access the data.

This wrapping up of data and methods which act on this data within single class has been named as encapsulation. In this post, let us try to understand what is the benefit we achieve out of keeping data private and with public getter ,setters to access data.





Below are few valid points which justifies to have private instance variables and public getters setters

1. We can make sure that right value is being set by putting some validations in the setters and right  value is being returned by putting some validations in getters.

2. We can provide only setter or only getter to make write only or read only.

3. Different access level can be given to setter and getter as per requirements.

4. Instance variables does not participate in polymorphism, however instance methods do participate.

Let us discuss these one by one


1.We can put validation in the setters before assigning values.

Let us first try to understand the concept of private in Java. If we look at the literal meaning of private, it means something we want to keep to ourselves and don't want to share with outside world. In Java world, if we have instance variables as private then that means these instance variables(data) can be accessed only from within class in which these variables are defined and access from any other outside class is not allowed.
Let us see how we will access private data if we don't have getters and setters for the private data.
package com.blogspot.javasolutionsguide;

public class Account {
    private String accountNumber;

    public static void main(String[] args) {
        Account account = new Account();
        //setting value directly without setter                        
        account.accountNumber = "123456";
        //getting value directly without getter               
        String actNumber = account.accountNumber;
        System.out.println(actNumber);
    }
}
Output : 12345
Assuming that in Account class, accountNumber needs to be set and get only at above mentioned two places,it looks manageable(Not recommended though,not a good practice).Now consider it is big class with lots of methods in it, written or maintained by different set of programmers,it is great chance that wrong value will be set in the accountNumber.
Say it is expected that accountNumber can only be of length 6,but if we follow above approach ,it can easily be set to value of less than 6 digit number as below
Account account = new Account();
account.accountNumber = "123";
and your program will fail miserably somewhere or will give some unexpected results.
How you will make sure that accountNumber will always be of length 6.....Provide a method which checks value which we are going to set in accountNumber that whether it is actually of length 6.If it is of length 6,set the value,otherwise fail immediately by throwing exception Or log it and skip this record.Now if this is the only class from where you will be setting value,you could keep your setter and getter private also as below :
    //Setter method
    private void setAccountNumber(String accountNumber) {
        if (accountNumber.length() == 6) {
            this.accountNumber = accountNumber;
        } else {
            throw new someException();
            //Or
            Log.error("Incorrect account number:" + accountNumber);

        }
    }
   //Getter method
    private String getAccountNumber() {
        if (accountNumber == null) {
            accountNumber = "999999";  //Some default Value
        }
        return accountNumber;
    }
But in case your Account class instance variables needs to be accessed from outside Account class, then you must declare getter and setter as Public, else these will not be accessible, so your public getter and setter will look like as below:
    //Setter method
    public void setAccountNumber(String accountNumber) {
        if (accountNumber.length() == 6) {
            this.accountNumber = accountNumber;
        } else {
            throw new someException();
            // Or
            Log.error("Incorrect account number:" + accountNumber);
        }
    }
   //Getter method
    public String getAccountNumber() {
        if (accountNumber == null) {
            accountNumber = "999999";  //Some default Value
        }
        return accountNumber;
    }
and below is how you will access accountNumber from outside Account class :

a) When accountNumber is not null and is of length 6
package com.blogspot.javasolutionsguide;

public class TestClass {
    public static void main(String[] args) {
        Account account = new Account();
        account.setAccountNumber("123456");
        System.out.println(account.getAccountNumber());
    }
}
output : 123456

b) When accountNumber is null
package com.blogspot.javasolutionsguide;

public class TestClass {
    public static void main(String[] args) {
        Account account = new Account();
        System.out.println(account.getAccountNumber());
    }
}
output : 999999

c) When length of accountNumber is not equal to 6
package com.blogspot.javasolutionsguide;

public class TestClass {
    public static void main(String[] args) {
        Account account = new Account();
        account.setAccountNumber("123");
        System.out.println(account.getAccountNumber());

    }
}
2. We can provide only setter or only getter to make write only or read only property

a)Making write only property

If you have used Spring framework, you must be aware of setter injection. Let us take a quick  example.
package com.blogspot.javasolutionsguide;

public class AccountAppService {

    private AccountService accountService;

    public void setAccountService(AccountService accountService) {
        this.accountService = accountService;
    }

    /*Method which used accountService reference to make call to one of it's method*/
    public void getAccounts() {
        accountService.getAccounts();
    }
} 
  
In applicationContext.xml

<bean id="accountAppService" class="com.jsg.AccountAppService">
   <property name ="accountService">
      <ref bean="accountService"/>
   </property>
</bean>

b)Making readOnly property

In some scenarios we would want to make our property(s) read only and would not like to set explicitly. In those scenarios we can just provide public getters with no setter to set value.
package com.blogspot.javasolutionsguide;

public Class Account{
    private final String accountNumber="99999";
    public String getAccountNumber(){
        return accountNumber;
    }
}

3. Different access level can be given to setter and getter as per requirements. 

We may want value of instance variable to be set from classes within package or subclasses outside package and value can be accessed (read or retrieved) from anywhere, then we can mark setter as protected and keep getter as public. Point is we have control over our data.


//Setter method
protected void setAccountNumber(String accountNumber) {
        if(accountNumber.length()== 6){
            this.accountNumber = accountNumber;
        }else{
            throw new someException();
            Or
            Log.error("Incorrect account number:"+accountNumber);
        }
    }

//Getter method
public String getAccountNumber() {
   if(accountNumber==null){
          accountNumber = "999999";  //Some default Value
   }
  return accountNumber;
}

4. Instance variables does not participate in polymorphism, however instance methods do participate.

Let us take two scenarios.

1. Define instance variables as public  and try to access them polymorphically.

Parent Class : HumanBeing
package com.blogpsot.javasolutionsguide;

public class HumanBeing {
   public String name="HumanBeing";
}

Subclass : Employee
package com.blogspot.javasolutionsguide;

public class Employee extends HumanBeing{
   public String name="Employee";
}

Client Class : TestClass
package com.blogspot.javasolutionsguide;

public class TestClass {

 public static void main(String[] args) {
  
  HumanBeing human = new Employee();
  System.out.println("Instance variables does not participate in polymorphism:"+human.name);
  
 }
}

Output :
Instance variables does not participate in polymorphism:HumanBeing

So what we did....
- we kept instance variable "name" as public in parent class HumanBeing.
- Employee class inherited HumanBeing class, which means it inherited "name" from it as it is public  in HumanBeing
- In Employee ,we defined another instance variable with name "name". The thing to note here is that we are not overriding "name" property which Employee class inherited from HumanBeing class, but we have defined just an another variable which happens to be of same name as that of variable in parent class. In the Employee class, if we will refer "name" anywhere ,it will always give value "Employee".
- In client code(TestClass) we created object of Employee class referred to by reference variable of type HumanBeing.
  HumanBeing human = new Employee();
- we accessed variable name using reference variable of type HumanBeing.

Now here which class's name variable will be accessed is determined by compile time type of reference variable using which we are accessing instance variable. As in our case compile time type of reference variable "human" is HumanBeing,so when we did human.name, it returned value of name in HumanBeing class.

2. Define instance variable as private and try to access them via accessor polymorphically.

Parent class : HumanBeing
package com.blogspot.javasolutionguide;

public class HumanBeing {

 private String name="HumanBeing";
 public String getName() {
  return name;
 }
}

Subclass : Employee
package com.blogspot.javasolutionsguide;
public class Employee extends HumanBeing{
  private String name="Employee";
      
  public String getName() {
  return name;
  }
}


Client Class : 
package com.blogspot.javasolutionsguide;
public class TestClass {

 public static void main(String[] args) {
  
  HumanBeing human = new Employee();
  System.out.println("Instance methods participate in polymorphism:"+human.getName());
 }
}
Output : 
Instance methods participate in polymorphism:Employee

So what we did....
- We kept instance variable "name" as private in parent class HumanBeing and we we deifned a public method getName() which returns name.
- Employee class inherited HumanBeing class,but it could not inherit name property of HumanBeing class,as name is defined private in HumanBeing class,but it could certainly inherit getName() method which is defined as public in HumanBeing class.
- In Employee ,we defined another private instance variable with name "name" and we overrode getName() method of HumanBeing class.
- In client code(TestClass) we created object of Employee class referred to by reference variable of type HumanBeing.
  HumanBeing human = new Employee();
- we accessed variable "name" using reference variable of type HumanBeing,but this time calling getName() method

 human.getName()

Now here which class's getName() method will be called is determined by the runtime type of the reference variable human.Now as here reference variable human is referring to Employee object at runtime,getName() method of Employee class will be called ,hence value of name defined in the Employee class will be returned.

Point is if we want to access value of certain instance variables polymorphically, we can do it by making instance variables private and accessing value through public method.

Summary 

By providing setter ,getter we have better control over the data and we can make sure that data integrity is maintained,so we keep instance variables(data) as private and provide public methods(API) to access them ,in a class.

Hope this post was helpful to you. If you can think of more reasons, please mention that in comments section, will add it to the post.