2012-01-23 16 views
7

Digamos que tengo el siguiente:¿Por qué la implementación de esta interfaz genérica crea una referencia ambigua?

public interface Filter<E> { 
    public boolean accept(E obj); 
} 

y

import java.io.File; 
import java.io.FilenameFilter; 

public abstract class CombiningFileFilter extends javax.swing.filechooser.FileFilter 
     implements java.io.FileFilter, FilenameFilter { 

    @Override 
    public boolean accept(File dir, String name) { 
     return accept(new File(dir, name)); 
    } 
} 

En su forma actual, puede utilizar javac para compilar CombiningFileFilter. Pero, si también decide implementar Filter<File> en CombiningFileFilter, se obtiene el siguiente error:

CombiningFileFilter.java:9: error: reference to accept is ambiguous, 
both method accept(File) in FileFilter and method accept(E) in Filter match 
       return accept(new File(dir, name)); 
        ^
    where E is a type-variable: 
    E extends Object declared in interface Filter 
1 error 

Sin embargo, si hago una tercera clase:

import java.io.File; 

public abstract class AnotherFileFilter extends CombiningFileFilter implements 
     Filter<File> { 
} 

Ya no es un error de compilación. El error de compilación también desaparece si Filter no es genérica:

public interface Filter { 
    public boolean accept(File obj); 
} 

¿Por qué no puede la figura compilador que desde la clase implementa Filter<File>, el método accept debería ser en realidad accept(File) y que no hay ninguna ambigüedad? Además, ¿por qué este error solo ocurre con javac? (Funciona bien con el compilador de Eclipse.)

/editar
Una solución más limpia a este problema compilador que la creación de la tercera clase sería añadir el método public abstract boolean accept(File) en CombiningFileFilter. Eso borra la ambigüedad.

/e2
Estoy usando JDK 1.7.0_02.

+0

intentar retirar el '@ Override' –

+0

@HunterMcMillen Aún obtiene el error – Jeffrey

+0

hay una colisión interfaz entre' javax.swing.filechooser.FileFilter # aceptar (Archivo) '' y su propio filtro #accept (Archivo) '. Vea aquí para una explicación detallada: http://stackoverflow.com/a/2832651/406984 – earldouglas

Respuesta

9

Por lo que yo puedo decir, el error de compilación es un mandato de la especificación del lenguaje Java, que writes:

Let C be a class or interface declaration with formal type parameters A1,...,An , and let C<T1,...,Tn> be an invocation of C , where, for 1in, Ti are types (rather than wildcards). Then:

  • Let m be a member or constructor declaration in C, whose type as declared is T. Then the type of m (§8.2, §8.8.6) in the type C<T1,...,Tn> , is T[A1 := T1, ..., An := Tn] .
  • Let m be a member or constructor declaration in D, where D is a class extended by C or an interface implemented by C. Let D<U1,...,Uk> be the supertype of C<T1,...,Tn> that corresponds to D. Then the type of m in C<T1,...,Tn> is the type of m in D<U1,...,Uk> .

If any of the type arguments to a parameterized type are wildcards, the type of its members and constructors is undefined.

Es decir, el método ha declarado por Filter<File>boolean accept(File) escribir. FileFilter también declara un método boolean accept(File).

CombiningFilterFilter hereda ambos métodos.

¿Qué significa eso? La especificación del lenguaje Java writes:

It is possible for a class to inherit multiple methods with override-equivalent (§8.4.2) signatures.

It is a compile time error if a class C inherits a concrete method whose signatures is a subsignature of another concrete method inherited by C.

(Esto no se aplica, ya que ninguno de estos métodos es concreta.)

Otherwise, there are two possible cases:

  • If one of the inherited methods is not abstract, then there are two subcases:
    • If the method that is not abstract is static, a compile-time error occurs.
    • Otherwise, the method that is not abstract is considered to override, and therefore to implement, all the other methods on behalf of the class that inherits it. If the signature of the non-abstract method is not a subsignature of each of the other inherited methods an unchecked warning must be issued (unless suppressed (§9.6.1.5)). A compile-time error also occurs if the return type of the non-abstract method is not return type substitutable (§8.4.5) for each of the other inherited methods. If the return type of the non-abstract method is not a subtype of the return type of any of the other inherited methods, an unchecked warning must be issued. Moreover, a compile-time error occurs if the inherited method that is not abstract has a throws clause that conflicts (§8.4.6) with that of any other of the inherited methods.
  • If all the inherited methods are abstract, then the class is necessarily an abstract class and is considered to inherit all the abstract methods. A compile-time error occurs if, for any two such inherited methods, one of the methods is not return type substitutable for the other (The throws clauses do not cause errors in this case.)

Así que la "fusión" de los métodos heredados de anulación en el equivalente a un método sólo se produce si uno de ellos es concreto, si todos son abstractos, permanecen separados, por lo que todos son accesibles y se pueden aplicar a la invocación del método.

La especificación del lenguaje Java defines lo que va a ocurrir a continuación de la siguiente manera:

If more than one member method is both accessible and applicable to a method invocation, it is necessary to choose one to provide the descriptor for the run-time method dispatch. The Java programming language uses the rule that the most specific method is chosen.

