Monday, 27 October 2014

Overriding and Overloading


You can not override a static method or an instance variable (their value is always defined from the reference type).


Overriding a method


To override a method, the following rules must be followed:
  • a final method can not be overridden,
  • a static method can not be overridden but can be "hidden", more info click here,
  • the name of the method must match,
  • the argument list must match with the ones declared in the overridden method (no polymorphism),
  • the return type must be the same type or a subtype (covariant returns) than the overridden method's return type (you can make sure with a IS-A test),
  • the access level can not be more restrictive; it can be less restrictive,
  • an overriding method can throw new unchecked exceptions,
  • the overriding method can also declare new checked exception(s) if they are the same subtype (not broader) than the exception(s) that the overridden method throws,
  • if the overridden method throws checked exceptions then the overriding method may avoid declaring them,
  • a reference variable calls the overridden method of its object type and not reference type.

 class Animal {
    public void eat(int quantityFood){
       System.out.println("'Animal' version of 'eat()''");
    }
 }

 class Cat extends Animal {
    // Override the method eat()
    public void eat(int quantityFood){
       System.out.println("'Cat' version of 'eat()''");
    }
 }

 // Runs 'Animal' version of "eat()"
 Animal a = new Animal();
 a.eat(10);

 // Runs 'Cat' version of "eat()"
 Cat c = new Cat();
 c.eat(10);

 // Runs 'Cat' version of "eat()"
 // as the object in the heap is a Cat
 Animal ac = new Cat();
 ac.eat(10);

The example above shows that a reference variable always try to call an overridden method from an interface which match with its object type and not with its reference type.


If you decide to not declare the overridden method's checked exceptions then take in consideration this example:
 class Animal {
    public void eat(int quantityFood) throws Exception {
 }
 }

 class Cat extends Animal {
    // Override the method eat()
    // without declaring the Exception
    public void eat(int quantityFood){
    }
 }

 Cat c = new Cat();

 // Ok, this works
 c.eat();

 // upcast of Cat to Animal
 Animal ac = new Cat();

 // compilation error: " unreported exception
 // java.lang.Exception; must be caught or
 // declared to be thrown"
 ac.eat();

In the example above, in "ac.eat();" the compiler tries to call the method "eat()" of the class "Cat" with using the interface of the class "Animal". Therefore, the compiler expects to see a catch Exception.

This modification will work:
 ...
 try{
    ac.eat();
 }catch(Exception ex){
    // Do something
 }

Finally an overriding method is called in function of the object to which a reference variable refers to:
 // implicit "upcast" of Cat to Animal
 Animal ac = new Cat();

 // Runs 'Cat' version of "eat()"
 // as the object in the heap is a Cat
 ac.eat();


Overloading a method


An overloaded method must have:
  • the same name than the parent class method to overload
  • a different list of argument than the parent class method to overload

Except the rules above, an overloading method is free to have any return type and exception declaration. Indeed, unlike an overridden method, an overloading method is seen by the compiler as a different method from the overloaded method.

Differences between overloaded and overridden methods:


Overloaded method
Overridden method
Argument(s)
Must change.
Must not change.
Return type
No constraint.
Can not change, except for covariant returns.
Exceptions
No constraint.
Can reduce or eliminate checked exceptions. Must not throw new or broader checked exceptions.
Can declare any amount of unchecked exceptions.
Access
No constraint.
Must not make more restrictive but can make less restrictive.
Invocation
Reference type determines which overloaded version is selected.
Happens at compile time. The actual method that's invoked is still a virtual method invocation that happens at runtime, but the compiler will already know the signature of the method to be invoked. So at runtime, the argument match will already have been nailed down, just not the class in which the method lives.
Object type, in other words, the type of the actual instance on the heap, determines which method is selected.
Happens at runtime.


Overloading and widening, auto-boxing and var-args:
  • widening beats auto-boxing
  • widening beats var-args
  • auto-boxing beats var-args

Eg1.
 // Widening against boxing
 class WideningAgainstBoxing {

    // With a wrapper parameter
    static void go (Integer x) {
       System.out.println("Integer");
    }

    // With a primitive parameter
    static void go (long x) {
       System.out.println("long");
    }

   public static void main(String [] args){
      int i = 5;

      go(i);

   }
 }

The execution of this class will output: "long"


Eg2.
 // Widening against var-args
 class WideningAgainstVarArgs {

    // With a var-args parameter
    static void go (byte... x) {
       System.out.println("byte... ");
    }

    // With primitive parameters
    static void go (int x, int y){
       System.out.println("int, int");
    }

    public static void main(String [] args){
       byte b = 5;
       go(b, b);
    }

 }

The execution of this class will output: "int, int"


Eg2.
 // Boxing against var-args
 class BoxingAgainstVarArgs {

    // With a var-args parameter
    static void go (byte... x) {
       System.out.println("byte... ");
    }

    // With wrapper parameters
    static void go (Byte x, Byte y) {
       System.out.println("Byte, Byte");
    }

    public static void main(String [] args){
       byte b = 5;
       go(b, b);
    }

 }

The execution of this class will output: "Byte, Byte"