2010-08-10 9 views
9

Tengo la siguiente clase de prueba que usa genéricos para sobrecargar un método. Funciona cuando se compila con javac y no se puede compilar en Eclipse Helios. Mi versión de Java es 1.6.0_21.Código genérico de Java compila con javac, falla con Eclipse Helios

Todos los artículos que leí indican que Eclipse tiene razón y que este código no debería funcionar. Sin embargo, cuando se compila con javac y se ejecuta, se selecciona el método correcto.

¿Cómo es esto posible?

Gracias!

import java.util.ArrayList; 

public class Test { 
    public static void main (String [] args) { 
     Test t = new Test(); 
     ArrayList<String> ss = new ArrayList<String>(); 
     ss.add("hello"); 
     ss.add("world"); 
     ArrayList<Integer> is = new ArrayList<Integer>(); 
     is.add(1); 
     is.add(2); 
     System.out.println(t.getFirst(ss)); 
     System.out.println(t.getFirst(is)); 
    } 
    public String getFirst (ArrayList<String> ss) { 
     return ss.get(0); 
    } 
    public Integer getFirst (ArrayList<Integer> ss) { 
     return ss.get(0); 
    } 
} 

Respuesta

2

Sería posible si javac tuviera un error en él. Javac es solo software y es tan propenso a errores como cualquier otra pieza de software.

Además, Java Language Spec es muy complejo y un poco vago en algunos lugares, por lo que también podría deberse a una diferencia en la interpretación entre los chicos de Eclipse y los muchachos javac.

Comenzaría preguntando esto en los canales de soporte de Eclipse. Normalmente son muy buenos para recoger estas cosas y explicar por qué creen que tienen razón o admitir que están equivocados.

Para que quede constancia, creo que Eclipse también está aquí.

+0

Eclipse está mal. Esto es perfectamente legal Java. – erickson

+0

Meh. Estaba adivinando basándome en promedios históricos. Dado el análisis de otras personas, sospecho que tienes razón. De cualquier manera, plantear esto con los chicos de Eclipse es el camino a seguir. – dty

2

¿Está seguro de que Eclipse también está configurado para usar Java 1.6?

+2

Fuera de interés, ¿por qué crees que es relevante? Eclipse está configurado en al menos 1.5 o de lo contrario no compilaría en absoluto. ¿Crees que hay una diferencia significativa entre la semántica del lenguaje en 1.5 y 1.6? – dty

+0

Solo quería asegurarme de que no estaba configurado en 1.4. No uso eclipse, pero sé que IDE generalmente tiene una configuración de versión java independiente de la de tu sistema. En cuanto a las diferencias de 1.5-1.6, no conozco ninguna significativa. – Jeffrey

+0

Si se estableció en 1.4, no se habría compilado en absoluto debido a los genéricos. – dty

0

Eclipse y javac usan compiladores diferentes. Eclipse utiliza un compilador de terceros para convertir su código en bytecodes para Java VM. Javac usa el compilador de Java que Sun publica. Por lo tanto, es posible que un código idéntico produzca resultados ligeramente diferentes. Netbeans, creo que usa el compilador de Sun, así que revísalo allí también.

+2

Un poco suelto con el lenguaje aquí. Para ser claros, javac es el compilador que Sun (Oracle) publica. Y Eclipse no usa un compilador de terceros: usa su propia implementación de compilador. – dty

3

Trabaja para mí en Eclipse Helios. La selección del método ocurre en tiempo de compilación, y el compilador tiene suficiente información para hacerlo.

4

Este código es correcto, como se describe en JLS 15.12.2.5 Choosing the Most Specific Method.

Además, considere la codificación de la interfaz:

List<String> ss = new ArrayList<String>(); 
List<Integer> is = new ArrayList<Integer>(); 
// etc. 

Como notas @McDowell, las firmas de los métodos modificados aparecen en el archivo de clase:

$ javap build/classes/Test 
Compiled from "Test.java" 
public class Test extends java.lang.Object{ 
    public Test(); 
    public static void main(java.lang.String[]); 
    public java.lang.String getFirst(java.util.ArrayList); 
    public java.lang.Integer getFirst(java.util.ArrayList); 
} 

Tenga en cuenta que esto no contradice la observación de @ Meriton sobre el archivo de clase. Por ejemplo, la salida de este fragmento

Method[] methods = Test.class.getDeclaredMethods(); 
for (Method m : methods) { 
    System.out.println(Arrays.toString(m.getGenericParameterTypes())); 
} 

muestra el parámetro formal de main(), así como los dos parámetros de tipo genérico:

