Saturday, August 2, 2014

Java's Method Overloading vs Method Overriding

As a Java developer its important to understand the difference between method overloading and method overriding. Overriding occurs when you change the implementation of a method that was inherited from a parent class. Overloading, on the other hand, occurs when you have multiple methods in a class that have the same name.

Recall that the syntax for declaring a method in Java looks like this:

[access modifier][return type][method's name] ([argument list]) [exceptions thrown]

Let's look at each one of these parts, try and change them, and see if there's a difference in behavior when the method is defined in the same class versus when the method is inherited.

Differing Access Modifiers

So the first part we'll look at is the access modifier. Whether the method is in the same class or is inherited, you can't make the scope of a method more narrow than what it already was.
public class Klass {

   public void fooBar() {
   }

   private void fooBar() {
   }
}
    
In this case trying to narrow the scope of a method will result in a compiler error similar to the following...
'fooBar()' is already defined in 'Klass'
Unlike the above example, method overriding does allow you to change the scope of a method. You just can't make the scope narrower. You have to make it wider. For instance, trying to go from public to private...
public class Parent {
   public void fooBar() {
   }
}
public class Child extends Parent {
   private void fooBar() {
   }
}
...will result in the following error message.
'fooBar()' in 'Child' clashes with 'fooBar()' in 'Parent'; 
attempting to assign weaker access privileges ('private');
  was public
However, if the Parent's fooBar method had been declared as protected...
public class Parent {
   protected void fooBar() {
      System.out.println("Parent");
   }

   public static void main(String[] args) {
      new Parent().fooBar(); // Parent
   }
}
...we could have then broaden the access to public in the Child class, like so.
public class Child extends Parent {
   public void fooBar()  {
      System.out.println("Child");
   }

   public static void main(String[] args) {
      new Child().fooBar();  // Child
   }
}
I added a main method to both the Parent and Child class just to prove that we are indeed overriding the fooBar method.

Differing Return Types

In both cases, differing just the return type is only going to get you a compiler error. The error message you'll receive, however, is going to depend on what technique you use. So let's look at them.

When the two methods are defined in the same class but differ only in their return type...
public class Klass {

   public void fooBar() {
   }

   public Object fooBar() {
      return new Object();
   }
}
...this will result in the following compiler error.
'fooBar()' is already defined in 'Klass'
When inheritance is used...
public class Parent {
   public void fooBar() {
   }
}
public class Child extends Parent {
   public Object fooBar() {
      return new Object();
   }
}
...you'll receive a different compiler error.
'fooBar()' in 'Child' clashes with 'fooBar()' in 'Parent';
  attempting to use incompatible return type

Differing Parameter Types

When the method is in the same class...
public class Klass {

   public void fooBar(int x) {
      System.out.println("x = " + x);
   }

   public void fooBar(boolean flag) {
      System.out.println("flag = " + flag);
   }

   public static void main(String[] args) {
      Klass klass = new Klass();

      klass.fooBar(27);   // x = 27
      klass.fooBar(true); // flag = true
   }
}
...and when inheritance is used.
public class Parent {

   public void fooBar(int x) {
      System.out.println("x = " + x);
   }

   public static void main(String[] args) {
      new Parent().fooBar(27); // x = 27
   }
}
public class Child extends Parent {

   public void fooBar(boolean flag) {
      System.out.println("flag = " + flag);
   }

   public static void main(String[] args) {
      Child child = new Child();

      child.fooBar(27);   // x = 27
      child.fooBar(true); // flag = true
   }
}
Notice that the Child class now has two fooBar methods. That means method overloading was used.

Differing Number of Parameters

Same class...
public class Klass {

   public void fooBar(int x) {
      System.out.println("x = " + x);
   }

   public void fooBar(int x, int y) {
      System.out.println("x = " + x + ", y = " + y);
   }

   public static void main(String[] args) {
      Klass klass = new Klass();

      klass.fooBar(27);  // x = 27
      klass.fooBar(2,7); // x = 2, y = 7
   }
}
...and when inheritance is used.
public class Parent {

   public void fooBar(int x) {
      System.out.println("x = " + x);
   }

   public static void main(String[] args) {
      new Parent().fooBar(27); // x = 27
   }
}
public class Child extends Parent {

   public void fooBar(int x, int y) {
      System.out.println("x = " + x + ", y = " + y);
   }

   public static void main(String[] args) {
      Child child = new Child();

      child.fooBar(27);   // x = 27
      child.fooBar(2, 7); // x = 2, y = 7
   }
}
Child class has two methods that may be called. So method overloading.

Differing Arrangement of Parameters

When both of the methods are defined in the same class file...
public class Klass {

   public void fooBar(int x, String str) {
      System.out.println("x = " + x + ", str = " + str);
   }

   public void fooBar(String str, int x) {
      System.out.println("str = " + str + ", x = " + x);
   }

   public static void main(String[] args) {
      Klass klass = new Klass();

      klass.fooBar(27, "Java"); // x = 27, str = Java
      klass.fooBar("Java", 27); // str = Java, x = 27
   }
}
When inheritance is used...
public class Parent {

   public void fooBar(int x, String str) {
      System.out.println("x = " + x + ", str = " + str);
   }

   public static void main(String[] args) {
      new Parent().fooBar(27, "Java"); // x = 27, str = Java
   }
}
public class Child extends Parent {

   public void fooBar(String str, int x) {
      System.out.println("str = " + str + ", x = " + x);
   }

   public static void main(String[] args) {
      Child child = new Child();

      child.fooBar(27, "Java"); // x = 27, str = Java
      child.fooBar("Java", 27); // str = Java, x = 27
   }
}
Child class has two fooBar methods. Therefore, method overloading was used.

Differing The Type Of Exception That Gets Thrown

When the two methods are defined in the same class file and you try to do this...
import java.io.IOException;
import java.sql.SQLException;
import java.util.TooManyListenersException;

public class Klass {

   public void fooBar() throws IOException {
   }

   public void fooBar() throws SQLException {
   }
}
...you will receive the following error message.
'fooBar()' is already defined in 'Klass'
Similarly, when inheritance is used...
import java.io.IOException;
import java.util.TooManyListenersException;

public class Parent {

   public void fooBar() throws IOException {
   }
}
import java.io.IOException;
import java.sql.SQLException;

public class Child extends Parent {

   public void fooBar() throws SQLException {
   }
}
...it too will cause an error message upon compiling.
'fooBar()' in 'Child' clashes with 'fooBar()' in 'Parent';
  overridden method does not throw 'java.sql.SQLException'
Note: SQLException is not a subtype of IOException. If Child's fooBar method had thrown a FileNotFoundException instead than this would have worked. FileNotFoundException extends IOException.

Differing The Number Of Exceptions That Get Thrown

When the two methods are defined in the same class file and you try to do this...
import java.io.IOException;
import java.sql.SQLException;
import java.util.TooManyListenersException;

public class Klass {

   public void fooBar() throws IOException {
   }

   public void fooBar() {
   }
}
'fooBar()' is already defined in 'Klass'
It is, however, allowed when inheritance is used...
import java.io.IOException;

public class Parent {

   public void fooBar() throws IOException {
      System.out.println("Parent");
   }
}
public class Child extends Parent {

   public void fooBar() {
      System.out.println("Child");
   }

   public static void main(String[] args) {
      new Child().fooBar(); // Child
   }
}
In this case this is an example of method overriding as the Child class has only one fooBar method.

According to Cay Horstmann's book Big Java: "You cannot add types to the list of exceptions in the overridden method, but you can remove types and make the throws clause more restrictive." (pg 225)

Summary

  • Differing Access Modifiers
    • method overriding only works
      • when inheritance is used
      • when widening the access to the method
    • Child override Parent, so method overriding
  • Differing Return Types
    • results in a compiler error
  • Differing Parameter Types
    • multiple fooBar methods, so method overloading
  • Differing Number of Parameters
    • multiple fooBar methods, so method overloading
  • Differing Arrangement of Parameters
    • multiple fooBar methods, so method overloading
  • Differing The Type Of Exception That Gets Thrown
    • method overriding only works
      • when inheritance is used
      • using same exception type as parent
  • Differing The Number of Exceptions that Get Thrown
    • method overriding only works
      • when inheritance is used
      • removing the number of exceptions that get throw

Reference

Java Programming : Advanced Topics (first edition) by Joe wigglesworth and Paula McMillan, pages 165, 166, 223, and 225.

1 comment: