user7401478
user7401478

Reputation: 1376

How to return interface from generic class implementing the interface?

I want to make a method that accepts any class T that implements any interface I.

Then do something with the class and return the interface I that is implemented.

Here's what I've tried:

class MyLibrary {
    
    public static <I, T extends I> I registerImplementation(Class<T> classImpl) {
        I interfaceImpl = (I) classImpl.newInstance();
        return interfaceImpl;
    }
}

I'm then creating an interface and a class which implements that interface:

interface UserInterface {
    void doSomethingDefined();
}

class UserClass_v1_10_R2 implements UserInterface {

    @Override
    public void doSomethingDefined() {
        System.out.println("What this does is well-defined");
    }

    public void doVersionSpecificStuff() {
        System.out.println("This is not in the interface, so don't really depend on this");
    }
}

However, when I'm calling the method referencing UserClass, it returns the same class type T instead of the interface type I, allowing all the class methods which are not declared in the interface to be called.

I.e. in the statement below, even though registerImplementation() is declared to return a reference to UserInterface, the compiler sees the reference as pointing to an instance of UserClass_v1_10_R2, allowing access to the implementation's methods that are not in the interface.

MyLibrary.registerImplementation(UserClass_v1_10_R2.class).doVersionSpecificStuff();

Contrast this with the supposedly identical

UserInterface uiObj = MyLibrary.registerImplementation(UserClass_v1_10_R2.class);

uiObj.doVersionSpecificStuff(); // This does not compile

Upvotes: 7

Views: 870

Answers (2)

Eugene
Eugene

Reputation: 120848

That's just how the compiler infers the type. If you compile with a special (undocumented) flag:

javac --debug=verboseResolution=all  ...

you will see that:

  Note: Deferred instantiation of method <I,T>registerImplementation(Class<T>)
  MyLibrary.registerImplementation(UserClass_v1_10_R2.class).doVersionSpecificStuff();
                                    ^
  instantiated signature: (Class<UserClass_v1_10_R2>)UserClass_v1_10_R2

Look at the instantiated signature: (Class<UserClass_v1_10_R2>)UserClass_v1_10_R2

So you need to add another argument, to take the interface:

public static <I, T extends I> I registerImplementation(Class<I> interfaceClass, Class<T> implClass) {
    // do whatever checks you want if this is an interface or if a constructor can be called....
    I interfaceImpl = null;
    try {
        interfaceImpl = interfaceClass.cast(implClass.newInstance());
    } catch (Exception e) {
        e.printStackTrace();
    }
    return interfaceImpl;
}

Of course this raises the question if you have multiple interfaces, but that is something for you to think about.

Upvotes: 1

user7401478
user7401478

Reputation: 1376

It's possible to achieve this by passing the desired interface class as another argument to the method. This also allows to verify that the generic type class is an interface, as well as adding extra checks to make sure that the class implementing the interface can be instantiated.

public class MyLibrary {
    public static <I> I registerImplementation(Class<I> interfaceClass, Class<? extends I> implClass) {
        if (!interfaceClass.isInterface()) {
            throw new IllegalArgumentException("Must be an interface class");
        }

        // Check constructor accessibility, etc...
        if (Modifier.isAbstract(implClass.getModifiers())) {
            throw new IllegalArgumentException("Must be instantiable");
        }

        return implClass.newInstance();
    }
}

This returns an implementation of the interface passed as the first parameter. The second parameter's bounded type Class<? extends I> means an explicit cast is not required:

// This returns UserInterface
MyLibrary.registerImplementation(UserInterface.class, UserClass_v1_10_R2.class);

Upvotes: 0

Related Questions