sentencia - return metodos en java
¿Por qué se borra el tipo genérico de devolución cuando hay una conversión no verificada de un parámetro de método en Java 8? (4)
Echemos un vistazo a la especificación del lenguaje Java:
15.12.2.6. Tipo de Invocación de Método
...
Si el método elegido no es genérico, entonces:
- Si la conversión sin marcar era necesaria para que el método fuera aplicable, los tipos de parámetros del tipo de invocación son los tipos de parámetros del tipo de método, y el tipo de retorno y los tipos lanzados vienen dados por los borrados del tipo de retorno y los tipos de lanzamiento de los métodos. tipo.
...
15.12.2.6. Método de resultados y tipos de tiros
El tipo de resultado del método elegido se determina de la siguiente manera:
Si el método elegido se declara con un tipo de retorno de vacío, entonces el resultado es nulo.
De lo contrario, si la conversión sin marcar era necesaria para que el método fuera aplicable, entonces el tipo de resultado es el borrado (§4.6) del tipo de retorno declarado del método.
Es importante entender, qué significa "método genérico":
8.4.4. Métodos genéricos
Un método es genérico si declara una o más variables de tipo (§4.4).
En otras palabras, el método
static Producer<String> getProducer(List<Integer> list)
tiene parámetros genéricos y tipos de retorno, pero no es genérico, ya que no declara variables de tipo.
Por lo tanto, las partes citadas se aplican y, a pesar de hacer diferencias con respecto a los requisitos previos, concuerdan con las consecuencias de esta invocación específica del método, " si la conversión no verificada era necesaria para que el método fuera aplicable, entonces el tipo de resultado es la eliminación ... de los métodos declarados tipo de devolución ".
Así que es el compilador más antiguo que viola la especificación utilizando el tipo de retorno Producer<String>
lugar del Producer
borrado.
Considere el siguiente ejemplo de código:
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List list = new ArrayList<Integer>();
String response = getProducer(list).get();
}
static Producer<String> getProducer(List<Integer> list) {
return new Producer<String>();
}
}
class Producer<T> {
T get() {
return null;
}
}
Cuando se compila en Java 7, solo produce una advertencia esperada para getProducer(list)
:
Advertencia: (7, 39) java: se requiere conversión sin
java.util.List<java.lang.Integer>
:java.util.List<java.lang.Integer>
found:java.util.List
Sin embargo, cuando se compila en Java 8, produce el siguiente error para la response = getProducer(list).get()
:
Error: (7, 48) java: tipos incompatibles:
java.lang.Object
no se puede convertir ajava.lang.String
Entonces, aparentemente, el tipo devuelto por getProducer(list)
no es Producer<String>
, sino que se borró Producer
(que también se confirma mediante la función ''extract variable'' en el IDE). Esto es muy desconcertante porque el método getProducer
siempre devuelve Producer<String>
.
Curiosamente, podría solucionarse evitando la conversión sin getProducer
mientras se llama getProducer
método getProducer
, ya sea por:
- Cambie el tipo de parámetro de
getProducer
deList<Integer>
aList
- Cambiar el tipo de variable de
List
deList
aList<Integer>
Actualizaciones
- Java utilizado es Oracle JDK 1.8.0_40
- También intenté usar las opciones de origen y destino de 1.5 a 1.7 con el compilador de Java 8 y el resultado fue el mismo.
Preguntas
- ¿Cómo podría el tipo genérico del argumento pasado afectar un tipo genérico del valor de retorno del método mientras que el tipo genérico del valor de retorno está fijado en la firma del método?
- ¿Por qué hay un cambio de comportamiento incompatible con versiones anteriores entre Java 7 y Java 8?
Esto parece un problema de compatibilidad conocido reportado here y here .
Desde el segundo enlace:
El siguiente código compilado, con advertencias, en JDK 7 no se compilará en JDK 8:
import java.util.List;
class SampleClass {
static class Baz<T> {
public static List<Baz<Object>> sampleMethod(Baz<Object> param) {
return null;
}
}
private static void bar(Baz arg) {
Baz element = Baz.sampleMethod(arg).get(0);
}
}
Compilar este código en JDK 8 produce el siguiente error:
SampleClass.java:12: error: tipos incompatibles: el objeto no se puede convertir a Baz
Baz element = Baz.sampleMethod(arg).get(0);
Nota: SampleClass.java usa operaciones no verificadas o inseguras. Nota: Recompile con -Xlint: sin marcar para más detalles. 1 error
Derivado de esto, el código de OP puede ser reparado reemplazando esta línea (la declaración de tipo en el lado derecho me echó, la leo como una lista de matriz tipada que no es):
List list = new ArrayList<Integer>();
con
List<Integer> list = new ArrayList<Integer>();
que no dará lugar a que el tipo se borre del tipo de devolución del método getProducer(List<Integer> list)
Citar de nuevo el segundo enlace:
En este ejemplo, se está pasando un tipo sin procesar al
sampleMethod(Baz<Object>)
que se puede aplicar mediante subtipos (consulte JLS, Java SE 7 Edition, sección 15.12.2.2). Una conversión sin marcar es necesaria para que el método sea aplicable, por lo que su tipo de retorno se borra (consulte JLS, Java SE 7 Edition, sección 15.12.2.6). En este caso, el tipo de retorno desampleMethod(Baz<Object>)
esjava.util.List
lugar dejava.util.List<Baz<Object>>
y, por lo tanto, el tipo de retorno deget(int)
esObject
, que no es una asignación -compatible conBaz
.
La verdadera pregunta es; ¿Por qué puedes usar el tipo sin procesar? Por compatibilidad con versiones anteriores. Si es por compatibilidad con versiones anteriores, la suposición es que solo se deben usar tipos crudos.
¿Cómo podría afectar el tipo genérico de argumento pasado al tipo de valor de retorno del método mientras que el tipo de valor de retorno genérico se corrige en la firma del método?
Un método o constructor tiene uno de los dos modos. Es completamente genérico o está completamente tipeado para compatibilidad con versiones anteriores. No hay un modo de modo de uso en el que se tipea parcialmente en bruto y en parte es genérico. La única razón por la que los tipos primarios son una opción es por compatibilidad con versiones anteriores y en esta situación se supone que todos los tipos son / fueron tipos crudos.
¿Por qué hay un cambio de comportamiento tan incompatible hacia atrás entre Java7 y Java8?
Ya que Java 1.4 ya no es compatible y no lo ha sido por un tiempo, el argumento de compatibilidad con versiones anteriores no es tan fuerte y tiene algún sentido que los tipos sin formato tengan un lugar en el lenguaje actual.
Supongo que el razonamiento es el siguiente, que se centra en la compatibilidad con la "migración".
Si la invocación proporciona un argumento de tipo sin procesar a un parámetro de método de un tipo genérico, es muy probable que el código de invocación sea el código pre-genérico, y la declaración de método, que también se usaba antes del genérico, ha sido "evolucionado" para usar tipos genéricos.
Para mantener una compatibilidad perfecta, la solución infalible es borrar el tipo de método, de modo que sea como la versión pre-genérica. De esta forma, el significado de la invocación permanece exactamente igual.
Una solución más sofisticada podría ser demasiado complicada para JLS, por ejemplo, ¿cómo podemos hacer la inferencia de tipos si hay argumentos en bruto?
Hoy en día, esa suposición ya no es válida; la invocación es más probable que sea un código post-genérico, que, por diversas razones, todavía utiliza el tipo sin formato. Es mejor "corregir" el tipo crudo desde el principio
List list0 = ...; // got it from somewhere, possibly from an old API
@SuppressWarnings("unchecked")
List<Integer> list = list0;