oop-digress

Table of Contents

1 Overloading, Overriding and Hiding

This concerns statically typed OO languages like Java, which support overloading and overriding. When we call a method o.f(arg), on object o, how does the function call take place? In particular, since f may have multiple definitions within the same class (overloading), and maybe redefined by subclasses (overriding), picking the "correct" function involves some reasoning.

We will discuss two concepts: covariance and contravariance. We will finally look at how it bears on the question, whether an array of a class can be extended to an array of a subclass

To keep the arguments specific, we stick with Java, without generics.

2 Method Signatures

The main concept in method dispatch is that of the signature of a method.

Two methods have the same signature if they have the same name and argument types. (This definition is modified substantially in Java with generics, but we're not dealing with it here.)

Two methods M and N have the same argument types if they have the same number of formal parameters, and the types match exactly.

3 Method Declaration

3.1 Overriding

Suppose class C implements a method m1 and class D which extends C, implements m2.

We say m2 overrides m1 if

  1. The signatures of m1 and m2 are the same.
  2. Either

    2.1 m1 is public, protected or has default access in D.

    2.2 m2 overrides a method n1, such that n1 overrides m1.

 +-------+
 |  C    |
 | m1    |
 +-------+
    ^
............
. +------+ .
. | A    | .
. | n1   | .
. +------+ .
............
    ^
 +------+
 |  D   |
 | m2   |
 +------+

Two method signatures are override-equivalent if they are the same.

class Point{ int x = 0, y = 0; void move(int dx, int dy) { x+=dx; y+=dy;} void move(Point p){System.out.println("Point.move");} }

public class BoundedPoint extends Point{ void move(BoundedPoint P) { System.out.println("BP.move"); }

public static void main(String[] args) { Point p = new BoundedPoint(); p.move(p); } }

class Point{
    int x = 0, y = 0;
    void move(int dx, int dy) { x+=dx; y+=dy;}
    void move(Point p){System.out.println("Point.move");}
}

public class BoundedPoint extends Point
{
    //not override equivalent to Point.move
    void move(Object P)
    {
        System.out.println("BP.move");
    }

    public static void main(String[] args)
    {
        Point p = new BoundedPoint();
        p.move(p); //prints "Point.move"
    }
}

Overriding allows covariant return types: In the following example, the move in BoundedPoint2 overrides the move in Point, even though the return types are different. There is no error because the return type in the subclass method is a subtype of the return type in the superclass.

class Point{
    int x = 0, y = 0;

    Point move(Point p){System.out.println("Point.move");return this;}
}

public class BoundedPoint2 extends Point{
    BoundedPoint2 move(Point P)
    {
        System.out.println("BP2.move");
        return this;
    }

    public static void main(String[] args)
    {
        Point p = new BoundedPoint2();
        p.move(p); //prints "BP2.move"
    }
} 

3.2 Hiding

If a class declares m to be static, then it hides any method n with matching signature in its superclass. A compile-time error occurs if a static method hides an instance method.

Point.java

3.3 Overloading

Suppose a class has two methods with the same name, which are not override-equivalent. It does not matter whether both these methods are declard in the class, or if one of them is inherited from a superclass. Then the method is said to be overloaded.

For two overloaded functions, there is no restriction on the return type.

class Point{
    int x = 0, y = 0;
    void move(int dx, int dy) { x+=dx; y+=dy;}
}

class BoundedPoint extends Point{
    int xLimit = 1000, yLimit=1000;
    
    //overrides move in Point
    void move(int dx, int dy)
    {
        x = Math.min(x+dx, xLimit);
        y = Math.min(y+dy, yLimit);
    }

    //overloading
    void move(BoundedPoint bp){x=p.x; y=bp.y;}

    //overloading does not care about the return type
    BoundedPoint move(Point p){return this;}

    /* This would be override equivalent to move in Point,
       but return types are not equivalent. Will cause 
       compilation error.
    Point move(int x, int y){return this;} */
}

4 Method Invocation

The following is a simplified picture of what happens when you invoke a Java object's method. In presence of overloading and overriding, how is the "correct" method picked?

4.1 Compile time checks

  1. Determine which class to search. For most instance method calls, the class to be called is the declared type of the class.
class Point{
  int x;
  public int getX(){return x;}
  public void setX(int x){this.x = x;}
}

class ColoredPoint extends Point{
  protected int color;
  public int getColor(){return color;}
  public void setColor(int c){this.color = c;}
}


// Usage - Suppose the code in main is:
Point p = new ColorPoint();
ColorPoint cp = new ColorPoint();
cp.getColor(); //ok
p.getColor();  //Compiler error. Declared type Point does not have 
               //getColor, even though runtime type of p has it.

This is different in dynamically typed languages, since method resolution and dispatch happens completely at runtime.

  1. Determine method signature. Use name of the method and types of the arguments to find methods which are both applicable and accessible. There may be more than one such method. The most specific method is chosen.
    1. A method declaration is applicable if
      • The number of parameters match
      • The type of the argument can be converted by method invocation conversion to the type of the corresponding parameter.
      class Animal{} class Lion extends Animal{}
      class Forest{
        public bool canSupport(Animal a){return false;}
      }
      
      //Usage: suppose the code inside main is:
      Lion lion = new Lion();
      Forest f = new Forest();
      f.canSupport(lion); //ok since lion can be converted to animal.
      
    2. A method declaration is accessible if it has the appropriate visibility.
    3. Choose the most specific method.

      Suppose C.m(T1, T2, …, Tn) and D.m(S1, S2, …, Sn) are both applicable and accessible. Then C.m is more specific if

      • C can be converted to D by method invocation converstion.
      • Each Tj can be converted to Sj by method invocation conversion.

      A method is maximally specific if there is a unique such most specific method. If there is none, then a compiler error results.

      //(contd.)
      class Savannah extends Forest{
        public bool canSupport(Animal a){return false;}
        public bool canSupport(Lion l){return true;}
      }
      
      //Usage: Suppose the code in main is:
      Savannah s = new Savannah();
      Lion lion = new Lion();
      s.canSupport(lion); //will return true. (Most specific method.)
      

      An example where there is no method which is most specific.

      //This class declaration does not cause a compiler error.
      class Savannah{
        public bool isPredatorOf(Animal a, Lion l){}
        public bool isPredatorOf(Lion l, Animan a){}
      }
      
      //Usage: 
      Lion lion1 = new Lion(), lion2 = new Lion();
      Savannah s = new Savannah();
      //Are lions cannibalistic?
      s.isPredatorOf(lion1, lion2);  //will cause compilation error
      

4.2 Runtime

Author: Satyadev Nandakumar <satyadev@cse.iitk.ac.in>

Date: 2011-11-04 14:28:38 IST

HTML generated by org-mode 6.33x in emacs 23