Core Java

Runtime Method Overriding in an Instantiated Java Object

Method overriding in Java allows a subclass to redefine a method inherited from its superclass. However, there are many real-world cases where you want to modify or override behavior after an object has been instantiated — such as for logging, security checks, caching, or changing runtime logic dynamically. While Java doesn’t directly allow you to reassign a method implementation in an already-created object, it provides several techniques to achieve similar results. Let us delve into how to implement overriding methods of an instantiated object in Java and modify its behavior dynamically at runtime.

1. Subclassing

Subclassing is the most direct way to override behavior. You create a derived class and redefine the desired method. Then, at runtime, instantiate the subclass instead of the parent.

class Vehicle {
    void start() {
        System.out.println("Vehicle is starting...");
    }
}

class Car extends Vehicle {
    @Override
    void start() {
        System.out.println("Car is starting with keyless ignition!");
    }
}

public class SubclassDemo {
    public static void main(String[] args) {
        Vehicle myVehicle = new Car();
        myVehicle.start(); // Calls overridden method from Car
    }
}

1.1 Code Explanation and Output

In this Java program, the concept of method overriding is demonstrated using a base class Vehicle and a subclass Car. The Vehicle class defines a start() method that prints a generic message, while the Car class overrides this method to provide a specific implementation — indicating that the car starts with keyless ignition. Inside the main() method of SubclassDemo, an instance of Car is created but referenced through a Vehicle type. When myVehicle.start() is invoked, Java’s runtime polymorphism ensures that the overridden method in Car is executed instead of the one in Vehicle. This demonstrates how subclass behavior overrides parent class methods even when accessed through a parent reference.

Car is starting with keyless ignition!

2. Using Anonymous Inner Classes

An anonymous inner class allows you to create a subclass of an existing class inline, overriding its methods immediately. It’s ideal when you only need to change behavior once or within a limited scope.

class Greeting {
    void sayHello() {
        System.out.println("Hello from the original Greeting class!");
    }
}

public class AnonymousOverrideDemo {
    public static void main(String[] args) {
        Greeting greeting = new Greeting() {
            @Override
            void sayHello() {
                System.out.println("Hello from the overridden anonymous class!");
            }
        };
        greeting.sayHello();
    }
}

2.1 Code Explanation and Output

In this Java program, an example of runtime method overriding using an anonymous inner class is shown. The Greeting class defines a method sayHello() that prints a default message. In the main() method, instead of creating a regular instance of Greeting, an anonymous subclass is defined and instantiated simultaneously. This subclass overrides the sayHello() method to print a different message. When greeting.sayHello() is invoked, Java executes the overridden method in the anonymous class rather than the original one in Greeting, demonstrating dynamic method overriding without explicitly defining a named subclass.

Hello from the overridden anonymous class!

3. Using the Decorator Pattern

The Decorator Pattern is a structural design pattern that wraps an existing object to extend or modify its behavior dynamically, without changing the original class.

interface Message {
    String getContent();
}

class SimpleMessage implements Message {
    public String getContent() {
        return "Hello World";
    }
}

class EncryptedMessage implements Message {
    private Message message;

    EncryptedMessage(Message message) {
        this.message = message;
    }

    public String getContent() {
        return encrypt(message.getContent());
    }

    private String encrypt(String text) {
        return new StringBuilder(text).reverse().toString(); // simple reversal encryption
    }
}

public class DecoratorDemo {
    public static void main(String[] args) {
        Message msg = new SimpleMessage();
        System.out.println("Original: " + msg.getContent());

        Message encrypted = new EncryptedMessage(msg);
        System.out.println("Decorated: " + encrypted.getContent());
    }
}

3.1 Code Explanation and Output

In this Java program, an interface Message defines a method getContent() to represent a message’s content. The SimpleMessage class implements this interface and returns a plain text “Hello World”. The EncryptedMessage class also implements Message but wraps another Message object, adding extra behavior through composition — this demonstrates the Decorator Pattern. Its getContent() method calls encrypt(), which reverses the text using a simple string reversal as a form of encryption. In the DecoratorDemo class, the main method first creates a SimpleMessage instance and prints its plain content. Then, it wraps that object inside EncryptedMessage, showing how the same message can be dynamically enhanced at runtime to produce an encrypted output without modifying the original SimpleMessage class.

