Beginners Guide to Java Encapsulation and Subclassing

Encapsulation

Encapsulation is one of the four fundamental object-oriented programming concepts. The other three are inheritance, polymorphism, and abstraction. The term encapsulation means to enclose in a capsule or to wrap something around an object to cover it. Encapsulation covers, or wraps, the internal workings of a Java object.
– Data variables, or fields, are hidden from the user of the object.
– Methods, the functions in Java, provide an explicit service to the user of the object but hide the implementation.
– As long as the services do not change, the implementation can be modified without impacting the user.

An analogy for encapsulation is the steering wheel of a car. When you drive a car, whether it is your car, a friend’s car, or a rental car, you probably never worry about how the steering wheel implements a right-turn or left-turn function. The steering wheel could be connected to the front wheels in a number of ways: ball and socket, rack and pinion, or some exotic set of servo mechanisms. As long as the car steers properly when you turn the wheel, the steering wheel encapsulates the functions you need―you do not have to think about the implementation.

Encapsulation: Example

What data and operations would you encapsulate in an object that represents an employee?

A Simple Model

Suppose that you are asked to create a model of a typical employee. What data might you want to represent in an object that describes an employee?

  • Employee ID: You can use this as a unique identifier for the employee.
  • Name: Humanizing an employee is always a good idea.
  • Social Security Number: For United States employees only. You may want some other identification for non-U.S. employees.
  • Salary: How much the employee makes is always good to record.

What operations might you allow on the employee object?

  • Change Name: If the employee gets married or divorced, there could be a name change.
  • Raise Salary: It increases based on merit.

After an employee object is created, you probably do not want to allow changes to the Employee ID or Social Security fields. Therefore, you need a way to create an employee without alterations except through the allowed methods.

Encapsulation: Public and Private Access Modifiers

Java has three visibility modifiers: public, private, and protected.

– The public keyword, applied to fields and methods,allows any class in any package to access the field or method.
– The private keyword, applied to fields and methods, allows access only to other methods within the class itself.

Employee emp=new Employee();
emp.salary=2000;          // Compiler error- salary is a private field
emp.raiseSalary(2000);    //ok

– The private keyword can also be applied to a method to hide an implementation detail.

Encapsulation: Private Data, Public Methods

In Java, we accomplish encapsulation through the use of visibility modifiers. Declaring Java fields private makes it invisible outside of the methods in the class itself.

In this example, the fields custID, name, and amount are now marked private, making them invisible outside of the methods in the class itself.

– The Employee class currently uses public access for all of its fields.
– To encapsulate the data, make the fields private.

public class Employee {
  
   private int empId;
   private String name;
   private String ssn;
   private double salary;
  
   //... constructor and methods
}

Employee Class Refined

The current setter methods in the class allow any class that uses an instance of Employee to alter the object’s ID, salary, and SSN fields. From a business standpoint, these are not operations you would want on an employee. Once the employee is created, these fields should be immutable (no changes allowed).

1 public class Employee {
2     // private fields ...
3    public Employee () {
4   }
5    // Remove all of the other setters
6   public void changeName(String newName) {
7    if (newName != null) {
8     this.name = newName;
9   }
10  }
11
12   public void raiseSalary(double increase) {
13     this.salary += increase;
14   }
15 }

The Employee model as defined in the previous section, had only two operations: one for changing an employee name (as a result of a marriage or divorce) and one for increasing an employee’s salary.

To refine the Employee class, the first step is to remove the setter methods and create methods that clearly identify their purpose. Here there are two methods, one to change an employee name (setName) and the other to increase an employee salary (raiseSalary).

Note that the implementation of the setName method tests the string parameter passed in to make sure that the string is not a null. The method can do further checking as necessary.

Make Classes as Immutable as Possible

Good Practice: Immutability

Finally, because the class no longer has setter methods, you need a way to set the initial value of the fields. The answer is to pass each field value in the construction of the object. By creating a constructor that takes all of the fields as arguments, you can guarantee that an Employee instance is fully populated with data before it is a valid employee object. This constructor replaces the default constructor.

Granted, the user of your class could pass null values, and you need to determine if you want to check for those in your constructor. Strategies for handling those types of situations are discussed in later lessons.

Removing the setter methods and replacing the no-arg constructor also guarantees that an instance of Employee has immutable Employee ID and Social Security Number (SSN) fields.

1 public class Employee {
2    // private fields ...
3    // Create an employee object
4    public Employee (int empId, String name,
5                     String ssn, double salary) {
6       this.empId = empId;
7       this.name = name;
8       this.ssn = ssn;
9       this.salary = salary;
10   }
11
12  public void changeName(String newName) { ... }
13
14  public void raiseSalary(double increase) { ... }
15 }

Method Naming: Best Practices

Choosing Well-Intentioned Methods

Just as fields should clearly define the type of data that they store, methods should clearly identify the operations that they perform. One of the easiest ways to improve the readability of your code (Java code or any other) is to write method names that clearly identify what they do.

Although the fields are now hidden by using private access, there are some issues with the current Employee class.

– The setter methods (currently public access ) allow any other class to change the ID, SSN, and salary (up or down).
– The current class does not really represent the operations defined in the original Employee class design.
– Two best practices for methods:

  • Hide as many of the implementation details as possible.
  • Name the method in a way that clearly identifies its use or functionality.

– The original model for the Employee class had a Change Name and an Increase Salary operation.

