Site Search:

Develop code that demonstrates the use of polymorphism; including overriding and object type versus reference type


Back OCAJP



If you need a review of java polymorphism, study this material Introduction To polymorphism first.

Previously, we describes inheritance and its benefits, inheritance allow subclass to inherit public and protected methods in super class, subclass can also redeclare variables therefore hides the variables declared in super class. In order to access the hidden variables defined in super class, super.variableName need to be used. Subclass can override methods declared in super class.

When overriding super class methods, the following rules have to be followed, otherwise the javac will give compilation errors.

  1. In contrary to overloaded methods, when overriding super class's method in the subclass, the same method name, parameter and return type need to be used.
  2. The method access modifier in sub class have to be more accessible than that of the super class.
  3. When the method needs to throw checked exceptions, the exceptions throwing in super class are also the superclasses of the exceptions throwing in the subclass. 
  4. When the method returns a value, the return type in super class is also the superclass of the return type in subclass.


OCAJP>cat test.java 
import java.io.*;
class test {
  public static void main(String...args) throws Exception{
    subClass sub = new subClass();
    sub.rule1Method(100);
    System.out.println("sub.a="+sub.a);
    //sub.rule1Method();  //error: no suitable method found for rule1Method(no arguments)
    sub.rule1Method("I pass it in");
    sub.rule2Method();
    sub.rule3Method();
    sub.rule3Method1();
    sub.rule3Method2();
    sub.rule3Method3();
    System.out.println("sub.rule4Method()="+sub.rule4Method());
  }
}
class superClass {
  int a = 0;
  public void rule1Method(int a) {}
  public void rule2Method() {}
  void rule3Method() throws Exception {System.out.println("throws Exception");}
  void rule3Method1() throws RuntimeException {System.out.println("rule3Method1 throws RuntimeException");}
  void rule3Method2() throws Exception {System.out.println("rule3Method2 throws Exception");}
  void rule3Method3() throws Exception {System.out.println("rule3Method3 throws Exception");}
  String rule4Method() {return "rule4 superClass";}
}

class subClass extends superClass {
  public void rule1Method(int a) {this.a = a;}  //override
  public String rule1Mehod() {return "not accessible from subClass";}  
  //public void rule1Mehod() {System.out.println("not accessbile");} //error: method rule1Mehod() is already defined in class subClass 
  public void rule1Method(String a) {System.out.println(a);}
  //void rule2Method() {} //attempting to assign weaker access privileges; was public 
  //void rule3Method() throws Throwable {}  //overridden method does not throw Throwable
  void rule3Method() throws IOException {System.out.println("throws IOException");} //IOExcpetion extends Exception, fine
  //void rule3Method() throws RunTimeException {}  //error: method rule3Method() is already defined in class subClass
  //RuntimeException is not checked exception, can not throw new types of checked exception in subclass 
  //void rule3Method1() throws Exception {System.out.println("throws Exception");} //overridden method does not throw Exception.
  void rule3Method1() {System.out.println("rule3Method1 no throws");}  //it is fine since RuntimeException is not checked exception 
  void rule3Method2() {System.out.println("rule3Method2 no throws");}  //ok to omit the throws, use super class's
  void rule3Method3() throws Error,RuntimeException {System.out.println("rule3Method3 throws Error");}  //ok to omit the throws, and throws any non-checked Excpetions
  //Object rule4Method() {return new Object();}  //return type Object is not compatible with String
  //int rule4Method() {return 1;} //return type int is not compatible with String 
  //void rule4Method() {};  //return type void is not compatible with String
  String rule4Method() {return "rule4 subClass";}
}

OCAJP>javac test.java 
OCAJP>java test
sub.a=100
I pass it in
throws IOException
rule3Method1 no throws
rule3Method2 no throws
rule3Method3 throws Error
sub.rule4Method()=rule4 subClass



In the above example, we have override methods in subclass, we then access the override methods via the subclass's reference, if we access the override methods via the super class's reference, the result may surprise you, well, that surprise is the key concept of polymorphism.

OCAJP>cat test.java 
import java.io.*;
class test {
  public static void main(String...args) throws Exception{
    subClass sub = new subClass();  //sub reference to an object with type subClass
    sub.rule3Method();  //expected, rule3Method is the one declared in subClass

    superClass su = new superClass(); //su reference to an object with type superClass
    su.rule3Method(); //expected, rule3Method is the one declared in superClass

    superClass sub2 = new subClass();  //sub2 reference to an object with type subClass
    sub2.rule3Method();  //not surprise maybe, rul3Method is the one declared in subClass (polymorphism)

    //subClass su2 = new superClass(); //error: incompatible types: superClass cannot be converted to subClass
    //subClass su2 = (subClass)new superClass();//java.lang.ClassCastException: superClass cannot be cast to subClass 
  }
}
class superClass {
  void rule3Method() throws Exception {System.out.println("rule3Method throws Exception");}
}

class subClass extends superClass {
  void rule3Method() throws IOException {System.out.println("throws IOException");}
}

OCAJP>javac test.java 
OCAJP>java test
throws IOException
rule3Method throws Exception
throws IOException


What happened here is, while keyword new create an object with a certain type, all created objects have to be accessed via reference. New subClass() created an object with type subClass in memory, the rule3Method of this object is the overriding version. This subClass object in memory can have multiple references pointing to it, these references can have either type subClass or superClass, however, they are all pointing to the same thing in memory. On the other hand, new subClass() created an object with type subClass in memory, the rule3Method is the original version. We can have multiple reference pointing to this object. If the reference type is superClass, we are fine, we got what's expected. The reference type can be subClass, compiler won't complain, however, at run time, jvm will throw java.lang.ClassCastException: superClass cannot be cast to subClass.


If you visualize, A surgeon named Peter is a doctor, you can reference Peter by referencing him as a doctor. A doctor can treatPatient, Peter surely have his way of treatPatient.



The object can also be accessed with interface type reference, as long as the object's class implements the interface. Nothing prevent irrelevant interface reference or class reference from pointing to the created objects at compiler time, if correct class type cast is used, however, these bogus pointers won't be able access the object, JVM will throw ClassCastException at runtime.

OCAJP>cat test.java 
import java.io.*;
class test {
  public static void main(String...args) throws Exception{
    canCry cancry = new class1();
    cancry.cry();
    cancry = new class2();
    cancry.cry();
    //irrelevant ir = new class1();  //error: incompatible types: class1 cannot be converted to irrelevant
    irrelevant ir = (irrelevant)new class1();
    
  }
}
interface canCry {
  public void cry();
interface irrelevant {
  public void confuse();
}
class class1 implements canCry{
  public void cry() {System.out.println("class1 cry");}
}

class class2 implements canCry {
  public void cry() {System.out.println("class2 cry");}
}

OCAJP>javac test.java 
OCAJP>java test
class1 cry
class2 cry
Exception in thread "main" java.lang.ClassCastException: class1 cannot be cast to irrelevant
at test.main(test.java:9)


Now we have known that we can access an object with its super class's reference variable or implemented interface's reference variable, we have already known polymorphism: the object a reference variable pointing to can change form at runtime, depending on what types of objects are referenced.

As the above example shows, methods call canCry.cry() changes behavior at run time, depending on canCry reference to object new class1() or new class2(). This is powerful technique. We can define a method that takes an instance of an interface as parameter, then pass in various types of objects which implements the interface in different ways. The behavior of the interface function therefore changes at runtime depending on types of objects passed in.

OCAJP>cat test.java 
import java.util.*;
class test {
  public static void main(String...args) throws Exception{
    ArrayList<canCry> cancrys = new ArrayList<>();
    cancrys.add(new class1());
    cancrys.add(new class2());
    //cancrys.add(new classn());
    test t = new test();
    t.cryTest(cancrys); 
  }
  void cryTest(ArrayList<canCry> cancrys) {
    for(canCry cancry: cancrys) {
      cancry.cry();
    }
  }
}
interface canCry {
  public void cry();
class class1 implements canCry{
  public void cry() {System.out.println("class1 cry");}
}

class class2 implements canCry {
  public void cry() {System.out.println("class2 cry");}
}

OCAJP>javac test.java 
OCAJP>java test
class1 cry
class2 cry

Back OCAJP