This article is Part 1 of the series Solid Code Design. We will start this article with the below diagram.
The above diagram defines a type hierarchy with a type, called SuperType, at the top of the hierarchy. Another important point is that subtypes can have subtypes.
How do you define a type hierarchy in Java?
In Java, we define a type hierarchy using inheritance. Whenever inheritance comes into picture, we picturize a superclass-subclass relationship. Consider the below classes.
class ClassT {
}
class ClassV extends ClassT {
}
Here ClassT is the superclass and ClassV is the subclass. Now consider the below code snippet:
ClassV v = new ClassV();
System.out.println(v.getClass());
System.out.println(v.getClass().getSuperclass());
The above code snippet will generate below output:
class ClassV
class ClassT
The class of the object v is ClassV and the superclass of ClassV is the ClassT.
Now consider the below classes and interfaces.
interface InterfaceA {
}
class ClassV implements InterfaceA {
}
You have written the below code snippet based on the above classes and interfaces.
ClassV v = new ClassV();
System.out.println(v.getClass());
System.out.println(v.getClass().getSuperclass());
System.out.println(v.getClass().getInterfaces()[0]);
The above code snippet will generate below output:
class ClassV
class java.lang.Object
interface InterfaceA
In this case, superclass of ClassV is Object class. Why? When a class doesn’t extend any other class, it implicitly extends java.lang.Object class.
We will have a look at another example. Consider the below classes and interfaces.
interface InterfaceA {
}
class ClassT {
}
class ClassV extends ClassT implements InterfaceA {
}
You have below code cnippet:
ClassV v = new ClassV();
System.out.println(v.getClass());
System.out.println(v.getClass().getSuperclass());
System.out.println(v.getClass().getInterfaces()[0]);
The above code snippet will generate below output:
class ClassV
class ClassT
interface InterfaceA
The class of the object v is ClassV and the superclass of ClassV is the ClassT.
Now time to go one step further with the below program (we call it Program A).
public class SuperClassInterfaceTypeDemo {
public static void main(String[] args) {
ClassV v1 = new ClassV("A");
InterfaceA v2 = new ClassV("B");
ClassT v3 = new ClassV("C");
System.out.println(v1.getInternalSymbol());
System.out.println(v2.getValue("L"));
System.out.println(v3.getName("L"));
}
}
interface InterfaceA {
// The method getValue returns Integer.MAX_VLAUE for the
// symbol "A" and Short.MAX_VALUE otherwise.
public int getValue(String symbol);
}
class ClassT {
// The method getName returns "Lion" for the symbol "L"
// and "Other" otherwise.
public String getName(String symbol) {
if (symbol.equals("L")) {
return "Lion";
} else {
return "Other";
}
}
}
class ClassV extends ClassT implements InterfaceA {
private String internalSymbol;
public ClassV(String internalSymbol) {
this.internalSymbol = internalSymbol;
}
@Override
public int getValue(String symbol) {
if (symbol.equals("L")) {
return Integer.MAX_VALUE;
} else {
return Short.MAX_VALUE;
}
}
// The method getInternalSymbol returns the internal
// symbol of the ClassV object
public String getInternalSymbol() {
return this.internalSymbol;
}
}
If you run the above program, you will get the below output:
A
2147483647
Lion
We can use the below diagram to represent the relationships between interface InterfaceA and classes ClassT and ClassV.
Here, objects of class ClassV may have three useful declared types: ClassV type, ClassT type, and InterfaceA type.
An object’s declared type determines the methods you can call on them. An object’s actual type may be different than its declared type. Consider the below examples:
// Both declared type and actual type are ClassV
ClassV v1 = new ClassV("A");
//Declared type is InterfaceA, but actual type is ClassV
InterfaceA v2 = new ClassV("B");
//Declared type is ClassT, but actual type is ClassV
ClassT v3 = new ClassV("C");
Compiler does the type checking based on the declared types. Declared types determine the legally allowed method calls. The below picture will help you understand better an object’s declared type and allowed method calls.
In this case, InterfaceA, ClassT, and Object are supertypes of subtype ClassV. In Java, we can define supertypes by both interfaces and classes.
In Java, every subclass is a Java subtype. Therefore, the subclass ClassV is a subtype.
In the above example, ClassT is the direct supertype of ClassV because ClassT is the direct superclass of ClassV (ClassV extends ClassT). Moreover, InterfaceA is the direct supertype of ClassV because InterfaceA is the direct superinterface of ClassV (ClassV implements InterfaceA).
According to Java Language Specification:
The supertypes of a type are obtained by reflexive and transitive closure over the direct supertype relation.
The subtypes of a type T are all types U such that T is a supertype of U, and the null type.
In the above example, ClassV is the subtype of ClassV (reflexive), ClassV is a subtype of ClassT, and ClassV is a subtype of InterfaceA.
That’s all for Part 1. You will learn about True Subtyping and more in Part 2 of the series Solid Code Design.
I have shared knowledge based on my understanding and experiences. If you find any significant errors or want to give me some feedback, feel free to contact me at maliksanjoykumar[@]gmail.com.
Sanjoy Kumar Malik is an experienced software architect and technologist. He is passionate about Cloud Computing, Software Architecture, and System Design. Apart from technology and software, he is an avid LinkedIn networker. You can join his 5.5+ lacs supporters on LinkedIn.