Overloading and Overriding in Java

Polymorphism can be of 2 forms

  1. Static
  2. Dynamic

When different form of a single entity are resolved at compile time (early binding) such polymorphism is called static polymorphism. When different form of a single entity are resolved during runtime (late binding) such polymorphism is called dynamic polymorphism.

Overloading is an example of static polymorphism.

ITCuties - Overloading and Overriding in Java

ITCuties – Overloading and Overriding in Java

Method overloading

In a class, how many methods can you define with the same name? Many! In Java, you can define multiple method with the same name, provided the argument list differ from each other.
In other words, if you provide different types of arguments, different number of arguments, or both, then you can define multiple methods with the same name. This is called method overloading.

The rules are simple:

  • Overloaded method must change the argument list.
  • Overloaded method can change the return type.
  • Overloaded method can change the access modifier.
  • Overloaded method can declare new or broader checked exception
  • A method can be overloaded in the same class or in a subclass. In other words, if class A define a go(int i) method, then class B could define go(String s) method without overriding the superclass that takes int. So methods with same name but in different class can still be considered overloaded, if the subclass inherits one version of the method and then declares another overloaded version in its class definition.

Legal Overloads

Let’s look at a method we want to overload:

public void registerAtItCuties(int age, String name) { }

The following methods are legal overloads of the changeSize() method

public void registerAtItCuties(int age, String name) { }
public int registerAtItCuties(int age,  Float money) { }
public void registerAtItCuties(String name,  Float money) throws IOException { }

Example

package com.javalatte.itcuties.overloadingoverriding;

import java.io.IOException;

public class MethodOverloading {

    public static void main(String[] JavaLatte) {
        MethodOverloading obj = new MethodOverloading();
        obj.registerAtItCuties(30, "Charlie", 99999.00F);

        try {
            obj.registerAtItCuties("Charlie", 55555F);
        } catch (IOException e) {
        }

        obj.registerAtItCuties(30, "Charlie");

        obj.registerAtItCuties(31, 111F);
    }

    public void registerAtItCuties(int age, String name, float money) {
        System.out.println("Register with " + name + " Age: " + age + " Money : " + money);
    }

    public void registerAtItCuties(int age, String name) {
        System.out.println("Register with " + name + " Age: " + age);
    }

    public int registerAtItCuties(int age, float money) {
        System.out.println("Register with Age: " + age + " Money : " + money);
        return 0;
    }

    public void registerAtItCuties(String name, Float money) throws IOException {
        System.out.println("Register with Name: " + name + " Money : " + money);
    }

}

Constructor Overloading

A default constructor is useful for creating object with a default initialization value.When you want to initialize the objects with different arguments in different instantiations, you can pass them as the arguments to constructors. This is called constructor overloading.

Example

package com.javalatte.itcuties.overloadingoverriding;

class Student {

    private final int age;
    private final String name;
    private final long id;

    public Student() {
        this.age = 0;
        this.name = "unknown";
        this.id = 0;
    }

    Student(String name) {
        this.name = name;
        this.age = 18;//default value
        this.id = 0; //default value
    }

    Student(String name, int age) {
        this.name = name;
        this.age = age;
        this.id = 0;// default value
    }

    Student(String name, int age, long id) {
        this.name = name;
        this.age = age;
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public long getId() {
        return id;
    }

    public String getName() {
        return name;
    }
}

public class ConstructorOverloading {

    public static void main(String[] JavaLatte) {
        Student s1 = new Student();
        System.out.println("Name: " + s1.getAge() + " Age: " + s1.getAge() + " Id: " + s1.getId());

        Student s2 = new Student("Charlie");
        System.out.println("Name: " + s2.getAge() + " Age: " + s2.getAge() + " Id: " + s2.getId());

        Student s3 = new Student("Charlie", 32);
        System.out.println("Name: " + s3.getAge() + " Age: " + s3.getAge() + " Id: " + s3.getId());

        Student s4 = new Student("Charlie", 32, 122);
        System.out.println("Name: " + s4.getAge() + " Age: " + s4.getAge() + " Id: " + s4.getId());
    }
	
}

Invoking Overloaded Methods

When you define overloaded methods, how does the compiler know which method to call? Can you guess the output of the code?

public static void testMethod (int val)
public static void testMethod (short val)
public static void testMethod (Object val)
public static void testMethod (String val )

public static void main(String[] args) {
byte b = 9;
testMethod(b); // first call
testMethod(9); // second call
Integer i = 9;
testMethod(i); // third call
testMethod("9"); // fourth call
}

Here is how the compiler resolved these calls

  • In the first method call, the statement is testMethod(b) where the variable b is of type byte. There is no testMethod definition that takes byte as an argument. The closest type (in size) is short type and not int, so the compiler resolves the call testMethod(b) to testMethod(short val) definition
  • In the second method call, the statement is testMethod(9). The constant value 9 is of type int. The closest match is testMethod(int), so the compiler resolves the call testMethod(9) to testMethod(int val) definition.
  • The third method call is testMethod(i), where the variable i is of type Integer. There is no testMethod definition that takes Integer as an argument. The closest match is testMethod(Object val), so it is called. Why not testMethod(int val)? For finding the closest match, the compiler allows implicit upcasts, not downcasts, so testMethod(int val) is not considered
  • The last method call is testMethod("9"). The argument is a String type. Since there is an exact match, testMethod(String val) is called.

For resolving a method call, it first looks for the exact match – the method definition with exactly same number of parameters and types of parameters. If it can’t find an exact match, it looks for the closest match by using upcasts. If the compiler can’t find any match, then you’ll get a compiler error.

Example

package com.javalatte.itcuties.overloadingoverriding;

public class InvokingOverloadedMethod {

