Inheritance

Inheritance allows you to create classes hierarchically.
Subclasses inherit the superclass's implementation.

The key to object-oriented programming is reusability. You can reuse classes as they are or reuse super ones.

Reusing a superclass means creating a subclass appropriately from the superclass.

Let's start with classes unrelated to inheritance. The following are the Employee and Manager classes.

Employee.java
package net.java_school.oop;

public class Employee {
  private String name;
  private String position;
  private String telephone;
	
  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getPosition() {
    return position;
  }

  public void setPosition(String position) {
    this.position = position;
  }

  public String getTelephone() {
    return telephone;
  }

  public void setTelephone(String telephone) {
    this.telephone = telephone;
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append(name);
    sb.append("|");
    sb.append(position);
    sb.append("|");
    sb.append(telephone);

    return sb.toString();
  }
}
Manager.java
package net.java_school.oop;

public class Manager {
  private String name;
  private String position;
  private String telephone;
  private String manageJob;
	
  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getPosition() {
    return position;
  }

  public void setPosition(String position) {
    this.position = position;
  }

  public String getTelephone() {
    return telephone;
  }

  public void setTelephone(String telephone) {
    this.telephone = telephone;
  }

  public String getManageJob() {
    return manageJob;
  }

  public void setManageJob(String manageJob) {
    this.manageJob = manageJob;
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append(name);
    sb.append("|");
    sb.append(position);
    sb.append("|");
    sb.append(telephone);
    sb.append("|");
    sb.append(manageJob);
		
    return sb.toString();
  }
}
Test.java
package net.java_school.oop;

public class Test {
  public static void main(String[] args) {
    Employee james = new Employee();
    james.setName("JAMES");
    james.setPosition("CLERK");
    james.setTelephone("19");
    System.out.println(james.toString());	
	
    Manager blake = new Manager();
    blake.setName("BLAKE");
    blake.setPosition("MANAGER");
    blake.setTelephone("9");
    blake.setManageJob("Project Management");
    System.out.println(blake.toString());
  }	
}

The two classes above have nothing to do with each other.

However, since the proposition that a manager is an employee is correct, the two classes should be an inheritance relationship that establishes an is-a relationship.

Since an employee is a concept broader than a manager, an employee is a superclass, and a manager is a subclass.

To establish inheritance as code, make sure that there is an overlapping code in the employee and manager classes.

You can find the name, position, telephone field, and getters and setters methods are overlapping.

You can expect that the part inherited from the employee class will disappear from the manager class source.

Modify the Manager class to inherit the Employee class like below.

Manager.java
package net.java_school.oop;

public class Manager extends Employee {
  private String manageJob;
	
  public String getManageJob() {
    return manageJob;
  }

  public void setManageJob(String manageJob) {
    this.manageJob = manageJob;
  }
	
  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append(getName());
    sb.append("|");
    sb.append(getPosition());
    sb.append("|");
    sb.append(getTelephone());
    sb.append("|");
    sb.append(manageJob);
		
    return sb.toString();
  }
}

You can use the 'extends' keyword in the class declaration of a subclass to inherit a superclass.

The superclass's access modifiers are still working when the subclass accesses the inherited variables or methods.

The reason for using getName(), getPosition(), and getTelephone() in the toString() method of Manager is that Employee's name, position, and telephone are private. To access these variables directly, you need to change the access modifiers of these superclass variables.

Since the employee class and the manager class are in the same package, modifying these variables' access modifiers in the employee class as package-private, protected, or public allows direct access to these superclass variables from the subclass.

Suppose the employee class and the manager class are not the same packages. In that case, you must declare the employee class's name, position, and telephone as protected or public for direct access from outside.

An object of a subclass type can access a superclass's protected variables and methods even if the package of the subclass and superclass are different. The protected access modifier protects the inheritance relationship between classes in different packages.

Run the Test class to see the results.

Method Overriding

You can use methods inherited from a parent class, but you can redefine those methods. It is called Method Overriding.

When redefining parent class methods in a child class, the return type, method name, and parameter list must be the same as those of the parents.

The toString() of the manager class overrides the toString() of the employee class, and the toString() of the employee class overrides the toString() of the Object class.

