Quiero crear un método que acepte cualquier clase T que implemente cualquier interfaz I
Luego haga algo con la clase y devuelva la interfaz I que está implementada.
Esto es lo que he intentado:
class MyLibrary { public static <I, T extends I> I registerImplementation(Class<T> classImpl) { I interfaceImpl = (I) classImpl.newInstance(); return interfaceImpl; } }Luego estoy creando una interfaz y una clase que implementa esa interfaz:
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"); } } Sin embargo, cuando llamo al método que hace referencia UserClass , devuelve el mismo tipo de clase T en lugar del tipo de interfaz I , lo que permite llamar a todos los métodos de clase que no están declarados en la interfaz.
Es decir, en la siguiente declaración, aunque se declara registerImplementation() para devolver una referencia a UserInterface , el compilador ve la referencia como apuntando a una instancia de UserClass_v1_10_R2 , lo que permite el acceso a los métodos de implementación que no están en la interfaz.
MyLibrary.registerImplementation(UserClass_v1_10_R2.class).doVersionSpecificStuff();Contraste esto con el supuestamente idéntico
UserInterface uiObj = MyLibrary.registerImplementation(UserClass_v1_10_R2.class); uiObj.doVersionSpecificStuff(); // This does not compileAsí es como el compilador infiere el tipo. Si compila con un indicador especial (no documentado):
javac --debug=verboseResolution=all ...verás que:
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 Mire la firma instanciada: (Class<UserClass_v1_10_R2>)UserClass_v1_10_R2
Entonces necesita agregar otro argumento, para tomar la interfaz:
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; }Por supuesto, esto plantea la pregunta si tiene múltiples interfaces, pero eso es algo en lo que debe pensar.
Es posible lograr esto pasando la clase de interfaz deseada como otro argumento del método. Esto también permite verificar que la clase de tipo genérico es una interfaz, así como agregar controles adicionales para asegurarse de que se pueda crear una instancia de la clase que implementa la interfaz.
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(); } } Esto devuelve una implementación de la interfaz pasada como primer parámetro. El tipo acotado del segundo parámetro Class<? extends I> significa que no se requiere una conversión explícita:
// This returns UserInterface MyLibrary.registerImplementation(UserInterface.class, UserClass_v1_10_R2.class);El motivo de la discrepancia es que en la primera llamada al método genérico confía en la inferencia de tipos y los argumentos de tipo se infieren como I=UserClass_v1_10_R2 y T=UserClass_v1_10_R2 porque el compilador no tiene forma de determinar que I ser invocado como UserInterface de usuario.
Si invoca los parámetros de tipo explícitamente, el comportamiento será el esperado, es decir, un error de compilación:
MyLibrary.<UserInterface, UserClass_v1_10_R2>registerImplementation( UserClass_v1_10_R2.class).doVersionSpecificStuff(); Cuando usa una variable local, no es completamente equivalente, porque la inferencia de tipo usa la información de tipo en la declaración de tipo de la variable local, que proporciona suficiente información al compilador para invocar el tipo I=UserInterface deseado.