valores studio retornar retornan que programacion objeto móviles metodos devolver desarrollo curso como aplicaciones java generics type-inference generic-method

studio - retornar un objeto en java



¿Por qué este método genérico con un límite puede devolver algún tipo? (1)

¿Por qué se compila el siguiente código? El método IElement.getX(String) devuelve una instancia del tipo IElement o de sus subclases. El código en la clase Main invoca el getX(String) . El compilador permite almacenar el valor de retorno en una variable del tipo Integer (que obviamente no está en la jerarquía de IElement ).

public interface IElement extends CharSequence { <T extends IElement> T getX(String value); } public class Main { public void example(IElement element) { Integer x = element.getX("x"); } }

¿No debería ser el tipo de retorno una instancia de IElement , incluso después del borrado del tipo?

El bytecode del método getX(String) es:

public abstract <T extends IElement> T getX(java.lang.String); flags: ACC_PUBLIC, ACC_ABSTRACT Signature: #7 // <T::LIElement;>(Ljava/lang/String;)TT;

Editar: String reemplazada de manera consistente con Integer .


Esto es realmente una inferencia de tipo legítima *.

Podemos reducir esto al siguiente ejemplo ( Ideone ):

interface Foo { <F extends Foo> F bar(); public static void main(String[] args) { Foo foo = null; String baz = foo.bar(); } }

El compilador puede inferir un tipo de intersección (sin sentido, realmente) String & Foo porque Foo es una interfaz. Para el ejemplo en la pregunta, se infiere Integer & IElement .

No tiene sentido porque la conversión es imposible. No podemos hacer tal elenco nosotros mismos:

// won''t compile because Integer is final Integer x = (Integer & IElement) element;

La inferencia de tipos básicamente funciona con:

  • un conjunto de variables de inferencia para cada uno de los parámetros de tipo de un método.
  • Un conjunto de límites a los que debe ajustarse.
  • a veces restricciones , que se reducen a límites.

Al final del algoritmo, cada variable se resuelve en un tipo de intersección basado en el conjunto enlazado, y si son válidas, la invocación se compila.

El proceso comienza en 8.1.3 :

Cuando comienza la inferencia, generalmente se genera un conjunto enlazado a partir de una lista de declaraciones de parámetros de tipo P 1 , ..., P p y las variables de inferencia asociadas α 1 , ..., α p . Tal conjunto enlazado se construye de la siguiente manera. Para cada l (1 ≤ l ≤ p) :

  • [...]

  • De lo contrario, para cada tipo T delimitado por & en un TypeBound , el límite α l <: T[P 1 :=α 1 , ..., P p :=α p ] aparece en el conjunto [...].

Entonces, esto significa primero que el compilador comienza con un límite de F <: Foo (lo que significa que F es un subtipo de Foo ).

Pasando a 18.5.2 , se considera el tipo de objetivo de retorno:

Si la invocación es una expresión poli, […] deje que R sea ​​el tipo de retorno de m , T sea ​​el tipo de destino de la invocación y luego:

  • [...]

  • De lo contrario, la fórmula de restricción ‹R θ → T› se reduce e incorpora con [el conjunto enlazado].

La fórmula de restricción ‹R θ → T› se reduce a otro límite de R θ <: T , por lo que tenemos F <: String .

Más tarde, estos se resuelven de acuerdo con 18.4 :

[…] Se define una instanciación candidata T i para cada α i :

  • De lo contrario, donde α i tiene límites superiores adecuados U 1 , ..., U k , T i = glb(U 1 , ..., U k ) .

Los límites α 1 = T 1 , ..., α n = T n se incorporan con el conjunto de límites actual.

Recuerde que nuestro conjunto de límites es F <: Foo, F <: String . glb(String, Foo) se define como String & Foo . Aparentemente, este es un tipo legítimo para glb , que solo requiere que:

Es un error en tiempo de compilación si, para cualquiera de las dos clases ( no interfaces ) V i y V j , V i no es una subclase de V j o viceversa.

Finalmente:

Si la resolución tiene éxito con las instancias T 1 , ..., T p para las variables de inferencia α 1 , ..., α p , deje que be θ'' sea ​​la sustitución [P 1 :=T 1 , ..., P p :=T p ] . Entonces:

  • Si la conversión no verificada no era necesaria para que el método fuera aplicable, entonces el tipo de invocación de m se obtiene aplicando θ'' al tipo de m .

Por lo tanto, el método se invoca con String & Foo como el tipo de F Por supuesto, podemos asignar esto a una String , convirtiendo así de manera imposible un Foo en una String .

El hecho de que String / Integer son clases finales aparentemente no se considera.

* Nota: el borrado de tipo no está / estaba completamente relacionado con el problema.

Además, aunque esto también se compila en Java 7, creo que es razonable decir que no debemos preocuparnos por la especificación allí. La inferencia de tipos de Java 7 era esencialmente una versión menos sofisticada de Java 8. Se compila por razones similares.

Como addendum, aunque extraño, esto probablemente nunca causará un problema que aún no estaba presente. Raramente es útil escribir un método genérico cuyo tipo de retorno se infiera únicamente del objetivo de retorno, porque solo se puede devolver null desde dicho método sin conversión.

Supongamos, por ejemplo, que tenemos un mapa analógico que almacena subtipos de una interfaz particular:

interface FooImplMap { void put(String key, Foo value); <F extends Foo> F get(String key); } class Bar implements Foo {} class Biz implements Foo {}

Ya es perfectamente válido cometer un error como el siguiente:

FooImplMap m = ...; m.put("b", new Bar()); Biz b = m.get("b"); // casting Bar to Biz

Entonces, el hecho de que también podemos hacer Integer i = m.get("b"); No es una nueva posibilidad de error. Si estuviéramos programando un código como este, para empezar ya era potencialmente poco sólido.

En general, un parámetro de tipo solo debe inferirse únicamente del tipo de destino si no hay ningún motivo para vincularlo, por ejemplo, Collections.emptyList() y Optional.empty() :

private static final Optional<?> EMPTY = new Optional<>(); public static<T> Optional<T> empty() { @SuppressWarnings("unchecked") Optional<T> t = (Optional<T>) EMPTY; return t; }

Esto es A-OK porque Optional.empty() no puede producir ni consumir una T