Reputation: 1376
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
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
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