简体   繁体   中英

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.

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)
        );
    }
  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.

The easiest way to do this is with JDK Proxies:

public class DuckTyper {

    public static <T> T quack(Object target, Class<T> duck) {

        return (T) Proxy.newProxyInstance(duck.getClassLoader(), new Class[] { duck }, new DuckCostume(target));

    }

    private static class DuckCostume implements InvocationHandler {

         private final Object target;

         public DuckCostume(Object target) {

             this.target = target;

         }

         @Override
         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

             Method targetMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes());
             return targetMethod.invoke(target, args);
         }

    }

}

(There are a bunch of unhandled exceptions and none of the reflection is cached, so it'll be slow until you do that, but this should give you a starting point)

After this, you can duck-type your car like this:

Vehicle carVehicle = DuckTyper.quack(car, Vehicle.class);

I would personally, as you mentioned, create a wrapper class for it.
Maybe a reference to the Car interface could help?

public abstract class MyCar implements Vehicle {

    private Car car;

    (...)
}

There aren't very much ways to do this, and I am not sure my way is what you want nor if it's fully valid.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM