2008-10-13 35 views
43

Entonces, entiendo que lo siguiente no funciona, pero ¿por qué no funciona?¿Por qué no puedo usar un argumento de tipo en un parámetro de tipo con múltiples límites?

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 cualquier adaptador enlazado adicional <E> cuando se une primero es un parámetro de tipo" (en Eclipse), o "parámetro de tipo no puede ser seguido por otros límites" (en IDEA), haga su elección.

Está claro que no está permitido utilizar el parámetro de tipo I allí, antes del &, y eso es todo. (Y antes de preguntar, no funciona si los cambias, porque no hay garantía de que I no 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, es porque 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í. 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(), por lo que no es ambiguo ...

¿Alguien me puede demostrar el problema?

Respuesta

23

Tampoco estoy seguro de por qué la restricción está allí. Puede intentar enviar un correo electrónico amigable a los diseñadores de Java 5 Generics (principalmente Gilad Bracha y Neal Gafter).

Supongo que querían admitir solo un mínimo absoluto de intersection types (que es lo que básicamente son varios límites), para 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 por 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 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 múltiples límites, sólo el extremo izquierdo unido se considera para el borrado, así que si max se da la siguiente firma:

public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) 

A continuación, el borrado de su firma se convierte en:

public static Object max(Collection coll) 

¿Qué es igual a la firma de max antes de los genéricos.

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 upcoming OOPSLA paper.

+0

En realidad, si el punto de múltiples límites es controlar el borrado, eso tiene sentido, porque voy a borrar Objeto. –

1

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 que me llevó a this blog entry, que señala además a jls 4.4:

El límite consiste en ya sea una variable de tipo, o una clase o interfaz de tipo T, posiblemente seguido de más tipos de interfaz I1, ... , En.

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.

+0

Porque podría ser una clase? El hecho de que A se extienda no significa que sea una interfaz (está bien que haría de A una interfaz), sino que A podría ser fácilmente una subclase de I, lo que está prohibido por la especificación –

+0

¿Cuál es el problema si yo soy una clase? ? Se sabe que el adaptador es una interfaz. –

9

Así es otra cita de JLS:

La forma de una cota está restringido (sólo el primer elemento puede ser una variable de clase o tipo, y sólo una variable de tipo puede aparecer en el límite) para oponen ciertas situaciones incómodas que entran en existencia.

¿Cuáles son exactamente esas situaciones incómodas, no sé.

+1

Quizás ellos tampoco lo sepan? – Pacerier

12

dos posibles razones para prohibir esta:

  1. complejidad. Sun bug 4899305 sugiere que un límite que contiene un parámetro de tipo más tipos parametrizados adicionales permitiría tipos mutuamente recursivos incluso más complicados que los que ya existen. En resumen, Bruno's answer.

  2. La posibilidad de especificar tipos ilegales. Específicamente, extending a generic interface twice with different parameters. No puedo llegar a 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 una Comparator<Integer> y una Comparator<String>. No está claro para mí exactamente cuántos problemas causaría esto para el compilador, pero claramente no es bueno. Supongamos que en particular que Comparator tenían un método como este:

void sort(List<? extends T> list);

Nuestra Comparator<Integer>/Comparator<String> híbrido 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 por las que no se puede especificar un tipo tal 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>

Desde <A extends I & Adapter<E>> le permite hacer lo mismo indirectamente, está fuera, también.

+2

Interesante. Tengo que pensar en eso un poco. Todavía parece que el compilador lo atrapó, en la declaración de foo(), de todos modos. Pero tal vez hay un ejemplo aún más artificial que lo haría más claro. :) James Iry dice que funciona en Scala: http://www.chrononaut.org/showyourwork/?p=52#comment-46 –

+0

@Chris, Su segundo enlace está inactivo ...... – Pacerier

+0

@Pacerier , Gracias. Lamentablemente, parece que no puedo encontrar una copia en vivo ahora :( –

Cuestiones relacionadas