    public static void main(String[] JavaLatte) {
        byte b = 9;
        testMethod(b); // first call
        testMethod(9); // second call
        Integer i = 9;
        testMethod(i); // third call
        testMethod("9"); // fourth call

    }

    public static void testMethod(int val) {
        System.out.println("int");
    }

    public static void testMethod(short val) {
        System.out.println("short");
    }

    public static void testMethod(Object val) {
        System.out.println("object");
    }

    public static void testMethod(String val) {
        System.out.println("String");
    }
}

Here are some interesting rules regarding method overloading

  • You cannot overload methods with the methods differing in return types alone.
  • You cannot overload methods with the methods differing in exception specifications alone.
  • For overload resolution to succeed, you need to define methods such that the compiler finds one exact match. If the compiler finds no matches for your call or if the matching is ambiguous, the overload resolution fails and the compiler issues an error.

Overloading is an example of dynamic/runtime polymorphism

Method Overriding

Any time you have a class that inherits a method from a superclass, you have the opportunity to override the method unless it is marked final. The key benefit of overriding is the ability to define behaviour that’s specific to a particular class.
Overriding i.e, runtime polymorphism is a simple yet powerful idea for extending functionality.

For abstract methods you inherit from a superclass, you have no choice. You must implement the method in the subclass unless the subclass is also abstract. Abstract methods must be implemented by the concrete subclass, but this is a lot like saying that the concrete subclass overrides the abstract methods of the superclass. So you could think of abstract methods as methods you’re forced to override.

The rules are simple:

  • The argument list must exactly match that of the overridden method. If they don’t match, you can end up with an overloaded method you didn’t intend.
  • The return type must be the same as, or a subtype of, the return type declared in the original overridden method in the superclass.
  • The access level can’t be more restrictive than the overridden method’s.
  • The access level CAN be less restrictive than that of the overridden method.
  • Instance methods can be overridden only if they are inherited by the subclass. A subclass within the same package as the instance’s superclass can override any superclass method that is not marked private or final. A subclass in a different package can override only those non-final methods marked public or protected.
  • The overriding method CAN throw any unchecked (runtime) exception, regardless of whether the overridden method declares the exception
  • The overriding method must NOT throw checked exceptions that are new or broader than those declared by the overridden method.
  • You cannot override a method marked final.
  • You cannot override a method marked static.
  • If a method can’t be inherited, you cannot override it.

Example

package com.javalatte.itcuties.overloadingoverriding;

class Animal {

    public void eat() {
        System.out.println("Generic eating method");
    }

    public void run() {
        System.out.println("Generic run");
    }
}

class Horse extends Animal {

    @Override
    public void eat() {
        System.out.println("Horse eating method");
    }

    //You cannot have more restrictive access modifer than the method being
    //overriden
    //private void run(){ }
}

public class MethodOverriding {

    public static void main(String[] JavaLatte) {
        Animal a = new Animal();
        Animal a1 = new Horse();
        a.eat(); // animal eat() 
        a1.eat(); // horse eat()
    }
}

Polymorphically, when someone has an Animal reference that refers not to an Animal instance, but to an Animal subclass instance, the caller should be able to invoke eat() on the Animal reference, but the actual runtime object (say, a Horse instance) will run its own specific eat() method.

To reiterate, the compiler looks only at the reference type, not the instance type. Polymorphism lets you use a more abstract supertype (including an interface) reference to refer to one of its subtypes (including interface implementers).

Invoking a Superclass Version of an Overridden Method

You’ll want to take advantage of some of the code in the superclass version of a method, yet still override it to provide some additional specific behavior.
It’s like saying, “Run the superclass version of the method, then come back down here and finish with my subclass additional method code.”

Example

package com.javalatte.itcuties.overloadingoverriding;

class Mammel {

    public void eat() {
    }

    public void printing() {
        System.out.println("Useful code for printing");
    }
}

class cat extends Mammel {

    @Override
    public void printing() {
        //taking advantage of super class method
        super.printing();
        System.out.println("Useful code for printing for cat Class");
    }
}

public class InvokeOverridenMethod {

    public static void main(String[] JavaLatte) {
        Mammel m = new cat();
        m.printing();
    }
}

Examples of Legal and Illegal Method Overrides

public class Animal {
  public void run() { }
}

private void run() { } : Access modifier is more restrictive
public void run() throws IOException { } : Declares a checked exception not defined by superclass version
public void run(String food) { } : A legal overload, not an override, because the argument list changed
public String run() { } : Not an override because of the return type, not an overload either because there’s no change in the argument list

Difference between Overloaded and Overridden method

  Overloaded Method Overridden Method
Arguments Must change Must not change
Return type Can change Can’t change
Exceptions Can change Can reduce or eliminate.Must not throw new or broader checked exceptions.
Access Can change Must not make more restrictive (can be less restrictive).
Invocation Reference type determines which overloaded version (based on declared argument types) is selected. Happens at compile time. Object type (in other words, the type of the actual instance on the heap) determines which method is selected. Happens at runtime.

Download this sample code here.

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>