Computer Science 15-100, Fall 2008
Class Notes:  Inheritance and Polymorphism


  1. Inheritance: extends and instanceof
  2. Inheriting Methods
  3. Overriding Methods
  4. Overriding vs Overloading Methods
  5. Calling Super Methods
  6. Calling Super Constructors
  7. Final (Non-Overrideable) Methods
  8. Final (Non-Extendable) Classes
  9. Abstract (Non-Instantiable) Classes
  10. Class Hierarchies
  11. Polymorphism
    1. Example:  Polymorphism via Inheritance
    2. Example:  Polymorphism via Interfaces
  12. Additional Topics
    1. More Polymorphism
      1. Counterexample:  Non-Polymorphic Early Binding of Overloaded Operators
      2. Counterexample:  Non-Polymorphic Early Binding of Overloaded Methods
      3. Counter-counterexample:  Polymorphism via instanceof
    2. Multiple Inheritance vs Single Inheritance + Interfaces
  13. Even More Additional Topics
    1. Initialization Sequence
    2. Inheriting Instance Variables (and "protected" visibility)
    3. Shadowing Instance Variables (Not recommended!)
    4. The Singleton Pattern
    5. The Listener + Adapter Pattern
    6. getClass() + .class + getSuperclass()

Inheritance and Polymorphism

  1. Inheritance: extends and instanceof
    class MyCode {
      public static void main(String[] args) {
        Animal animal = new Collie();
        System.out.println(animal instanceof Collie);
        System.out.println(animal instanceof Dog);
        System.out.println(animal instanceof Animal);
        System.out.println(animal instanceof Object);
        System.out.println(animal instanceof Jersey);
        System.out.println(animal instanceof Cow);
      }
    }
    
    class Animal { }
    class Dog extends Animal { }
    class Beagle extends Dog { }
    class Collie extends Dog { }
    class Cow extends Animal { }
    class Holstein extends Cow { }
    class Jersey extends Cow { }
    
    Another example:
    class MyCode {
      public static void main(String[] args) {
        System.out.println("Collie:");
        checkInstanceOf(new Collie());
    
        System.out.println("Dog:");
        checkInstanceOf(new Dog());
    
        System.out.println("Holstein:");
        checkInstanceOf(new Holstein());
      }
    
      public static void checkInstanceOf(Object object) {
        System.out.println("  instanceof Object   = " + (object instanceof Object));
        System.out.println("  instanceof Animal   = " + (object instanceof Animal));
        System.out.println("  instanceof Dog      = " + (object instanceof Dog));
        System.out.println("  instanceof Collie   = " + (object instanceof Collie));
        System.out.println("  instanceof Beagle   = " + (object instanceof Beagle));
        System.out.println("  instanceof Cow      = " + (object instanceof Cow));
        System.out.println("  instanceof Holstein = " + (object instanceof Holstein));
        System.out.println("  instanceof Jersey   = " + (object instanceof Jersey));
      }
    }
    
    class Animal { }
    class Dog extends Animal { }
    class Beagle extends Dog { }
    class Collie extends Dog { }
    class Cow extends Animal { }
    class Holstein extends Cow { }
    class Jersey extends Cow { }
  2. Inheriting Methods
     
  3. Overriding Methods
     
  4. Overriding vs Overloading Methods
     
  5. Calling Super Methods
     
  6. Calling Super Constructors
     
  7. Final (Non-Overrideable) Methods
     
  8. Final (Non-Extendable) Classes
     
  9. Abstract (Non-Instantiable) Classes
     
  10. Class Hierarchies
     
  11. Polymorphism
     
    1. Example:  Polymorphism via Inheritance
      class MyCode {
        public static void main(String[] args) {
          A a1 = new A();
          A a2 = new B();
      
          System.out.println("Polymorphism via inheritance:");
          System.out.println("Though a1 and a2 are both of type A, different");
          System.out.println("methods are called by a1.hello() and a2.hello():");
          System.out.println(a1.hello());
          System.out.println(a2.hello());
        }
      }
      
      class A {
        public String hello() {
          return "A says hello!";
        }
      }
      
      class B extends A {
        public String hello() {
          return "B says howdy!";
        }
      }
    2. Example:  Polymorphism via Interfaces
      class MyCode {
        public static void main(String[] args) {
          Hello h1 = new A();
          Hello h2 = new B();
      
          System.out.println("Polymorphism via interfaces:");
          System.out.println("Though h1 and h2 are instances of classes that are");
          System.out.println("not subclasses of each other, both classes implement");
          System.out.println("interface Hello, so their instances can be stored in");
          System.out.println("variables of type Hello (like h1 and h2), and different");
          System.out.println("methods are called by h1.hello() and h2.hello():");
          System.out.println(h1.hello());
          System.out.println(h2.hello());
        }
      }
      
      interface Hello {
        public String hello();
      }
      
      class A implements Hello {
        public String hello() {
          return "A says hello!";
        }
      }
      
      class B implements Hello {
        public String hello() {
          return "B says howdy!";
        }
      }
  12. Additional Topics
     
    1. More Polymorphism
       
      1. Counterexample:  Non-Polymorphic Early Binding of Overloaded Operators
        class MyCode {
          public static void main(String[] args) {
            System.out.println("Non-polymorphism of overloaded operators:");
            
            System.out.println("The '+' operator can mean numeric addition:");
            Integer i1 = 1;
            Integer i2 = 2;
            System.out.println("   " + i1 + " + " + i2 + " = " + (i1 + i2));
        
            System.out.println("Or it can mean string concatenation:");
            String s1 = "a";
            String s2 = "b";
            System.out.println("   " + s1 + " + " + s2 + " = " + (s1 + s2));
            System.out.println();
        
            System.out.println("To be polymorphic, we'd need objects of the same");
            System.out.println("super-type (here, Object) to invoke these different");
            System.out.println("forms of the '+' operator depending on their sub-type");
            System.out.println("(here, Integer or String):");
            Object o1 = "a";
            Object o2 = "b";
            System.out.println("   " + o1 + " + " + o2 + " = " + (o1 + o2)); // will not compile
          }
        }
      2. Counterexample:  Non-Polymorphic Early Binding of Overloaded Methods
        class MyCode {
          public static void main(String[] args) {
            System.out.println("Non-polymorphism of overloaded methods:");
        
            System.out.println("Overloaded method 'add' works over Integers:");
            Integer i1 = 1;
            Integer i2 = 2;
            add(i1, i2);
        
            System.out.println("And another overloaded method 'add' works over Strings:");
            String s1 = "a";
            String s2 = "b";
            add(s1, s2);
        
            System.out.println("To be polymorphic, we'd need objects of the same");
            System.out.println("super-type (Object) to invoke these different");
            System.out.println("forms of the 'add' method depending on their sub-type");
            System.out.println("(here, Integer or String)");
            Object o1 = "a";
            Object o2 = "b";
            add(o1, o2);  // will not compile
          }
        
          public static void add(Integer a, Integer b) {
            System.out.println("   " + a + " + " + b + " = " + (a + b));
          }
        
          public static void add(String a, String b) {
            System.out.println("   " + a + " + " + b + " = " + (a + b));
          }
        }
      3. Counter-counterexample: Polymorphism via instanceof
        class MyCode {
          public static void main(String[] args) {
            System.out.println("Polymorphism with overloaded methods using instanceof.");
            System.out.println("NOTE: This approach is NOT RECOMMENDED, but is shown");
            System.out.println("here for demonstrational purposes.");
            System.out.println();
        
            System.out.println("As 'add' is polymorphic, it works with objects of the same");
            System.out.println("super-type (Object) to invoke different forms of the 'add'");
            System.out.println("method depending on their sub-type (here, Integer or String):");
        
            Object o1 = "a";
            Object o2 = "b";
            add(o1, o2);  // works -- does String concatenation!
        
            Object o3 = 1;
            Object o4 = 2;
            add(o3, o4);  // works -- does Integer addition!
          }
        
          public static void add(Integer a, Integer b) {
            System.out.println("   " + a + " + " + b + " = " + (a + b));
          }
        
          public static void add(String a, String b) {
            System.out.println("   " + a + " + " + b + " = " + (a + b));
          }
        
          // A polymorphic dispatcher using instanceof.
          // This style is NOT RECOMMENDED, but is shown
          // here for demonstrational purposes.
          public static void add(Object a, Object b) {
            if ((a instanceof Integer) && (b instanceof Integer))
              add((Integer)a, (Integer)b);
            else if ((a instanceof String) && (b instanceof String))
              add((String)a, (String)b);
            else {
              System.out.println("Unexpected types, using toString's...");
              add(a.toString(), b.toString());
            }
          }
        }
    2. Multiple Inheritance vs Single Inheritance + Interfaces
       
  13. Even More Additional Topics
     
    1. Initialization Sequence
      import java.util.*;
      class MyCode {
        public static void main(String[] args) {
          A a;
          System.out.println("Creating a new instance of A:");
          a = new A();
          System.out.println("Creating another new instance of A:");
          a = new A();
          System.out.println("Creating a new instance of B:");
          a = new B();
          System.out.println("Creating another new instance of B:");
          a = new B();
        }
      }
      
      
      // A simple counter which prints out a message each time Counter.next()
      // is called, so we can easily track the order of events.
      class Counter {
        private static int counter = 0;
      
        public static int next(String msg) {
          ++counter;
          System.out.println("  Step #" + counter + ": " + msg);
          return counter;
        }
      }
      
      class A {
        private static int sa = Counter.next("Static variable 'sa' in A");
        private int ia = Counter.next("Instance variable 'ia' in A");
      
        public A() {
          int la = Counter.next("Local variable 'la' in A's constructor");
        }
      }
      
      class B extends A {
        private static int sb = Counter.next("Static variable 'sb' in B");
        private int ib = Counter.next("Instance variable 'ib' in B");
      
        public B() {
          int lb = Counter.next("Local variable 'lb' in b's constructor");
        }
      }
    2. Inheriting Instance Variables (and "protected" visibility)
       
    3. Shadowing Instance Variables (Not recommended!)
       
    4. The Singleton Pattern
       
    5. The Listener + Adapter Pattern
       
    6. getClass() + .class + getSuperclass()

carpe diem   -   carpe diem   -   carpe diem   -   carpe diem   -   carpe diem   -   carpe diem   -   carpe diem   -   carpe diem   -   carpe diem