Is there the 'extends' keyword in the class declaration of Employee? None.
A class without the 'extends' keyword inherits the Object class at the class declaration.

The compiler intervenes and changes class Employee to class Employee extends Object. As a result, the superclass of the employee class becomes an Object.

An element such as @Override is called an annotation. An annotation passes information to the compiler or platform that code cannot pass to the compiler or platform. @Override tells the compiler that the method overrides the superclass method.

Constructor

In the method main of Test class, Manager blake = new Manager(); is the code to create Manager object. Now it is time to talk more about this part of the code. The Manager() that comes after new is the code that calls the Manager() constructor. You need to declare the Manager() in the class body like the method.

After a new keyword, you can call one of the constructors declared in the class.

We did not create a constructor for the Employee and Manager classes in the above example. However, we have code that calls the constructor in the method main. And the example runs without error. It means that the calling constructor has run. How did the undeclared constructor run?

The compiler compiles after adding a parameterless constructor to the class code when there isn't any constructor. The constructor that the compiler automatically generates is called the default constructor. The compiler does not add a default constructor if you declare any constructors.

You can declare multiple constructors with different parameter lists.

The constructor is called just once after an object is created and is not called again.

Don't assume that a constructor creates an object. The new keyword allocates space for an object in the heap memory and initializes the variables of an object. Then the constructor after the new keyword is called.

When the constructor finishes without errors, the reference variable is assigned a reference value that can refer to the created object. If there is an error in the constructor, the reference variable is not assigned a reference value, and as a result, the object is disabled.

JVM calls a constructor after creating an object. So, most constructors consist of code that initializes an object. A constructor must have no return type, and the constructor name must be the same as the class name. Many people mistake putting the 'void' keyword before the constructor's name when making a constructor. If you add the 'void' keyword, it becomes a method, not a constructor.

Subclasses do not inherit superclass constructors. The first line of the subclass constructor must invoke the superclass constructor. If not, the compiler adds code to the first line that calls the default constructor of the superclass.

Before adding appropriate constructors to the Employee and Manager classes, look at keywords: 'this' and 'super.'

this

When JVM executes 'this,' 'this' has the instance's reference. You can use the 'this' keyword to differentiate between a constructor parameter and an instance variable within a constructor. Also, you can use this to call another constructor within a constructor.

Note that when you write your code in Eclipse, you can get code assists for the available resources (variables, methods, and so on) by typing dot after this.

super

You can use the super keyword in the following cases:

  • When you need to call superclass methods hidden by Method overriding
  • When you need to call superclass constructors within a subclass constructor

Add constructors to the Employee like below.

public Employee() {} //default constructor

public Employee(String name, String position, String telephone) {
  this.name = name;
  this.position = position;
  this.telephone = telephone;
}

Add constructors to the Manager like below.

public Manager() {} //default constructor

public Manager(String name, String position, String telephone, String manageJob) {
  super(name, position, telephone);
  this.manageJob = manageJob;
}

Modify the main() of the Test.

Employee james = new Employee("JAMES", "CLERK", "19");
System.out.println(james.toString());

Manager blake = new Manager("BLAKE", "MANAGER", "9", "Project Management");
System.out.println(blake);

In System.out.println(blake);, the println() calls the toString() of the instance pointed to by the variable blake. Therefore, the results of System.out.println(blake.toString()); and System.out.println(blake); are the same.

The compiler changes constructors as below.

public Employee() {
  super();
}

public Employee(String name, String position, String telephone) {
  super();
  this.name = name;
  this.position = position;
  this.telephone = telephone;
}
public Manager() {
  super();
}

public Manager(String name, String position, String telephone, String manageJob) {
  super(name, position, telephone);
  this.manageJob = manageJob;
}

You can assign a subclass type reference to a superclass type variable

Codes with polymorphism make you feel like they are in the same shape but executed in various forms.
You can experience polymorphism by assigning a subclass type reference to a superclass type variable.

Polymorphism

Guitar extends Instrument
Drum extends Instrument

Instrument i = new Guitar();//i is the variable of Intrument type
/*
JVM casts the reference returned by new Guitar() to the Instrument type
and then assigns it to the variable i.
*/
i.play();//Guitar play
i = new Drum();
i.play();//Drum play

