example - javac install
¿Es esto válido Java? (10)
--- Editado en respuesta a los comentarios a continuación ---
Ok, entonces es válido Java, pero no debería ser. La clave es que no se basa realmente en el tipo de devolución, sino en el parámetro genérico borrado.
Esto no funcionaría en un método no estático, y está explícitamente prohibido en un método no estático. Intentar hacerlo en una clase fallaría debido a problemas adicionales, el primero es que una clase típica no es definitiva ya que la clase es Clase .
Es una incoherencia en un lenguaje por lo demás bastante consistente. TI se desmayará y dirá que debería ser ilegal, incluso si técnicamente está permitido. Realmente no agrega nada a la legibilidad del lenguaje, y agrega muy poco a la capacidad de resolver problemas significativos. El único problema significativo que parece resolver es si usted está lo suficientemente familiarizado con el idioma como para saber cuándo sus principios centrales parecen ser violados por las inconsistencias internas del lenguaje para resolver el borrado de tipos, los genéricos y las firmas de métodos resultantes.
Definitivamente, se debe evitar el código, ya que es trivial resolver el mismo problema de muchas maneras más significativas, y el único beneficio es ver si el revisor / extensor conoce una esquina polvorienta y sucia de la especificación del idioma.
--- Publicación original sigue ---
Si bien los compiladores podrían haberlo permitido, la respuesta sigue siendo no.
Erasure convertirá tanto la Lista <String> como la Lista <Integer> en una Lista sin adornos. Eso significa que sus dos métodos "f" tendrán la misma firma pero diferentes tipos de devolución. El tipo de devolución no se puede utilizar para diferenciar métodos, ya que al hacerlo se producirá un error al volver a un super-tipo común; me gusta:
Object o = f(Arrays.asList("asdf"));
¿Has intentado capturar los valores devueltos en variables? Tal vez el compilador haya optimizado las cosas de tal manera que no esté pisoteando el código de error correcto.
¿Es esto válido Java?
import java.util.Arrays;
import java.util.List;
class TestWillThatCompile {
public static String f(List<String> list) {
System.out.println("strings");
return null;
}
public static Integer f(List<Integer> list) {
System.out.println("numbers");
return null;
}
public static void main(String[] args) {
f(Arrays.asList("asdf"));
f(Arrays.asList(123));
}
}
- Eclipse 3.5 dice que sí
- Eclipse 3.6 dice que no
- Intellij 9 dice que sí
- Sun javac 1.6.0_20 dice que sí
- GCJ 4.4.3 dice sí
- El compilador GWT dice sí
- La muchedumbre en mi pregunta anterior de Stackoverflow dice que no
¡Mi comprensión de la teoría de Java dice que no !
Sería interesante saber qué dice JLS al respecto.
Bien, si entiendo correctamente el punto tres en la primera lista de la sección 8.4.2 de la especificación, dice que sus métodos f () tienen la misma firma:
http://java.sun.com/docs/books/jls/third_edition/html/classes.html#38649
Es la especificación que realmente responde a esta pregunta, y no el comportamiento observado del compilador X o IDE X. Todo lo que podemos decir mirando las herramientas es cómo el autor de la herramienta interpretó la especificación.
Si aplicamos la viñeta tres, obtenemos:
... public static String f(List<String> list) { System.out.println("strings"); return null; } public static Integer f(List<String> list) { System.out.println("numbers"); return null; } ...
y las firmas coinciden, entonces hay una colisión y el código no debe compilarse.
Depende de cómo desee llamar a estos métodos. Si desea llamar a estos métodos desde otro código fuente de Java , se considera no válido por los motivos ilustrados en la respuesta de Edwin . Esta es una limitación del lenguaje Java.
Sin embargo, no todas las clases deben generarse a partir del código fuente de Java (considere todos los lenguajes que usan la JVM como su tiempo de ejecución: JRuby, Jython, etc.). En el nivel de código de bytes , la JVM puede eliminar la ambigüedad de los dos métodos porque las instrucciones de bytecode especifican el tipo de devolución que esperan. Por ejemplo, aquí hay una clase escrita en Jasmin que puede llamar a cualquiera de estos métodos:
.class public CallAmbiguousMethod
.super java/lang/Object
.method public static main([Ljava/lang/String;)V
.limit stack 3
.limit locals 1
; Call the method that returns String
aconst_null
invokestatic TestWillThatCompile/f(Ljava/util/List;)Ljava/lang/String;
; Call the method that returns Integer
aconst_null
invokestatic TestWillThatCompile/f(Ljava/util/List;)Ljava/lang/Integer;
return
.end method
Lo compilo en un archivo de clase usando el siguiente comando:
java -jar jasmin.jar CallAmbiguousMethod.j
Y llámalo usando:
java CallAmbiguousMethod
Mira, la salida es:
> java CallAmbiguousMethod strings numbers
Actualizar
Simon publicó un programa de ejemplo que llama a estos métodos:
import java.util.Arrays;
import java.util.List;
class RealyCompilesAndRunsFine {
public static String f(List<String> list) {
return list.get(0);
}
public static Integer f(List<Integer> list) {
return list.get(0);
}
public static void main(String[] args) {
final String string = f(Arrays.asList("asdf"));
final Integer integer = f(Arrays.asList(123));
System.out.println(string);
System.out.println(integer);
}
}
Aquí está el bytecode de Java generado:
>javap -c RealyCompilesAndRunsFine Compiled from "RealyCompilesAndRunsFine.java" class RealyCompilesAndRunsFine extends java.lang.Object{ RealyCompilesAndRunsFine(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."":()V 4: return public static java.lang.String f(java.util.List); Code: 0: aload_0 1: iconst_0 2: invokeinterface #2, 2; //InterfaceMethod java/util/List.get:(I)Ljava/lang/Object; 7: checkcast #3; //class java/lang/String 10: areturn public static java.lang.Integer f(java.util.List); Code: 0: aload_0 1: iconst_0 2: invokeinterface #2, 2; //InterfaceMethod java/util/List.get:(I)Ljava/lang/Object; 7: checkcast #4; //class java/lang/Integer 10: areturn public static void main(java.lang.String[]); Code: 0: iconst_1 1: anewarray #3; //class java/lang/String 4: dup 5: iconst_0 6: ldc #5; //String asdf 8: aastore 9: invokestatic #6; //Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List; 12: invokestatic #7; //Method f:(Ljava/util/List;)Ljava/lang/String; 15: astore_1 16: iconst_1 17: anewarray #4; //class java/lang/Integer 20: dup 21: iconst_0 22: bipush 123 24: invokestatic #8; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 27: aastore 28: invokestatic #6; //Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List; 31: invokestatic #9; //Method f:(Ljava/util/List;)Ljava/lang/Integer; 34: astore_2 35: getstatic #10; //Field java/lang/System.out:Ljava/io/PrintStream; 38: aload_1 39: invokevirtual #11; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 42: getstatic #10; //Field java/lang/System.out:Ljava/io/PrintStream; 45: aload_2 46: invokevirtual #12; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V 49: return
Resulta que el compilador de Sun está generando el bytecode necesario para eliminar la ambigüedad de los métodos (ver las instrucciones 12 y 31 en el último método).
Actualización # 2
La Especificación del lenguaje Java sugiere que, de hecho, puede ser un código fuente Java válido. En la página 449 (§15.12 Expresiones de Invocación de Método) vemos esto:
Es posible que ningún método sea el más específico, porque hay dos o más métodos que son específicos al máximo. En este caso:
- Si todos los métodos de máxima especificidad tienen firmas equivalentes de anulación (§8.4.2), entonces:
- Si exactamente uno de los métodos máximos específicos no se declara abstracto, es el método más específico.
- De lo contrario, si todos los métodos de máxima especificidad se declaran abstractos, y las firmas de todos los métodos de máxima especificidad tienen el mismo borrado (§4.6), entonces el método más específico se elige arbitrariamente entre el subconjunto de los métodos máximos específicos que tienen el tipo de devolución más específico . Sin embargo, se considera que el método más específico arroja una excepción marcada si y solo si esa excepción o su borrado se declara en las cláusulas throws de cada uno de los métodos máximamente específicos.
- De lo contrario, diremos que la invocación del método es ambigua y se produce un error de compilación.
A menos que esté equivocado, este comportamiento solo debería aplicarse a los métodos declarados como abstractos, aunque ...
Actualización # 3
Gracias al comentario de ILMTitan:
@Adam Paynter: Su texto en negrita no importa, porque es solo un caso cuando dos métodos son anula-equivalentes, que Dan demostró que no era el caso. Por lo tanto, el factor determinante debe ser si el JLS toma en cuenta los tipos genéricos al determinar el método más específico. - ILMTitan
Eclipse puede producir código de bytes a partir de esto:
public class Bla {
private static BigDecimal abc(List<BigDecimal> l) {
return l.iterator().next().multiply(new BigDecimal(123));
}
private static String abc(List<String> l) {
return l.iterator().next().length() + "";
}
public static void main(String[] args) {
System.out.println(abc(Arrays.asList("asdf")));
System.out.println(abc(Arrays.<BigDecimal>asList(new BigDecimal(123))));
}
}
Salida:
4
15129
Es válido en la http://java.sun.com/docs/books/jls/third_edition/html/classes.html#38649 .
La firma de un método
m1
es una subscripción de la firma de un métodom2
si cualquiera
m2
tiene la misma firma quem1
, ola firma de
m1
es lo mismo que el borrado de la firma dem2
.
Entonces, estas no son suscripciones entre sí porque el borrado de List<String>
no es List<Integer>
y viceversa.
Dos firmas de métodos
m1
ym2
son anuladas, equivalentes sim1
es una subsigna dem2
om2
es una subsigna dem1
.
Entonces estos dos no son equivalentes de anulación (observe el iff ). Y la regla para sobrecargar es:
Si dos métodos de una clase (si ambos declarados en la misma clase, o ambos heredados por una clase, o uno declarado y uno heredado) tienen el mismo nombre pero las firmas que no son anuladas equivalen, entonces se dice que el nombre del método es sobrecargado.
Por lo tanto, estos dos métodos están sobrecargados y todo debería funcionar.
La inferencia de tipo Java (lo que está sucediendo cuando llama a métodos genéricos estáticos como Array.asList) es complicado y no está bien especificado en el JLS. Este documento de 2008 tiene una descripción muy interesante de algunos de los problemas y cómo podría solucionarse:
La inferencia del tipo Java está rota: ¿cómo podemos solucionarlo?
Parece que el compilador elige el método más específico basado en los genéricos.
import java.util.Arrays;
import java.util.List;
class TestWillThatCompile {
public static Object f(List<?> list) {
System.out.println("strings");
return null;
}
public static Integer f(List<Integer> list) {
System.out.println("numbers");
return null;
}
public static void main(String[] args) {
f(Arrays.asList("asdf"));
f(Arrays.asList(123));
}
}
Salida:
strings
numbers
Por lo que puedo decir, el archivo .class puede contener ambos métodos, ya que el descriptor del método contiene los parámetros, así como el tipo de retorno. Si el tipo de devolución fuese el mismo, los descriptores serían los mismos y los métodos no se distinguirían después de la eliminación del tipo (por lo tanto, tampoco funciona con el vacío). http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html#7035
Ahora, llamar al método con invoke_virtual requiere el descriptor del método, por lo que puede decir cuál de los métodos desea llamar, por lo que parece que todos los compiladores, que aún tienen la información genérica, simplemente colocan el descriptor para el método que coincide con el tipo genérico del parámetro, entonces está codificado en bytecode qué método llamar (según lo distingan sus descriptores, o más exactamente por el tipo de retorno en esos descriptores), incluso si el parámetro ahora es una lista, sin genéricos información.
Si bien encuentro esta práctica un poco cuestionable, debo decir que me parece genial que puedas hacer esto y pensar que los genéricos deberían haber sido diseñados para poder trabajar así en primer lugar (sí, sé que crear problemas con la compatibilidad hacia atrás).
También funciona (con Sun Java 1.6.0_16 esta vez) es
import java.util.Arrays;
import java.util.List;
class RealyCompilesAndRunsFine {
public static String f(List<String> list) {
return list.get(0);
}
public static Integer f(List<Integer> list) {
return list.get(0);
}
public static void main(String[] args) {
final String string = f(Arrays.asList("asdf"));
final Integer integer = f(Arrays.asList(123));
System.out.println(string);
System.out.println(integer);
}
}
Una pregunta que no se ha respondido es: ¿por qué solo desencadena un error de compilación en Eclipse 3.6?
He aquí por qué: es una característica .
En javac 7, dos métodos se consideran duplicados (o un error de clasificación de nombre) independientemente de sus tipos de devolución.
Este comportamiento ahora es más consistente con javac 1.5, que reportó errores de clasificación de nombres en los métodos e ignoró sus tipos de devolución. Solo en 1.6 se realizó un cambio que incluía tipos de devolución al detectar métodos duplicados.
Hemos decidido realizar este cambio en todos los niveles de cumplimiento (1.5, 1.6, 1.7) en la versión 3.6 para que los usuarios no se sorprendan con el cambio si compilan su código con javac 7.