java - ejemplos - ¿Hay alguna diferencia al especificar explícitamente los límites superiores para los comodines?
django (1)
Tomando su pregunta inicial literalmente, si “¿Hay una diferencia notable” entre Generic<?>
Y Generic<? extends BaseType>
Generic<? extends BaseType>
, la respuesta debe ser, no son equivalentes.
JLS §4.5.1 establece claramente:
El comodín
? extends Object
? extends Object
es equivalente al comodín sin límites?
.
¿Entonces sería equivalente a ? extends BaseType
? extends BaseType
solo si BaseType
es Object
, pero incluso entonces, son equivalentes, pero aún tienen diferencias notables, por ejemplo, en lugares donde no se realiza la conversión de captura:
boolean b1 = new Object() instanceof Supplier<?>; // valid code
boolean b2 = new Object() instanceof Supplier<? extends Object>; // invalid
Supplier<?>[] array1; // valid declaration
Supplier<? extends Object>[] array1; // invalid
Podría valer la pena señalar que, contrariamente a la primera intuición, dada una declaración Generic<T extends BaseType>
, especificando Generic<? extends Object>
Generic<? extends Object>
es tan válido como el Generic<?>
equivalente Generic<?>
. Los límites al comodín son válidos siempre que no sean demostrablemente distintos al límite del parámetro de tipo y dado que los límites siempre son subtipos de Object
,? ? extends Object
siempre es válido.
Así que si tenemos una declaración de tipo como
interface NumberSupplier<N extends Number> extends Supplier<N> {}
podemos escribir
NumberSupplier<? extends Object> s1;
NumberSupplier<? extends Serializable> s2;
NumberSupplier<? extends BigInteger> s3;
o incluso
NumberSupplier<? extends CharSequence> s4;
Incluso podemos implementarlo sin un tipo real extendiendo Number
y CharSequence
usando () -> null
pero no
NumberSupplier<? extends String> s5;
como String
y Number
son demostrablemente distintos .
Cuando se trata de asignaciones, podemos usar las reglas de subtipo ya citadas en la pregunta para concluir que NumberSupplier<? extends BigInteger>
NumberSupplier<? extends BigInteger>
es un subtipo de NumberSupplier<? extends Object>
NumberSupplier<? extends Object>
, porque ? extends BigInteger
? extends BigInteger
contiene ? extends Object
? extends Object
(y también contiene ? extends Number
), porque BigInteger
es un subtipo de Object
y Number
, pero como se señaló correctamente, esto no se aplica a los tipos parametrizados cuyos argumentos de tipo no son comodines.
Entonces, si tenemos declaraciones como List<NumberSupplier<?>>
, List<NumberSupplier<? extends Object>>
List<NumberSupplier<? extends Object>>
, o List<NumberSupplier<? extends Number>>
List<NumberSupplier<? extends Number>>
y quiere razonar si es un subtipo de los demás de acuerdo con la regla de §4.5.1, la única regla que podría aplicarse es cuando los argumentos de tipo son del mismo tipo ( T <= T
), pero entonces, no necesitaríamos reglas de subtipo, como entonces, todos estos tipos de listas son del mismo tipo :
Dos tipos de referencia son el mismo tipo de tiempo de compilación si tienen el mismo nombre binario (§13.1) y sus argumentos de tipo, si los hay, son los mismos, aplicando esta definición de forma recursiva.
La regla de contenidos sigue siendo útil, por ejemplo, permite concluir que Map<String,? extends Number>
Map<String,? extends Number>
es un subtipo de Map<String,Integer>
, porque para el primer argumento de tipo, String <= String
aplica y los argumentos de tipo para el segundo parámetro de tipo están cubiertos por una regla de comodín específica para comodines.
Entonces, la pregunta restante es, ¿qué regla nos permite concluir que NumberSupplier<?>
, NumberSupplier<? extends Object>
NumberSupplier<? extends Object>
, o NumberSupplier<? extends Number>
NumberSupplier<? extends Number>
son los mismos tipos, de modo que List<NumberSupplier<?>>
, List<NumberSupplier<? extends Object>>
List<NumberSupplier<? extends Object>>
, o List<NumberSupplier<? extends Number>>
List<NumberSupplier<? extends Number>>
son asignables entre sí.
No parece ser una conversión de captura , ya que la conversión de captura implicaría calcular límites efectivos, pero también creará una "variable de tipo nuevo" para cada comodín que es definitivamente un tipo diferente. Pero no hay otra regla que cubra la compatibilidad con comodines. O no lo encontré. Tratar de hacer coincidir la especificación con el comportamiento real de javac
tuvo algunos resultados muy interesantes:
Dado
interface MySupplier<S extends CharSequence&Appendable> extends Supplier<S> {}
Las siguientes declaraciones son obviamente válidas:
List<MySupplier<? extends CharSequence>> list1 = Collections.emptyList();
List<MySupplier<? extends Appendable>> list2 = Collections.emptyList();
Como en ambos casos, el límite del comodín es redundante, ya que coincide con el límite de S
, podemos suponer que en realidad son del mismo tipo.
Pero javac
piensa que no lo son.
list1 = list2; // compiler error
list2 = list1; // dito
aunque cualquier operación que implique conversión de captura concluye tipos compatibles, por ejemplo,
list1.set(0, list2.get(0)); // no problem
list2.set(0, list1.get(0)); // no problem
Y haciendo las tareas rechazadas trabaja indirectamente:
List<MySupplier<?>> list3;
list3 = list1;
list2 = list3; // no problem
// or
list3 = list2;
list1 = list3; // again no problem
pero aquí, no es equivalente a ? extends Object
? extends Object
:
List<MySupplier<? extends Object>> list4;
list4 = list1; // compiler error
list2 = list4; // dito
// or
list4 = list2; // dito
list1 = list4; // dito
Pero de nuevo, las tareas indirectas funcionan.
list4 = list3 = list1; // works
list1 = list3 = list4; // works
list4 = list3 = list2; // works
list2 = list3 = list4; // works
Entonces, cualquiera sea la regla que javac
use aquí, no es transitiva, lo que descarta las relaciones de subtipo, así como una regla general de "es del mismo tipo". Parece que esto realmente no está especificado, y afecta directamente la implementación. Y, como se implementa actualmente,? Sin límites es especial, permitiendo una cadena de asignación que no es posible con ningún otro tipo de comodín.
Supongamos que tengo una class Generic<A extends BaseType>
.
¿Existe una diferencia notable, en lo que respecta a la Especificación del lenguaje Java, entre las siguientes dos declaraciones de tipo?
Generic<?>
Generic<? extends BaseType>
¿Qué pasa con los comodines anidados?
List<Generic<?>>
List<Generic<? extends BaseType>>
Pensando en esto, asumiría que estos son equivalentes. Generic
especifica que el parámetro de tipo A
tiene un tipo de base para un límite superior.
Por lo tanto, el comodín siempre debe estar limitado "automáticamente" o "implícitamente" por BaseType
, sin importar si lo especifico explícitamente o no.
A continuación, trato de conciliar mi intención con el JLS.
No pude encontrar información sobre límites "implícitos", así que empecé a observar las reglas de subtipo.
Leyendo la sección de JLS sobre los subtipos de $ 4.10.2 , dice:
Dada una declaración de tipo genérico
C<F1,...,Fn>
(n> 0), los supertipos directos del tipo parametrizadoC<T1,...,Tn>
, donde Ti (1 ≤ i ≤ n) es un tipo , son todos los siguientes:
D<U1 θ,...,Uk θ>
, dondeD<U1,...,Uk>
es un tipo genérico que es un supertipo directo del tipo genéricoC<T1,...,Tn>
y θ es la sustitución [F1: = T1, ..., Fn: = Tn].
C<S1,...,Sn>
, donde Si contiene Ti (1 ≤ i ≤ n) (§4.5.1).
(énfasis mío)
Por lo que entiendo, los "comodines" no se consideran "tipos" en el JLS. Por lo tanto, esto no se puede aplicar a los dos primeros, pero se aplicaría a los dos ejemplos de la List
.
En su lugar, esto debería aplicarse:
Dada una declaración de tipo genérico
C<F1,...,Fn>
(n> 0), los supertipos directos del tipo parametrizadoC<R1,...,Rn>
donde al menos uno de los Ri (1 ≤ i ≤ n) es un argumento de tipo comodín , son los supertipos directos del tipo parametrizadoC<X1,...,Xn>
que es el resultado de aplicar la conversión de captura aC<R1,...,Rn>
( §5.1.10 ).
(énfasis mío)
Aplicando la conversión de captura $ 5.1.10 a Generic<?>
Y Generic<? extends BaseType>
Generic<? extends BaseType>
; Creo que tengo los mismos límites en las variables de tipo fresco. Después de la conversión de captura, puedo usar las reglas "contenidas" para establecer el subtipo.
Para el primer ejemplo, a través de
Si Ti es un argumento de tipo comodín (§4.5.1) de la forma?, Entonces Si es una variable de tipo nueva cuyo límite superior es Ui [A1: = S1, ..., An: = Sn] y cuyo límite inferior es el tipo nulo (§4.1).
Dado que A 1 es BaseType
, la variable nueva tiene un límite superior de BaseType
.
Para el segundo caso, via
Si Ti es un argumento de tipo comodín de la forma? extiende Bi, luego Si es una nueva variable de tipo cuyo límite superior es glb (Bi, Ui [A1: = S1, ..., An: = Sn]) y cuyo límite inferior es el tipo nulo.
glb (V1, ..., Vm) se define como V1 & ... & Vm.
Obtengo glb(BaseType, BaseType)
, que, de nuevo, es BaseType
.
Entonces, parece que la relación de subtipo entre Generic<?>
Y Generic<? extends BaseType>
Generic<? extends BaseType>
va en ambos sentidos según el JLS, que coincide con mi intuición.
Para los comodines anidados, usaría la regla "contiene" :
Se dice que un argumento de tipo T1 contiene otro argumento de tipo T2, escrito T2 <= T1, si el conjunto de tipos denotado por T2 es probablemente un subconjunto del conjunto de tipos denotado por T1 bajo el cierre reflexivo y transitivo de las siguientes reglas ( donde <: denota subtipo (§4.10)):
? extends T <= ? extends S if T <: S
? extends T <= ?
? super T <= ? super S if S <: T
? super T <= ?
? super T <= ? extends Object
T <= T
T <= ? extends T
T <= ? super T
Combinado con
C<S1,...,Sn>, where Si contains Ti (1 ≤ i ≤ n) (§4.5.1).
Desde arriba, obtengo:
List<Generic<?>>
es un supertipo directo de List<Generic<? extends BaseType>>
List<Generic<? extends BaseType>>
si Generic<?>
contiene Generic<? extends BaseType>>
Generic<? extends BaseType>>
Aunque, no veo cómo uso la regla de contenidos. La única información adicional que puedo usar es el subtipo, de acuerdo con las reglas. Ya sé que los subtipos van en ambos sentidos entre los dos tipos.
Aunque, si la respuesta está contenida con subtipos entre los dos, también podría mostrar que List<String>
es un subtipo de List<Object>
que no es ni debería ser.
Además, necesito mostrar algo del tipo Type <= OtherType
y la única regla con el lado derecho del formulario "type" es T <= T
, por lo que estas reglas no parecen ayudar en absoluto.
¿Cómo obtengo esa List<Generic<?>>
y List<Generic<? extends BaseType>>
List<Generic<? extends BaseType>>
son subtipos entre sí a través de JLS?