Encapsulation: Benefits

The benefits of using encapsulation are as follows:

  • Protects an object from unwanted access by clients
  • Prevents assigning undesired values for its variables by the clients, which can make the state of an object unstable
  • Allows changing the class implementation without modifying the client interface

Creating Subclasses

You created a Java class to model the data and operations of an Employee. Now suppose you wanted to specialize the data and operations to describe a Manager.

1 package com.example.domain;
2 public class Manager {
3     private int empId;
4     private String name;
5     private String ssn;
6     private double salary;
7     private String deptName;
8     public Manager () { }
9     // access and mutator methods...
10 }

Specialization by Using Java Subclassing

The Manager class shown here closely resembles the Employee class, but with some specialization. A Manager also has a department, with a department name. As a result, there are likely to be additional operations as well.

What this demonstrates is that a Manager is an Employee―but an Employee with additional features. However, if we were to define Java classes this way, there would be a lot of redundant coding.

Subclassing

In an object-oriented language like Java, subclassing is used to define a new class in terms of an existing one.

A Simple Java Program

When an existing class is subclassed, the new class created is said to inherit the characteristics of the other class. This new class is called the subclass and is a specialization of the superclass. All the nonprivate fields and methods from the superclass are part of the subclass.

superclass: Employee ("parent" class) 

In this diagram, a Manager class gets all the nonprivate fields and all the public methods from Employee. It is important to grasp that although Manager specializes Employee, a Manager is still an Employee.

Note: The term subclass is a bit of a misnomer. Most people think of the prefix “sub” as meaning “less.” However, a Java subclass is the sum of itself and its parent. When you create an instance of a subclass, the resulting in-memory structure contains all codes from the parent class, grandparent class, and so on all the way up the class hierarchy until you reach the class Object.

Manager Subclass

The code snippet below demonstrates the Java syntax for subclassing. The keyword extends creates the inheritance relationship:

public class Manager extends Employee { }

The code snippet below demonstrates an inheritance relationship between the Manager class and, its parent, the Employee class.

- The Manager class, by extending the Employee class, inherits all of the non-private data fields and methods from Employee.
- Since a manager is also an employee, then it follows that Manager has all of the same attributes and operations of Employee.

Note: The Manager class declares its own constructor. Constructors are not inherited from the parent class. There are additional details about this in the next slide.

Constructors in Subclasses

Every subclass inherits the nonprivate fields and methods from its parent (superclass). However, the subclass does not inherit the constructor from its parent. It must provide a constructor.

The Java Language Specification includes the following description: “Constructor declarations are not members. They are never inherited and therefore are not subject to hiding or overriding.”

Although a subclass inherits all of the methods and fields from a parent class, it does not inherit constructors. There are two ways to gain a constructor:

  • Write your own constructor.
  • Use the default constructor.
    • If you do not declare a constructor, a default no-arg constructor is provided for you.
    • If you declare your own constructor, the default constructor is no longer provided.

Using super

To construct an instance of a subclass, it is often easiest to call the constructor of the parent class.

- In its constructor, Manager calls the constructor of Employee.
- The super keyword is used to call a parent's constructor.
- It must be the first statement of the constructor.
- If it is not provided, a default call to super() is inserted for you.
- The super keyword may also be used to invoke a parent's method or to access a parent's (nonprivate) field.

 super (empId, name, ssn, salary);

Constructing a Manager Object

Even though the Manager.java file does not contain all of the methods from the Employee.java class (explicitly), they are included in the definition of the object. Thus, after you create an instance of a Manager object, you can use the methods declared in Employee.

You can also call methods that are specific to the Manager class as well.

- Creating a Manager object is the same as creating an Employee object:

Manager mgr = new Manager (102, "Barbara Jones", "107-99-9078", 109345.67, "Marketing");

- All of the Employee methods are available to Manager:

mgr.raiseSalary (10000.00);

- The Manager class defines a new method to get the Department Name:

String dept = mgr.getDeptName();

Overloading Methods

You might want to design methods with the same intent (method name), like print, to print out several different types. You could design a method for each type:

printInt(int i)
printFloat(float f)
printString(String s)

But this would be tedious and not very object-oriented. Instead, you can create a reusable method name and just change the argument list. This process is called overloading.

With overloading methods, the argument lists must be different—in order, number, or type. And the return types can be different. However, two methods with the same argument list that differ only in return type are not allowed.

Two rules apply to overloaded methods:

  • Argument lists must differ.
  • Return types can be different.

Therefore, the following is not legal:

public void print (int i)
public String print (int i)

Overloaded Constructors

In addition to overloading methods, you can overload constructors. The overloaded constructor is called based upon the parameters specified when the new is executed.

Overloaded Constructors: Example

The example below demonstrates overloaded constructors: there are three overloaded constructors, which vary based on the no of arguments.

public class Box {
  
   private double length, width, height;
  
   public Box() {
        this.length = 1;
        this.height = 1;
        this.width = 1;
   }
  
   public Box(double length) {
       this.width = this.length = this.height = length;
  }
  
   public Box(double length, double width, double height) {
       this.length = length;
       this.height = height;
       this.width = width;
       System.out.println("and the height of " + height + ".");
  }
  
   double volume() {
      return width * height * length;
  }
}

Single Inheritance

The Java programming language permits a class to extend only one other class. This is called single inheritance.

Related Post