tipos programacion pila parametrizadas metodos interfaz genericos generica datos clases arreglos java generics type-inference

java - programacion - Parámetro genérico: solo el operador de diamante parece funcionar



programacion generica java (2)

Antecedentes: la pregunta surgió en esta respuesta (la primera revisión de la respuesta, para ser exactos). El código presentado en esta pregunta se reduce al mínimo para explicar el problema.

Supongamos que tenemos el siguiente código:

public class Sample<T extends Sample<T>> { public static Sample<? extends Sample<?>> get() { return new Sample<>(); } public static void main(String... args) { Sample<? extends Sample<?>> sample = Sample.get(); } }

Se compila sin previo aviso y se ejecuta bien. Sin embargo, si se intenta definir de alguna manera el tipo inferido de return new Sample<>(); En get() explícitamente el compilador se queja.

Hasta ahora, tenía la impresión de que el operador de diamante es solo un poco de azúcar sintáctica para no escribir tipos explícitos y, por lo tanto, siempre podría ser reemplazado por algún tipo explícito. Para el ejemplo dado, no pude definir ningún tipo explícito para que el valor de retorno compile el código. ¿Es posible definir explícitamente el tipo genérico del valor de retorno o se necesita el operador de diamante en este caso?

A continuación se muestran algunos intentos que hice para definir explícitamente el tipo genérico del valor devuelto con los errores de compilación correspondientes.

return new Sample<Sample> en:

Sample.java:6: error: type argument Sample is not within bounds of type-variable T return new Sample<Sample>(); ^ where T is a type-variable: T extends Sample<T> declared in class Sample Sample.java:6: error: incompatible types: Sample<Sample> cannot be converted to Sample<? extends Sample<?>> return new Sample<Sample>(); ^

return new Sample<Sample<?>> en:

Sample.java:6: error: type argument Sample<?> is not within bounds of type-variable T return new Sample<Sample<?>>(); ^ where T is a type-variable: T extends Sample<T> declared in class Sample

return new Sample<Sample<>>(); resultados en:

Sample.java:6: error: illegal start of type return new Sample<Sample<>>(); ^


Creando genéricos con comodines

Aquí hay un par de problemas, pero antes de profundizar en ellos, permítame abordar su pregunta real:

¿Es posible definir explícitamente el tipo genérico del valor de retorno o se necesita el operador de diamante en este caso?

¿No es posible crear una instancia explícita de una Sample<? extends Sample<?>> Sample<? extends Sample<?>> (o una Sample<?> para el caso). Los comodines no se pueden usar como argumentos de tipo al crear una instancia de un tipo genérico, aunque pueden estar anidados dentro de los argumentos de tipo. Por ejemplo, si bien es legal crear una instancia de una ArrayList<Sample<?>> , no puede crear una instancia de una ArrayList<?> .

La solución más obvia sería simplemente devolver algún otro tipo concreto que sea asignable a la Sample<?> . Por ejemplo:

class Sample<T extends Sample<T>> { static class X extends Sample<X> {} public static Sample<? extends Sample<?>> get() { return new X(); } }

Sin embargo, si desea devolver específicamente una instanciación genérica de la clase Sample<> contiene comodines, debe confiar en la inferencia genérica para elaborar los argumentos de tipo para usted. Hay algunas maneras de hacerlo, pero generalmente involucra uno de los siguientes:

  1. Usando el operador de diamante, como lo está haciendo ahora.
  2. Delegación a un método genérico que captura su comodín con una variable de tipo.

Si bien no puede incluir un comodín directamente en una instanciación genérica, es perfectamente legal incluir una variable de tipo, y eso es lo que hace posible la opción (2). Todo lo que tenemos que hacer es asegurarnos de que la variable de tipo en el método de delegado esté vinculada a un comodín en el sitio de la llamada. Cada mención de la variable de tipo, la firma y el cuerpo del método, se reemplaza con una referencia a ese comodín. Por ejemplo:

public class Sample<T extends Sample<T>> { public static Sample<? extends Sample<?>> get() { final Sample<?> s = get0(); return s; } private static <T extends Sample<T>> Sample<T> get0() { return new Sample<T>(); } }

Aquí, el tipo de retorno de la Sample<T> get0() se amplía a la Sample<WC#1 extends Sample<WC#1>> , donde WC#1 representa una copia capturada del comodín inferido del objetivo de asignación en la Sample<?> s = get0() .

Múltiples comodines en una firma de tipo

