valor - parametros y atributos java
¿Por qué no puedo usar un argumento de tipo en un parámetro de tipo con múltiples límites? (4)
Aquí hay otra cita de JLS :
La forma de un límite está restringida (solo el primer elemento puede ser una clase o una variable de tipo, y solo una variable de tipo puede aparecer en el límite) para impedir que surjan ciertas situaciones incómodas .
Cuáles son exactamente esas situaciones incómodas, no sé.
Entonces, entiendo que lo siguiente no funciona, pero ¿por qué no funciona?
interface Adapter<E> {}
class Adaptulator<I> {
<E, A extends I & Adapter<E>> void add(Class<E> extl, Class<A> intl) {
addAdapterFactory(new AdapterFactory<E, A>(extl, intl));
}
}
El método add()
me da un error de compilación, "No se puede especificar ningún Adaptador vinculado <E> cuando el primer límite es un parámetro de tipo" (en Eclipse) o "Parámetro de tipo no puede ser seguido por otros límites" (en IDEA), elige tu opción.
Claramente, no está permitido usar el parámetro de tipo I
allí, antes de &
, y eso es todo. (Y antes de preguntar, no funciona si los cambias, porque no hay garantía de que I
sea una clase concreta). ¿Pero por qué no? Revisé las preguntas frecuentes de Angelika Langer y no puedo encontrar una respuesta.
Generalmente, cuando una limitación de genéricos parece arbitraria, se debe a que ha creado una situación en la que el sistema de tipos no puede imponer la corrección. Pero no veo qué caso rompería lo que estoy tratando de hacer aquí. Yo diría que tal vez tiene algo que ver con el envío de métodos después del borrado de tipos, pero solo hay un método add()
, así que no hay ambigüedad ...
¿Alguien puede demostrarme el problema?
Dos posibles razones para prohibir esto:
Complejidad. La falla solar 4899305 sugiere que un límite que contenga un parámetro de tipo más tipos parametrizados adicionales permitiría tipos mutuamente recursivos aún más complicados de los que ya existen. En resumen, la respuesta de Bruno .
La posibilidad de especificar tipos ilegales. Específicamente, extender una interfaz genérica dos veces con diferentes parámetros . No puedo proponer un ejemplo no artificial, pero:
/** Contains a Comparator<String> that also implements the given type T. */ class StringComparatorHolder<T, C extends T & Comparator<String>> { private final C comparator; // ... } void foo(StringComparatorHolder<Comparator<Integer>, ?> holder) { ... }
Ahora holder.comparator
es un Comparator<Integer>
y un Comparator<String>
. No está claro para mí exactamente cuántos problemas causaría esto para el compilador, pero claramente no es bueno. Supongamos en particular que el Comparator
tiene un método como este:
void sort(List<? extends T> list);
Nuestro híbrido Comparator<Integer>
/ Comparator<String>
ahora tiene dos métodos con el mismo borrado:
void sort(List<? extends Integer> list); void sort(List<? extends String> list);
Es por este tipo de razones que no puede especificar ese tipo directamente:
<T extends Comparator<Integer> & Comparator<String>> void bar() { ... }
java.util.Comparator cannot be inherited with different arguments: <java.lang.Integer> and <java.lang.String>
Como <A extends I & Adapter<E>>
permite hacer lo mismo indirectamente, también está fuera.
Esto probablemente no responde a la pregunta de la raíz, pero solo quiere señalar que la especificación lo prohíbe sin ambigüedades. La búsqueda de Google para el mensaje de error me llevó a esta entrada del blog , que además apunta a jls 4.4 :
El límite consiste en una variable de tipo, o una clase o tipo de interfaz T posiblemente seguida por otros tipos de interfaz I1, ..., In.
Por lo tanto, si usa el parámetro tipo como vinculado, no puede usar ningún otro límite, tal como lo indica el mensaje de error.
¿Por qué la restricción? No tengo idea.
Tampoco estoy seguro de por qué la restricción está ahí. Puede intentar enviar un correo electrónico amigable a los diseñadores de Java 5 Generics (principalmente Gilad Bracha y Neal Gafter).
Mi suposición es que querían soportar solo un mínimo absoluto de tipos de intersección (que es lo que básicamente son los límites múltiples), para hacer que el lenguaje no sea más complejo de lo necesario. Una intersección no se puede usar como una anotación de tipo; un programador solo puede expresar una intersección cuando aparece como el límite superior de una variable de tipo.
¿Y por qué este caso incluso fue compatible? La respuesta es que los límites múltiples le permiten controlar el borrado, lo que permite mantener la compatibilidad binaria al generar clases existentes. Como se explica en la sección 17.4 del book de Naftalin y Wadler, un método max
lógicamente tendría la siguiente firma:
public static <T extends Comparable<? super T>> T max(Collection<? extends T> coll)
Sin embargo, esto se borra a:
public static Comparable max(Collection coll)
Que no coincide con la firma histórica de max
, y hace que los clientes antiguos se rompan. Con límites múltiples, solo se considera el borrado de la izquierda para el borrado, por lo que si se le otorga a max
la siguiente firma:
public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)
Entonces el borrado de su firma se convierte en:
public static Object max(Collection coll)
Que es igual a la firma de max
antes de Generics.
Parece plausible que los diseñadores de Java solo se preocupen por este caso simple y restrinjan otros usos (más avanzados) de los tipos de intersección porque simplemente no estaban seguros de la complejidad que podría traer. Por lo tanto, el motivo de esta decisión de diseño no tiene que ser un posible problema de seguridad (como sugiere la pregunta).
Más discusión sobre tipos de intersección y restricciones de genéricos en un próximo artículo de OOPSLA .