The informal intuition is that one method is more specific than another if any invocation handled by the first method could be passed on to the other one without a compile-time type error.

Luego define más específica formalmente. Le ahorraré la definición, pero vale la pena señalar que más específico no es una orden parcial, ya que cada método es más específico que sí mismo. A continuación, escribe:

A method m1 is strictly more specific than another method m2 if and only if m1 is more specific than m2 and m2 is not more specific than m1.

lo tanto, en nuestro caso, donde tenemos varios métodos con firmas idénticas, cada una es más específica que el otro, pero tampoco es estrictamente más específica que la otra.

A method is said to be maximally specific for a method invocation if it is accessible and applicable and there is no other method that is applicable and accessible that is strictly more specific.

lo tanto, en nuestro caso, todo heredado accept métodos son máximamente específica.

If there is exactly one maximally specific method, then that method is in fact the most specific method; it is necessarily more specific than any other accessible method that is applicable. It is then subjected to some further compile-time checks as described in §15.12.3.

Lamentablemente, ese no es el caso aquí.

It is possible that no method is the most specific, because there are two or more methods that are maximally specific. In this case:

  • If all the maximally specific methods have override-equivalent (§8.4.2) signatures, then:
    • If exactly one of the maximally specific methods is not declared abstract, it is the most specific method.
    • Otherwise, if all the maximally specific methods are declared abstract, and the signatures of all of the maximally specific methods have the same erasure (§4.6), then the most specific method is chosen arbitrarily among the subset of the maximally specific methods that have the most specific return type. However, the most specific method is considered to throw a checked exception if and only if that exception or its erasure is declared in the throws clauses of each of the maximally specific methods.
  • Otherwise, we say that the method invocation is ambiguous, and a compile-time error occurs.

Y que, finalmente, es el punto saliente: Todos los métodos heredados tienen idénticos, y por lo tanto anulan equivalente firmas. Sin embargo, el método heredado de la interfaz genérica Filter no tiene el mismo borrado que los demás.

Por lo tanto,

  1. El primer ejemplo se compilará porque todos los métodos son abstractos, anular equivalente, y tienen el mismo borrado.
  2. El segundo ejemplo no se compilará, porque todos los métodos son abstractos, anulación-equivalente, pero su borrado no es el mismo.
  3. El tercer ejemplo se compilará, ya que todos los métodos de consulta son abstractos, anulan el equivalente y tienen el mismo borrado. (El método con un borrado diferente se declara en una subclase, y por lo tanto no es un candidato)
  4. El cuarto ejemplo se compilará, porque todos los métodos son abstractos, anulan el equivalente y tienen el mismo borrado.
  5. Se compilará el último ejemplo (método de resumen de repetición en CombiningFileFilter), porque ese método es anulación-equivalente con todos los métodos accept heredados, y por lo tanto los reemplaza (¡tenga en cuenta que no se requiere el mismo borrado para anulación!). Por lo tanto, solo hay un único método accesible y accesible, que es por lo tanto el más específico de.

Solo puedo especular por qué la especificación requiere los mismos borrados además de anular la equivalencia. Puede ser porque, para mantener la compatibilidad con código no genérico, el compilador debe emitir un método sintético con la firma borrada cuando una declaración de método hace referencia a parámetros de tipo. En este mundo borrado, ¿qué método puede usar el compilador como destino para la expresión de invocación del método? La Especificación del lenguaje Java hace un paso en paralelo a este problema al requerir que exista una declaración de método borrada, compartida y adecuada.

Para concluir, el comportamiento de javac, aunque lejos de ser intuitivo, es exigido por la Especificación del lenguaje Java, y eclipse falla la prueba de compatibilidad.

+0

Esto responde la pregunta de por qué el original no se compila. Supongo que el compilador de Eclipse combina todos los métodos en uno o simplemente elige uno arbitrariamente porque, como dijiste, todos están fusionados en la clase concreta de todos modos. – Jeffrey

+0

Espera, ¿no debería significar esto que 'CombiningFileFilter extiende javax.swing.filechooser.FileFilter implementa java.io.FileFilter, FilenameFilter' no debería compilar tampoco? Tanto 'java.io.FileFilter' como' javax.swing.filechooser.FileFilter' tienen métodos abstractos con la firma 'boolean accept (File)'. – Jeffrey

+0

Cavando más profundo, finalmente encontré el párrafo que lo explica todo, y he revisado la respuesta en consecuencia. – meriton

1

Hay un método en la interfaz FileFilter que tiene la misma firma que la de su interfaz concreta Filter<File>. Ambos tienen la firma accept(File f).

Es una referencia ambigua porque el compilador no tiene manera de saber cuál de estos métodos llamará a su llamada de método accept(File f, String name) reemplazada.

+0

Vaya, olvidé mencionar que esto solo ocurre cuando la interfaz 'Filter' es genérica. Si 'Filter' tiene el método' accept (File) 'en lugar de' accept (E) ', ya no hay una referencia ambigua. Y de acuerdo con el compilador, las firmas ambiguas son 'accept (File)' y 'accept (E)'. No 'aceptar (Archivo)' y 'aceptar (Archivo)'. – Jeffrey

Cuestiones relacionadas