Method Overriding in Java

Method overriding is a mechanism in Java that allows a subclass to provide its own implementation of a method that is already defined in its superclass. To override a method, the method in the subclass must have the same name, return type, and parameter list as the method in the superclass.

Here is an example of method overriding in Java:

class Animal {
    public void makeSound() {
        System.out.println("Animal is making a sound");
    }
}

class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Meow");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Animal();
        Cat cat = new Cat();

        animal.makeSound(); // output: Animal is making a sound
        cat.makeSound(); // output: Meow
    }
}

In the above example, the Animal class has a method makeSound(). The Cat class extends the Animal class and overrides the makeSound() method with its own implementation. When we call the makeSound() method on an instance of the Animal class, it executes the makeSound() method defined in the Animal class. However, when we call the makeSound() method on an instance of the Cat class, it executes the makeSound() method defined in the Cat class, which outputs “Meow”.

Usage of Java Method Overriding:

Method overriding is used in Java for several purposes, including:

  1. Polymorphism: Method overriding allows us to achieve polymorphism in Java, which is the ability of an object to take on many forms. By overriding methods in the subclass, we can create objects of the subclass and use them interchangeably with objects of the superclass.
  2. Customization: Method overriding allows us to customize the behavior of a method in the subclass without modifying the code in the superclass. This makes it easier to reuse code and makes our programs more maintainable.
  3. Abstraction: Method overriding allows us to provide a more specific implementation of a method in the subclass. This helps to abstract away the details of the superclass and allows us to work with objects at a higher level of abstraction.
  4. Extensibility: Method overriding allows us to extend the functionality of a class by adding new behavior to it in the subclass. This helps to make our programs more modular and easier to extend.

In summary, method overriding is a powerful feature of Java that allows us to create more flexible and extensible programs. By using method overriding, we can achieve polymorphism, customize behavior, abstract away details, and extend the functionality of our classes.

Rules for Java Method Overriding:

In Java, there are several rules that must be followed when overriding a method in a subclass:

  1. Method Signature: The method in the subclass must have the same method signature (name, return type, and parameter list) as the method in the superclass. If the method signature is different, then it is not considered overriding, but instead, it is considered as a new method in the subclass.
  2. Access Modifier: The access modifier for the overridden method in the subclass must not be more restrictive than the access modifier for the method in the superclass. For example, if the method in the superclass has public access, then the overriding method in the subclass can have either public or protected access but not private access.
  3. Return Type: The return type of the overriding method in the subclass must be the same as, or a subtype of, the return type of the method in the superclass. If the return types are not compatible, then it is considered a compilation error.
  4. Exception: If the overridden method in the superclass declares any checked exception, then the overriding method in the subclass must either declare the same exception or a subclass of it. If the overriding method declares any checked exception that is not declared in the superclass, then it is considered a compilation error.
  5. Final Method: A final method in the superclass cannot be overridden in the subclass. If an attempt is made to override a final method, it will result in a compilation error.
  6. Static Method: A static method in the superclass cannot be overridden in the subclass. If an attempt is made to override a static method, it is considered as a new method in the subclass.
  7. Accessor Method: Overriding an accessor method, such as a getter or setter method, is considered a good practice as it allows subclasses to access superclass fields in a controlled way.

By following these rules, we can safely and correctly override methods in Java subclasses, thereby achieving more flexible and extensible programs.

Understanding the problem without method overriding:

Method overriding is a powerful feature in Java that allows subclasses to provide their own implementation of a method defined in the superclass. Without method overriding, the behavior of a method in the superclass would be fixed and could not be customized or extended in any way by the subclasses.

Consider a scenario where we have a superclass Animal and two subclasses Dog and Cat. The Animal class has a method makeSound() that prints the sound made by the animal. The Dog and Cat classes inherit the makeSound() method from the Animal class. However, dogs and cats make different sounds, so we need to customize the behavior of the makeSound() method in each subclass to print the appropriate sound.

Without method overriding, we would need to define separate methods in each subclass with different names, such as bark() in Dog and meow() in Cat, to achieve the desired behavior. This would lead to code duplication and reduce the maintainability of our programs.

With method overriding, we can define a single method makeSound() in the Animal class and override it in the Dog and Cat classes to provide their own implementations. This leads to cleaner and more maintainable code, as we can reuse the code in the superclass and customize it in the subclasses without duplicating code.

In summary, method overriding is an essential feature in Java that allows us to customize the behavior of methods in subclasses and create more flexible and extensible programs. Without method overriding, we would need to resort to less elegant solutions such as duplicating code or using different method names in subclasses.

Example of method overriding:

Sure, here is an example of method overriding in Java:

public class Animal {
    public void makeSound() {
        System.out.println("The animal makes a sound");
    }
}

public class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("The dog barks");
    }
}

public class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("The cat meows");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal1 = new Animal();
        Animal animal2 = new Dog();
        Animal animal3 = new Cat();

        animal1.makeSound(); // Output: The animal makes a sound
        animal2.makeSound(); // Output: The dog barks
        animal3.makeSound(); // Output: The cat meows
    }
}

In this example, we have a superclass Animal with a method makeSound() that prints a generic message. We also have two subclasses Dog and Cat, which override the makeSound() method to print their respective sounds.

In the Main class, we create objects of Animal, Dog, and Cat and call the makeSound() method on each object. When we call the makeSound() method on the Animal object, it prints the generic message defined in the Animal class. When we call the makeSound() method on the Dog and Cat objects, it prints the overridden message defined in each subclass, demonstrating how method overriding allows us to customize the behavior of methods in subclasses.

A real example of Java Method Overriding:

Here is an example of method overriding in a real-world scenario:

Suppose we have a Vehicle class that represents a generic vehicle with a drive() method that prints a message indicating that the vehicle is being driven. We also have a Car class that extends the Vehicle class and overrides the drive() method to print a more specific message indicating that a car is being driven.

public class Vehicle {
    public void drive() {
        System.out.println("The vehicle is being driven.");
    }
}

public class Car extends Vehicle {
    @Override
    public void drive() {
        System.out.println("The car is being driven.");
    }
}

Now, suppose we have a method driveVehicle() that takes a Vehicle object as a parameter and calls its drive() method. We can pass in a Car object to this method, and since the Car class overrides the drive() method, the message printed will be specific to the Car class.

public class Main {
    public static void main(String[] args) {
        Vehicle vehicle = new Vehicle();
        Car car = new Car();

        driveVehicle(vehicle); // Output: The vehicle is being driven.
        driveVehicle(car); // Output: The car is being driven.
    }

    public static void driveVehicle(Vehicle vehicle) {
        vehicle.drive();
    }
}

In this example, method overriding allows us to define a more specific behavior for a method in a subclass. The Car class inherits the drive() method from the Vehicle class but overrides it to print a more specific message. When we pass a Car object to the driveVehicle() method, the overridden drive() method is called, and the specific message for the Car class is printed.

This demonstrates how method overriding can be used in real-world scenarios to create more flexible and extensible code by allowing subclasses to provide their own implementation of a method defined in the superclass.

Can we override static method?

In Java, static methods can be inherited but not overridden. This means that if a subclass defines a static method with the same name and signature as a static method in the superclass, it does not override the method but rather hides it.

When a static method is invoked on an object, the method that is invoked is determined at compile-time based on the type of the reference variable, not the type of the actual object. This means that if a subclass defines a static method with the same name and signature as a static method in the superclass, the subclass method will be called only if it is invoked using a reference variable of the subclass type. If it is invoked using a reference variable of the superclass type, the superclass method will be called instead.

Here’s an example to illustrate this:

public class Superclass {
    public static void staticMethod() {
        System.out.println("This is a static method in Superclass");
    }
}

public class Subclass extends Superclass {
    public static void staticMethod() {
        System.out.println("This is a static method in Subclass");
    }
}

public class Main {
    public static void main(String[] args) {
        Superclass obj1 = new Superclass();
        Subclass obj2 = new Subclass();

        obj1.staticMethod(); // Output: This is a static method in Superclass
        obj2.staticMethod(); // Output: This is a static method in Subclass
    }
}

In this example, we have a Superclass with a static method staticMethod(), and a Subclass that extends Superclass and defines a static method with the same name and signature. When we create objects of Superclass and Subclass and call the staticMethod() on each object, we can see that the method called is determined at compile-time based on the type of the reference variable. When we call the method on the Superclass object, the Superclass method is called, and when we call the method on the Subclass object, the Subclass method is called.

Therefore, while it is possible to define a static method with the same name and signature in a subclass as in the superclass, it does not result in method overriding but rather method hiding, and the method called is determined at compile-time based on the type of the reference variable.

Difference between method Overloading and Method Overriding in java:

Method overloading and method overriding are two different concepts in Java. The key differences between method overloading and method overriding are:

  1. Definition: Method overloading is defining multiple methods in a class with the same name but with different parameters. Method overriding is defining a method in a subclass with the same name, return type, and parameter list as a method in the superclass.
  2. Execution: Method overloading is resolved at compile-time based on the number and types of arguments passed to the method. Method overriding is resolved at runtime based on the type of the object on which the method is called.
  3. Purpose: Method overloading is used to provide multiple ways of invoking a method with different parameter types or numbers. Method overriding is used to provide a specific implementation of a method in a subclass that is different from the implementation in the superclass.
  4. Inheritance: Method overloading can be done in the same class or in a subclass. Method overriding can only be done in a subclass that extends a superclass.
  5. Access modifiers: Method overloading can have different access modifiers, whereas method overriding must have the same or more accessible access modifiers.

Here’s an example to illustrate the difference between method overloading and method overriding:

public class MyClass {
    public void myMethod(int num) {
        System.out.println("This is myMethod with int parameter: " + num);
    }

    public void myMethod(String str) {
        System.out.println("This is myMethod with String parameter: " + str);
    }
}

public class MySubClass extends MyClass {
    @Override
    public void myMethod(int num) {
        System.out.println("This is myMethod in MySubClass with int parameter: " + num);
    }
}

public class Main {
    public static void main(String[] args) {
        MyClass obj1 = new MyClass();
        MySubClass obj2 = new MySubClass();

        obj1.myMethod(123); // Output: This is myMethod with int parameter: 123
        obj1.myMethod("hello"); // Output: This is myMethod with String parameter: hello
        obj2.myMethod(123); // Output: This is myMethod in MySubClass with int parameter: 123
    }
}

In this example, we have a MyClass with two methods called myMethod(), one that takes an int parameter and another that takes a String parameter. We also have a MySubClass that extends MyClass and overrides the myMethod() method that takes an int parameter. When we create objects of MyClass and MySubClass and call the myMethod() method on each object, we can see that the method called is determined at compile-time for method overloading and at runtime for method overriding. When we call the method on the MySubClass object, the overridden myMethod() method in the MySubClass is called with the same parameter list as the method in the superclass.