Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cast a final class to a compatible interface that the class does not claim to implement

Sometimes in Java, there is a case where you use a library that supplies a final class Car and you wish it implemented some Vehicle interface so that you could make Truck and Bus classes and treat them all as Vehicles in your code. But Car is final and it doesn't implement any interfaces.

How do I cast someone else's final Car class to my Vehicle interface so that I can pass it around like my other Vehicles? Every instance method on Vehicle would be 100% compatible with a similar method on Car in terms of instance method names, argument types, and return types. It would be equivalent from a Duck-Typing perspective.

I know I could make a MyCar extends Vehicle wrapper class that just delegates each method call to an internal Car object. That would be The Java Way. But I'm just wondering if there is a technique to actually cast one class to an unrelated (but 100% compatible) interface. It's OK if the answer is evil.

like image 853
GlenPeterson Avatar asked Jan 07 '23 10:01

GlenPeterson


2 Answers

Do it with a proxy:

import java.lang.reflect.Proxy;

public class DuckTyping {

    static final class Car{
        public void honk(){
            System.out.println("honk");
        }
    }

    interface Vehicle{
        void honk();
    }

    public static void main(String[] a){
        Car c = new Car();
        Vehicle v = (Vehicle) Proxy.newProxyInstance(Vehicle.class.getClassLoader(), new Class[]{Vehicle.class}, (proxy, method, args) -> 
            Car.class.getMethod(method.getName(), method.getParameterTypes()).invoke(c, args)
        );

        v.honk();
    }
}

Generic method:

    static <T> T proxyCast(Object targetObject, Class<T> targetClass) {
        return (T) Proxy.newProxyInstance(targetClass.getClassLoader(), new Class[]{targetClass}, (proxy, method, args) -> 
            targetObject.getClass().getMethod(method.getName(), method.getParameterTypes()).invoke(targetObject, args)
        );
    }
like image 107
AxelW Avatar answered Jan 08 '23 23:01

AxelW


  1. A final modifier in a class was made to not allow changes in its state so it's there's no way to modify the class.

  2. Cast would only work for you if you extend from a parent class or implement to an interface, there's no other way (but you can't because the class is final)

  3. In this case you have three options: 1. Wrapper (you mention). 2. Reflection (you can figure out useful information about the class and its methods in run time). 3. Create your own class.

like image 22
Manuel Pamplona Avatar answered Jan 08 '23 23:01

Manuel Pamplona