Relation between a Super Class and Sub Class


A sub class has an ‘is a’ relationship with its superclass. This means that a sub class is a special kind of its super class. When we talk in terms of objects, a sub class object can also be treated as a super class object. And hence, we can assign the reference of a sub class object to a super class variable type. However, the reverse is not true. A reference of a super class object may not be assigned to a sub class variable. The following statements illustrate this concept by creating a Student object and assigning it to a Person variable. However, when we try to assign the reference of a Person object to a Student variable, we get a compilation error.
Person p=new Student(“Sai”, 19,100);
Student s=new Person(“Ram”,18);//error
This ‘is a’ relationship finds application in other areas too. For example, we can pass a Student object to a method which requires a Person object and we can return a Student object from a method whose return type is Person.
public Person someMethod () {
    Student s=new Student (“Sai”, 19,100);
    return s; // allowed
}

Public void needAPerson ( Person p ) {
    // code
}

Public void aMethod() {
    needAPerson ( new Student(“Sai”, 19,100) ) ; // allowed
}

The methods and variables of a class that are accessible will depend on the variable type and not the object type. For example, if we try to access the marks variables or the displayGrade() method of the Student object p above, we receive compilation errors. This is because, the accessible methods and variables are determined by the type of the variable and not the type of the object.
p.marks=100; // error
p.showGrades(); //errors
The reason behind this is that the sub class isn’t aware of any of the super classes that have been derived from it. Hence, it cannot assure that the object contains a marks variable or the showGrades() method.
This may sound unusual but you will agree that this is the correct way if you look at the following method and read the discussion that follow it.
public void displayPerson( Person p ) {
    p.displayDetails();
}
Assume that this method is defined in a new class. In this particular method, a statement like the following would seem quite absurd.
p.marks=100;
This is because we do not know whether this method would be called with a Student object or a Person object. If it is called with a Student object, there would be no problem with the above statement that access the marks variable of p, but if it is invoked with a Person object, then a problem would arise as p doesn’t posses marks. And since we specified the parameter type to be Person, we cannot assume that the caller will a Student object. Moreover, a Person object is more likely to be passed as that is what is the one required by the method.
But when we deal with overridden methods, the method call result resolution appears unusual. Before we look into it, include the following code in a main method and execute the program:
Person p=new Student(“Sai”, 12, 100);
p.printDetails();
When the program is executed, you will notice that the output contains three lines of text. The marks are printed along with the name and age. This contradicts what has been already said. The printDetails() method call above has to call the super class version as p is a Person object and Student methods cannot be invoked on p. So, p should also not be having any idea of the overriding method. But during run time, the version in Student was called. This is because of dynamic binding (also known as run time binding or run time polymorphism). When a class file is being compiled, the methods that can be called on a particular object referred to by a variable are decided by the data type of the variable. In the above example, the printDetails() method has been invoked on the Student object which is referred to by a Person variable p. Hence, the call would be resolved to the printDetails() method defined in the Person class during compile time. This is known as static binding which occurs during compile time. However, the actual version of the method to be called is decided during run time. If the super class variable holds a reference to a sub class object, the method calls to overridden methods would be resolved to the versions defined in the sub class. That is why the marks were also printed in the above case. If no overridden version exists for the method, then the call is resolved to the super class’ method.
However, such a thing doesn’t happen in the case of instance variables. Suppose, we have two variables both named var, one in the super class and in the sub class. If we access this variable through a super class variable holding a reference to a sub class object, we get the value corresponding to the super class variable and not the sub class variable, contrary to what was observed in the case of methods. The following example makes this concept clear.
class A {
    int var = 10;
}

Class B {
    int var = 20;
}

// in the main method of a class, write the following code

A obj =new B();
System.out.println(obj.var); // prints 10 and not 20

In order to check whether an object is of a particular class type, Java provides the instanceof operator (or keyword). This operator requires two operands, the first is a variable name and the second is a class type. The operator returns either true or false.
Student s=new Student(“Sai”, 12, 100);
boolean result1 = s instanceof Student; // true
Person p = new Person (“Ram”, 18);
boolean result2 = p instanceof Person; // true
The ‘is a’ relationship shows its effect on the working of instanceof operator also. A sub class object is an instance of it super class. Hence, the following behaviour.
boolean result3 = s instanceof Person; // true
The instanceof operator may be used to check whether a particular object passed is of the required data type. For example, if a method needs a Person object, a caller may pass a Student object also. To process these two types separately, we may use a code similar to the following:
public void process ( Person p ) {
    if ( p instanceof Student ) {
        // code
    } else {
        // code
    }
}
We will come back to the instanceof operator once again when we deal with interfaces.
The next topic to be dealt is casting from one class type to another. Just as we cast primitive data items, we can also cast reference data items to a different class type. Here too, the concepts of up casting, down casting, explicit casting and implicit casting come into picture. Implicit casting is performed in situations where we assign the reference of a sub class object to a super class variable, pass a sub class object reference to a method where a super class object is required or return a sub class object when a super class return is specified. In all these cases since a lower data type is converted to a higher data type, it is an up casting operation or type widening operation. Super classes are higher data types and sub classes are lower data types.
Person p = new Student (“Sai”, 19,100); // implicit casting
Student st = new Student (“Sai”, 19,97);
Person pe = st; // implicit casting
We may also perform explicit casting, using the cast operator as shown below.
Student s= (Student) p;
The above statement does not create a new Student object identical to p. It simply casts into to a Student type. Now, p and s refer to the same object and hence, changes made to the object through any one of the objects are reflected on the other.
Person p = new Student (“Sai”, 19,100); // implicit casting
Student s= (Student) p;
s.age=20;
System.out.println(p.age); // prints 20 and not19
When we cast objects from one type to another, the compatibility of these types is checked. For example, a Student object cannot be cast to String type as they are incompatible.
String str= (String) s; // error, s is a Student object
Just like the ‘is a ‘relationship, we also have the ‘has a’ relationship which is quite straightforward. A class A is said to have a ‘has a ‘ relationship with a class B if class A contains an instance variable of type B.
class A {
    B obj;
}

Class B {
}

Author: , 0000-00-00