Original: Hello World
Decorated: dlroW olleH

4. Using JDK Dynamic Proxy

Java’s java.lang.reflect.Proxy enables runtime interception of method calls on interfaces. This is often used in frameworks like Spring for adding cross-cutting concerns such as logging or transaction management.

import java.lang.reflect.*;

interface Service {
    void performTask();
}

class RealService implements Service {
    public void performTask() {
        System.out.println("Performing real task...");
    }
}

public class DynamicProxyDemo {
    public static void main(String[] args) {
        Service realService = new RealService();

        Service proxy = (Service) Proxy.newProxyInstance(
            RealService.class.getClassLoader(),
            new Class[]{Service.class},
            new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("Before method call...");
                    Object result = method.invoke(realService, args);
                    System.out.println("After method call...");
                    return result;
                }
            }
        );

        proxy.performTask();
    }
}

4.1 Code Explanation and Output

This Java program demonstrates the use of a JDK Dynamic Proxy to modify the behavior of an object at runtime. The Service interface defines a single method performTask(), which is implemented by the RealService class to print a simple message. In the DynamicProxyDemo class, an instance of RealService is wrapped inside a dynamically created proxy object using the Proxy.newProxyInstance() method. The proxy intercepts method calls to the real object through an InvocationHandler, allowing custom actions to be executed before and after invoking the actual method. In this example, when proxy.performTask() is called, the program prints “Before method call…”, executes the real service’s task, and then prints “After method call…”. This approach enables adding cross-cutting concerns like logging, security checks, or transaction handling without changing the original class implementation.

Before method call...
Performing real task...
After method call...

5. Using Spring’s ProxyFactory

Spring provides org.springframework.aop.framework.ProxyFactory for creating proxies that can override or wrap methods at runtime — typically used in AOP (Aspect-Oriented Programming).

import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.framework.ProxyFactory;
import java.lang.reflect.Method;

interface PaymentService {
    void processPayment();
}

class RealPaymentService implements PaymentService {
    public void processPayment() {
        System.out.println("Processing payment...");
    }
}

public class SpringProxyDemo {
    public static void main(String[] args) {
        RealPaymentService target = new RealPaymentService();

        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTarget(target);
        proxyFactory.addAdvice((MethodBeforeAdvice) (method, args1, targetObj) ->
                System.out.println("Before " + method.getName() + " - Validating payment..."));

        PaymentService proxy = (PaymentService) proxyFactory.getProxy();
        proxy.processPayment();
    }
}

5.1 Code Explanation and Output

This Java program illustrates how to use Spring’s ProxyFactory to dynamically add behavior to an existing object without modifying its code. The PaymentService interface defines a method processPayment(), implemented by RealPaymentService to simulate payment processing. In the SpringProxyDemo class, a ProxyFactory is created and configured with the target object (RealPaymentService). A MethodBeforeAdvice is added to define logic that executes before any target method runs — here, it prints a validation message before invoking processPayment(). The proxy object returned by proxyFactory.getProxy() acts as a wrapper around the real service. When proxy.processPayment() is called, the advice runs first to simulate a pre-processing step, followed by the actual method execution. This demonstrates how Spring AOP (Aspect-Oriented Programming) can be used to implement cross-cutting concerns like validation, logging, or authorization in a clean, modular way.

Before processPayment - Validating payment...
Processing payment...

6. Comparison

TechniqueUse CaseRequires Interface?Runtime Override?
SubclassingCompile-time extension of class behaviorNoNo
Anonymous Inner ClassInline method override at object creationNoPartial
Decorator PatternAdd new behavior dynamically via wrappingYes (preferred)Yes
JDK Dynamic ProxyIntercept calls on interfaces at runtimeYesYes
Spring ProxyFactoryAdvanced runtime proxy for AOP behaviorsYesYes

7. Conclusion

In conclusion, while traditional method overriding in Java happens at compile time through subclassing, there are multiple advanced techniques to modify or extend an instantiated object’s behavior at runtime. Different approaches provide developers with powerful tools to enhance objects dynamically without altering their original source code. Each technique offers varying levels of flexibility and complexity — from simple inline overrides to sophisticated AOP-based method interception. Understanding how Java overriding method instantiated object concepts interact helps developers design more modular, reusable, and adaptable applications that can evolve with changing runtime requirements.

Yatin Batra

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button