i.play();//Guitar play
i.play();//Drum play
i.play() plays a guitar or drum.
i.play() has polymorphism.

JVM determines Whether the i.play() code plays the guitar or the drum at runtime, not compile time.

When you assign a subclass type reference to a superclass type variable, JVM casts this reference to the superclass type. Not all variables and methods of an object can be accessed using this reference.

A reference cast to a superclass type can only access instance variables and instance methods inherited from a superclass and methods overridden by a subclass.

Add the following to the main() of the Test class.

Object king = new Manager("KING", "MANAGER", "1", "SALES");
System.out.println(king);
//king.setManageJob("ACCOUNTING");// You can not access the setManagerJob() method with an Object type reference.
//If you want to use the Manager object completely, you need to cast the reference to the Manager class type. 
Manager king1 = (Manager) king;
king1.setManageJob("ACCOUNTING");
System.out.println(king);

In the last line, System.out.println(king), the variable king has an Object class type's reference. The println() method internally calls the toString() of Object class. Since the instance created in the heap memory is the manager object, JVM calls the toString() that the manager class overrides. Regardless of the reference's data type, (2) and (3) are covered and can not be seen. Therefore, (1) is called.

Manager Object

Method Overloading

In Java, you can create multiple methods with the same name but different argument lists, called method overloading. Method Overloading ensures JVM calls the method with argument lists that match the arguments passed. Note that the return type has nothing to do with Method Overloading. You cannot create multiple methods with the same name and argument lists but only different return types.

In Java, naming is important. You should name a method so that its behavior is well understood. Method Overloading reduces this burden of naming.

Method Overloading also makes you think that the same method looks to run variously. Method Overloading is polymorphic. In System.out.println() code, the println() method looks like it prints whatever the argument value is. Java API developers have defined several println methods with different arguments. The appropriate println method reacts according to the passed value.

final

The keyword final is related to inheritance.

  • When used in a class declaration, it is not possible to create a subclass from this class.
  • When used in a method declaration, a subclass cannot override this method.

There are also uses that are not related to inheritance. When creating constants, prefix the variable name with the final keyword.

Abstract Class

In the class declaration section, a class with the abstract keyword before the class keyword is called an abstract class.

You cannot instantiate an abstract class using the new keyword.

To understand an abstract class, you first need to know the meaning of the abstract method. An abstract method does not have a method body. To declare an abstract method, add the abstract keyword after the access modifier in the method declaration. If a class has one abstract method, you must declare that class as an abstract class.

However, not all abstract classes are required to have abstract methods. If necessary, you can make an abstract class without an abstract method.

A class that inherits an abstract class must override the super class's abstract methods.

Let's create an abstract class by modifying the previous examples. Create an AbstractEmployee class.

AbstractEmployee.java
package net.java_school.example;

public abstract class AbstractEmployee {
  private String name;
	
  public AbstractEmployee() {}
	
  public AbstractEmployee(String name) {
    this.name = name;
  }
	
  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }
	
  //Abstract method
  public abstract void doWork();
  
}

The ambiguity of abstract classes improves portability. Of course, there must be a premise that the class design is good.

Change the Employee class to inherit the AbstractEmployee abstract class.
The Employee class must implement the abstract method, doWork() of the AbstractEmployee class.

Employee.java
package net.java_school.example;

public class Employee extends AbstractEmployee {
  private String position;
  private String telephone;
	
  public Employee() {}
	
  public Employee(String name,String position, String telephone) {
    super(name);
    this.position = position;
    this.telephone = telephone;
  }
	
  public String getPosition() {
    return position;
  }

  public void setPosition(String position) {
    this.position = position;
  }

  public String getTelephone() {
    return telephone;
  }

  public void setTelephone(String telephone) {
    this.telephone = telephone;
  }

  public String toString() {
    StringBuffer sb = new StringBuffer();
    sb.append(this.getName());
    sb.append("|");
    sb.append(position);
    sb.append("|");
    sb.append(telephone);
		
    return sb.toString();
  }
	
  @Override
  public void doWork() {
    System.out.println("work");
  }	
}

The Manager class does not change. Change the first line of the Test's main method as below.

AbstractEmployee james = new Employee ("JAMES", "CLERK", "19");