[class [Ljava.lang.String;] 
[java.util.ArrayList<java.lang.String>] 
[java.util.ArrayList<java.lang.Integer>] 
+1

Después del borrado de tipo, las firmas de método se convierten en 'public String getFirst (ArrayList)' y 'public Integer getFirst (ArrayList)'. – McDowell

+1

No McDowell, las firmas de método no se borran. – meriton

+0

@meriton: @McDowell: IIUC, ambos son corect: las firmas se borran _y_ se conservan los parámetros de tipo genérico, como se sugirió anteriormente. – trashgod

0

Un punto a tener en cuenta es que (todo ?, ciertamente algunos, como por ejemplo, el editor de NetBeans hace esto) las herramientas de análisis de código estático no consideran el tipo de retorno ni los modificadores (privado/público, etc.) como parte de la firma del método.

Si ese es el caso, entonces con la ayuda del borrado de tipo ambos métodos getFirst obtendrían la firma y por lo tanto desencadenarían un choque de nombre ...

+0

Los genéricos se borran en * bytecode * (el conjunto de instrucciones de la máquina virtual Java). Las firmas de métodos no son parte del conjunto de instrucciones; se escriben en el archivo de clase como se especifica en el código fuente. Para más información, mira mi respuesta. – meriton

6

El Java Language Specification, section 8.4.2 escribe:

Es un error de tiempo de compilación para declarar dos métodos con firmas de anulación en el equivalente (definido a continuación) en una clase.

Dos firmas de métodos m1 y m2 son anuladas-equivalentes si m1 es una subscripción de m2 o m2 es una subscripción de m1.

La firma de un m1 método es un subsignature de la firma de un m2 método si bien

  • m2 tiene la misma firma que m1, o

  • la firma de m1 es el mismo como el borrado de la firma de m2.

Claramente, los métodos no se anulan equivalente, ya que no es ArrayList<String>ArrayList (el borrado de ArrayList<Integer>).

Entonces, declarar los métodos es legal. Además, la expresión de invocación del método es válida, ya que trivialmente es un método más específico, ya que solo hay un método que coincide con los tipos de argumento.

Editar: Yishai correctamente señala que hay otra restricción muy cercada en este caso. El Java Language Specification, section 8.4.8.3 escribe:

Se trata de un error de tiempo de compilación si una declaración de tipo T tiene un método miembro de m1 y no existe un método m2 declarada en T o un supertipo de T tal que todas las siguientes condiciones mantener:

  • m1 y m2 tienen el mismo nombre.
  • m2 es accesible desde T.
  • La firma de m1 no es una subscripción (§8.4.2) de la firma de m2.
  • m1 o algunas sustituciones del método m1 (directa o indirectamente) tienen el mismo borrado que m2 o algunas sustituciones del método m2 (directa o indirectamente).

Apéndice: En ersure, y la falta de ella

Contrariamente a la noción popular, los genéricos en firmas de método no se borran. Los genéricos se borran en bytecode (el conjunto de instrucciones de la máquina virtual Java). Las firmas de métodos no son parte del conjunto de instrucciones; se escriben en el archivo de clase como se especifica en el código fuente. (Por otro lado, esta información también se puede consultar en tiempo de ejecución mediante reflexión).

Piense en esto: Si los parámetros de tipo fueron borrados de los archivos de clase en su totalidad, ¿cómo podría la finalización de código en el IDE de la pantalla elección que ArrayList.add(E) toma un parámetro de tipo E, y no Object (= el borrado de E) si no tenías el código fuente JDK adjunto? ¿Y cómo sabe el compilador lanzar un error de compilación cuando el tipo estático del argumento del método no era un subtipo de E?

+3

Creo que el caso es mucho más cercano que esto. Considere: si cambia los tipos de devolución de ambos métodos a Object, el código ya no se compilará. El JLS no dice que el tipo de devolución puede ser una característica distintiva de la firma de un método para determinar la equivalencia de anulación. Finalmente, estoy de acuerdo en que el compilador de Sun es correcto, pero es una llamada muy cercana. – Yishai

+0

Buen punto, he editado para incluir la sección correspondiente. La regla es que la pregunta no se trata de anular la equivalencia (donde los tipos de devolución se especifican para que no importen), sino de naturaleza más general. – meriton

1

Después de algunas investigaciones, que tengo la respuesta:

El código, tal como se especifica anterior no debe compilar. ArrayList<String> y ArrayList<Integer>, en tiempo de ejecución sigue siendo ArrayList. Pero su código no funciona porque el tipo que regresa. Si establece los mismos tipos de devolución para ambos métodos, javac no compilará ...

He leído que hay un error en Java 1.6 (que ya está solucionado en Java 1.7) sobre este error. Se trata de devolver tipos ... por lo tanto, deberá cambiar la firma de sus métodos.

Está el bug 6182950 in Oracle's Bug Database.

Cuestiones relacionadas