Can you cast an object to an interface it doesn't explicitly implement?

Viewed 115

I'm specifically interested in implementing a Strategy pattern where one (or more) of the strategies come from a third-party library.

I have a third-party library with a class like so:

public class LibraryClass {
    public void doSomething() {
        // ...
    }
}

I then created my own class with the same interface:

public class MyClass {
    public void doSomething() {
        // ...
    }
}

Now in my code I want to do the following:

public class MainActivity extends AppCompatActivity {
    public interface Strategy {
        void doSomething();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Strategy oneClass = (Strategy)(new MyClass());
        Strategy twoClass = (Strategy)(new LibraryClass());
        oneClass.doSomething();
        twoClass.doSomething();
    }
}

In this case the method exists, so the interface is technically valid, but it wasn't explicitly implemented. When I tried doing this, it threw a run-time error:

java.lang.ClassCastException: MyClass cannot be cast to MainActivity$Controls

Worst-case scenario I know I can use a Proxy pattern to wrap LibraryClass in something that does implement the Strategy interface:

public class LibraryClassProxy implements Strategy {
    private LibraryClass internal;
    public void LibraryClassProxy() {
        internal = new LibraryClass();
    }
    public void doSomething() {
        internal.doSomething();
    }
}

But I'd prefer to avoid this if possible - in particular because the class I'm extending implements a lot of other interfaces, and I'd rather not have to proxy every single public method.

3 Answers

From Java Docs:

When you define a new interface, you are defining a new reference data type. You can use interface names anywhere you can use any other data type name. If you define a reference variable whose type is an interface, any object you assign to it must be an instance of a class that implements the interface.

So, you can't cast an object to an interface that is not explicitly implemented

What you want to achieve is basically a form of duck typing: You have an interface with a doSomething method, and you want to be able to pass off anything with that method as an implementation of that interface, even if it does not explicitly specify that it implements the interface.

You can't do that kind of duck typing in Java; all type relationships have to be explicit. But with lambdas and SAM conversion, you can do something that is pretty close to that.

public class MainActivity extends AppCompatActivity {
    public interface Strategy {
        void doSomething();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Strategy oneClass = new MyClass()::doSomething;
        Strategy twoClass = new LibraryClass()::doSomething;

        oneClass.doSomething();
        twoClass.doSomething();
    }
}

Rather than trying to cast (which you can't), create new Strategy objects using the doSomething methods of the objects you want. Personally, it feels like a dirty hack, but hey, it's what you wanted.

As others have pointed out, in Java interface definitions are based on explicit declarations. I believe the situation you are describing is covered by the Adapter design pattern, not the Proxy:

Intent: Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces. ---GoF p. 139

And the "Proxy" solution you point out corresponds to the object variant of the Adapter pattern. If LibraryClass is not final, one option to consider is to use the class variant of the pattern:

class MyLibraryClass extends LibraryClass implements Strategy {
   /* ... */
}
Related