I was preparing for Java Certification Exam and there are few scenarios which gets very complicated sometimes when to comes to a mixture of polymorphism, Inheritance, overloading, overriding, Generics as well as Casting.
I am stuck with understanding these examples mentioned below :
// Tree is the base class
class Tree {
String type = "unknown";
String getTreeString() {
return "Tree";
}
}
// DeciduousTree is a subclass of Tree
class DeciduousTree extends Tree {
String type = "deciduous";
@Override
String getTreeString() {
return "Leafy Tree";
}
}
// FruitTree is a subclass of Tree
class FruitTree extends Tree {
String type = "fruit";
@Override
String getTreeString() {
return "Fruit Tree";
}
}
public class UpcastExamples {
public static void main(String[] args) {
UpcastExamples upex = new UpcastExamples();
// Create two specific trees
Tree tree = new Tree();
Tree mapleTree = new DeciduousTree();
Tree appleTree = new FruitTree();
// we upcast deciduousTree to its parent class
Tree genericTreeMaple = (Tree) mapleTree;
Tree genericTreeApple = (Tree) appleTree;
Tree genericTreeTree = (Tree) tree;
// Print mapleTree's type
System.out.println("Tree type = " + mapleTree.type);
// Let's upcast to use the generic Tree's type..
System.out.println("Tree type = " + (genericTreeMaple.type));
// Print Fruit Tree's type
System.out.println("Tree type = " + appleTree.type);
// Upcasting to pass object as a parameter
upex.printTreeType(genericTreeTree);
upex.printTreeType(genericTreeMaple);
upex.printTreeType(genericTreeApple);
upex.printTreeType(tree);
upex.printTreeType(mapleTree);
upex.printTreeType(appleTree);
}
public void printTreeType(Tree tree) {
System.out.println("Tree type = " + tree.getTreeString());
}
}
I expected these upex.printTreeType(genericTreeMaple);upex.printTreeType(genericTreeApple);
to print Tree
since they are upcasted to Tree(Base Class) but somehow it grabs the methods of its child classes (I know they are overriden) but the same thing when implemented via method overloading then
Animal genericDog = new Dog();Animal genericCat = new Cat();
will hold on to the method of the parent's class.
I tried to remember like this parentClass p = new childClass() will always refer methods of parentClass but it seems to fail during overriding and upcasting.
Also, there are few other things mentioned below that I could not understand, I don't just want to memorize stuffs.
Here BaseClass class is the parent class
And NextClass inherits the BaseClass
BaseClass[] myNextArray = new NextClass[6];
BaseClass[] myNextArray2 = new BaseClass[6];
//Allows this:
NextClass[] nextArray = (NextClass[]) myNextArray; //Line 1
//But doesn't allows this:
NextClass[] nextArray2 = (NextClass[]) myNextArray2;
Do I have to memorize that Array of SubClass is not equal to Array of SuperClass in Java but how come it allows Line 1 is also a big doubt
In Java, each object (which includes arrays) has a type that is determined upon construction, e.g. using the new
operator. This type never changes.
Variables only contain references to objects. Think of a remote control. You can refer to an object using a variable having a broader type, i.e. the type of a superclass or interface of the object. But this doesn’t change the type of the object itself.
Therefore, when you invoke an overridable method, you will always invoke the most specific method of the object’s actual type. Further, a type cast will succeed if the object’s actual type is compatible. The variable’s reference type does not tell whether the type cast will succeed, as otherwise, we wouldn’t need the runtime check at all¹.
When you initialize a variable like
BaseClass[] myNextArray = new NextClass[6];
the object’s actual type is NextClass[]
, hence, a subsequent type cast to NextClass[]
can succeed. In contrast, using
BaseClass[] myNextArray2 = new BaseClass[6];
the object’s actual type is BaseClass[]
, hence, a type cast to NextClass[]
will fail.
Note that it is possible to change a reference variable to let it point to a different object, so whether casting the reference to a specific type will succeed may change too.
BaseClass[] myNextArray = new NextClass[6];
NextClass[] nextArray = (NextClass[]) myNextArray; // succeeds
myNextArray = new BaseClass[6]; // let myNextArray refer to a different object
nextArray = (NextClass[]) myNextArray; // will fail
¹ The only exception to the rule is that the compiler will reject certain type casts that can be proven at compile-time to be impossible to succeed, like trying to cast an Integer
to a String
.