Interface

The Java interface has the interface keyword in place of the class keyword in the class declaration, and all methods of the class body are abstract methods. Since they are all abstract methods, you can omit the abstract keyword.

All fields declared in the interface body are static final.

As an abstract class, you cannot use an interface alone. You will use classes that implement an interface. A class that implements an interface is a class that implements all of the abstract methods of an interface. You can create a class that implements an interface using the 'implements' keyword in the class declaration sector. A comma-separated list of one or more interfaces can follow the 'implements' keyword, which can look like multiple inheritances.

The Java interface is the same as the user interface of the electronic product. Most TVs provide interfaces such as - volume + and - channel + at the bottom of the screen. The fact that electronics have adopted the same interface means that their usage is the same.

If the Java class is an electronic product, then the Java interface corresponds to the user interface of the electronic product. The implementation of the TV with the PDP, LCD, and LED in the CRT was different, but fortunately, the interface did not change. As a result, we had no trouble using the TV without having to look at the user manual after buying a new technology TV.

When should we use the interface?
If the focus is on what functions should be, you should consider using the interface.

Suppose you have a driver class that inherits the employee class and a transporter class that doesn't inherit the employee class but has many features like the driver class. You can create an interface with these functions.

However, we can not create a superclass with duplicate code in the transporter and driver classes. Because the driver cannot has two superclasses. There must be only one class after the 'extends' keyword in the class declaration. You can not list parent classes with (comma) after extends. But, you can create an interface with the duplicate functionality of the transporter and the driver.

Let's practice the contents so far in order. First, create a driver class as follows. Because a driver is an employee, the driver class inherits the employee class.

Driver.java
package net.java_school.example;

public class Driver extends Employee {
  private String carNo;
	
  public Driver() {}
	
  public Driver(String name, String position, String telephone, String carNo) {
    super(name, position, telephone);
    this.carNo = carNo;
  }

  public String getCarNo() {
    return carNo;
  }

  public void setCarNo(String carNo) {
    this.carNo = carNo;
  }

  public void drive() {
    System.out.println(this.getName() + " drives");
  }
	
  public void transport() {
    System.out.println(this.getName() + " transports");
  }
}
Transporter.java
package net.java_school.example;

public class Transporter {
  private String carNo;
	
  public String getCarNo() {
    return carNo;
  }

  public void setCarNo(String carNo) {
    this.carNo = carNo;
  }

  public void drive() {
    System.out.println("drives");
  }
	
  public void transport() {
    System.out.println("transports");
  }
}

The transporter and the driver class have the same features that drive() and transport(). You can create an interface with these same features.

Create a Drivable interface as follows.

Drivable.java
package net.java_school.example;

public interface Drivable {
  public void drive();
	
  public void transport();
}

Let's change the transporter and the driver classes to implement this interface.

Driver.java
package net.java_school.example;

public class Driver extends Employee implements Drivable {
  private String carNo;
	
  public Driver() {}
	
  public Driver(String name, String position, String telephone, String carNo) {
    super(name, position, telephone);
    this.carNo = carNo;
  }

  public String getCarNo() {
    return carNo;
  }

  public void setCarNo(String carNo) {
    this.carNo = carNo;
  }
	
  @Override
  public void drive() {
    System.out.println(this.getName() + " drives");
  }
	
  @Override
  public void transport() {
    System.out.println(this.getName() + " transports");
  }
}
Transporter.java
package net.java_school.example;

public class Transporter implements Drivable {
  private String carNo;
	
  public String getCarNo() {
    return carNo;
  }

  public void setCarNo(String carNo) {
    this.carNo = carNo;
  }

  @Override
  public void drive() {
    System.out.println("drives");
  }
	
  @Override
  public void transport() {
    System.out.println("transports");
  }
}

Add the following code snippet to the Test's main method.

Test.java
Drivable a = new Driver("Michael","CLERK","ext:8","015000");
System.out.println(a);
a.drive();
Drivable b = new Transporter();
// b.setCarNo("017000"); //error!
b.drive();

As you can see in inheritance, You can assign a subclass type reference to a superclass type variable. Likewise, you can assign a class --implements the interface-- type reference to the interface type variables.