Ahora, vamos a abordar esa firma de método tuya. Es difícil decirlo con certeza según el código que ha proporcionado, pero supongo que es un tipo de Sample<? extends Sample<?>> de devolución Sample<? extends Sample<?>> Sample<? extends Sample<?>> es * no * lo que realmente quieres. Cuando aparecen caracteres comodín en un tipo, cada uno es distinto de todos los demás . No hay ninguna imposición de que el primer comodín y el segundo comodín se refieran al mismo tipo.

Digamos que get() devuelve un valor de tipo X Si fue su intención asegurarse de que X amplíe la Sample<X> , entonces ha fallado. Considerar:

class Sample<T extends Sample<T>> { static class X extends Sample<X> {} static class Y extends Sample<X> {} public static Sample<? extends Sample<?>> get() { return new Y(); } public static void main(String... args) { Sample<?> s = Sample.get(); // legal (!) } }

En main , la variable s contiene un valor que es una Sample<X> y una Y , pero no una Sample<Y> . ¿Es eso lo que pretendías? Si no, sugiero que reemplace el comodín en su firma de método con una variable de tipo, luego deje que la persona que llama decida el argumento de tipo:

class Sample<T extends Sample<T>> { static class X extends Sample<X> {} static class Y extends Sample<X> {} public static <T extends Sample<T>> Sample<T> get() { /* ... */ } public static void main(String... args) { Sample<X> x = Sample.get(); // legal Sample<Y> y = Sample.get(); // NOT legal Sample<?> ww = Sample.get(); // legal Sample<?> wx = Sample.<X>get(); // legal Sample<?> wy = Sample.<Y>get(); // NOT legal } }

La versión anterior garantiza efectivamente que, para algún valor de retorno de tipo A , el valor devuelto extiende la Sample<A> . En teoría, incluso funciona cuando T está vinculada a un comodín. ¿Por qué? Se remonta a la captura de comodines :

En su método de get original, los dos comodines podrían terminar refiriéndose a diferentes tipos. En efecto, su tipo de devolución fue Sample<WC#1 extends Sample<WC#2> , donde WC#1 y WC#2 son comodines separados que no están relacionados de ninguna manera. Pero en el ejemplo anterior, la unión de T a un comodín lo captura , permitiendo que el mismo comodín aparezca en más de un punto. Por lo tanto, cuando T está vinculada a un comodín WC#1 , el tipo de retorno se expande a la Sample<WC#1 extends Sample<WC#1> . Recuerde, no hay forma de expresar ese tipo directamente en Java: solo puede hacerse confiando en la inferencia de tipos.

Ahora, dije que esto funciona con comodines en teoría . En la práctica, probablemente no podrá implementar get de tal manera que las restricciones genéricas sean ejecutables en tiempo de ejecución. Esto se debe al borrado de tipo : el compilador puede emitir una instrucción de classcast para verificar que el valor devuelto es, por ejemplo, una X y una Sample , pero no puede verificar que sea realmente una Sample<X> , porque todas las formas genéricas de Sample tienen el mismo tipo de tiempo de ejecución. Para argumentos de tipo concreto, el compilador generalmente puede evitar que se compile el código sospechoso, pero cuando se introducen comodines en la mezcla, las restricciones genéricas complejas se vuelven difíciles o imposibles de imponer. El comprador tenga cuidado :).

Aparte

Si todo esto le resulta confuso, no se preocupe: los comodines y la captura de comodines se encuentran entre los aspectos más difíciles de comprender de los genéricos de Java. Tampoco está claro si comprender esto realmente te ayudará con tu objetivo inmediato. Si tiene una API en mente, sería mejor enviarla al intercambio de pila de Revisión de Código y ver qué tipo de comentarios recibe.


El JLS simplemente dice:

Si la lista de argumentos de tipo para la clase está vacía, la forma de diamante <> , se deducen los argumentos de tipo de la clase.

Entonces, ¿hay alguna X inferida que satisfaga la solución? Sí.

Por supuesto, para que usted defina explícitamente tal X , tendría que declararla:

public static <X extends Sample<X>> Sample<? extends Sample<?>> get() { return new Sample<X>(); }

La Sample<X> explícita Sample<X> es compatible con el tipo de devolución Sample<? extends Sample<?>> Sample<? extends Sample<?>> , así que el compilador está contento.

El hecho de que el tipo de retorno sea una Sample<? extends Sample<?>> desordenada Sample<? extends Sample<?>> Sample<? extends Sample<?>> es una historia completamente diferente.