programacion - ¿Por qué un parámetro de tipo Java no puede tener un límite inferior?
new icon java (5)
Deduzco que no puede vincular un parámetro de tipo genérico de Java a un límite inferior (es decir, utilizando la palabra clave super
). Estaba leyendo lo que las preguntas frecuentes de Angelika Langer Generics tenían que decir sobre el tema . Dicen que básicamente se reduce a un límite inferior que es inútil ("no tiene sentido").
No estoy convencido. Puedo imaginar un uso para ellos para ayudarlo a ser más flexible con las personas que llaman de un método de biblioteca que produce un resultado mecanografiado. Imagine un método que crea una lista de matriz de un tamaño especificado por el usuario y lo llena con la cadena vacía. Una simple declaración sería
public static ArrayList<String> createArrayListFullOfEmptyStrings(int i);
Pero eso es innecesariamente restrictivo para sus clientes. ¿Por qué no pueden invocar su método de esta manera?
//should compile
List<Object> l1 = createArrayListFullOfEmptyStrings(5);
List<CharSequence> l2 = createArrayListFullOfEmptyStrings(5);
List<String> l3 = createArrayListFullOfEmptyStrings(5);
//shouldn''t compile
List<Integer> l4 = createArrayListFullOfEmptyStrings(5);
En este punto, estaría tentado de intentar la siguiente definición:
public static <T super String> List<T> createArrayListFullOfEmptyStrings(int size) {
List<T> list = new ArrayList<T>(size);
for(int i = 0; i < size; i++) {
list.add("");
}
return list;
}
Pero no compilará; la palabra clave super
es ilegal en este contexto.
¿Es mi ejemplo anterior un mal ejemplo (ignorando lo que digo a continuación)? ¿Por qué no es útil un límite inferior aquí? Y si fuera útil, ¿cuál es la verdadera razón por la que no está permitido en Java?
PD
Sé que una mejor organización podría ser algo como esto:
public static void populateListWithEmptyStrings(List<? super String> list, int size);
List<CharSequence> list = new ArrayList<CharSequence>();
populateListWithEmptyStrings(list, 5);
¿Podemos a los fines de esta pregunta pretender que, debido a un requisito, tenemos que hacer ambas operaciones en una llamada a un método?
Editar
@Tom G (justificadamente) pregunta qué beneficio tendría tener una List<CharSequence>
sobre una List<String>
. Por un lado, nadie dijo que la lista devuelta es inmutable, así que aquí hay una ventaja:
List<CharSequence> l2 = createArrayListFullOfEmptyStrings(5);
l2.add(new StringBuilder("foo").append("bar"));
¿Qué ventaja le da tipear la Lista en ese punto? Cuando itere sobre la colección devuelta, aún debería poder hacer lo siguiente:
for(String s : returnedList) {
CharSequence cs = s;
//do something with your CharSequence
}
Hmm, vale, trabajemos con esto. Usted define un método:
public static <T super String> List<T> createArrayListFullOfEmptyStrings(int size) {
Qué significa eso? Significa que si llamo a su método, entonces obtengo una lista de alguna superclase de String. Tal vez devuelva una lista de String. Tal vez devuelve una lista de Object. No lo sé.
Guay.
List<Object> l1 = createArrayListFullOfEmptyStrings(5);
Según usted, eso debería compilarse. ¡Pero eso no está bien! Puedo poner un Entero en una lista de Objeto - l1.add(3)
. Pero si devuelve una lista de String, entonces hacerlo debería ser ilegal.
List<String> l3 = createArrayListFullOfEmptyStrings(5);
Según usted, eso debería compilarse. ¡Pero eso no está bien! l3.get(1)
debería devolver siempre una Cadena ... pero ese método podría haber devuelto una lista de Objeto, lo que significa que l3.get (1) podría ser un Entero.
Lo único que funciona es
List<? super String> l5 = createArrayListFullOfEmptyStrings(5);
Todo lo que sé es que puedo llamar con seguridad a l4.put("foo")
, y puedo obtener Object o = l4.get(2)
forma segura.
Más que una respuesta, este es otro caso de uso (¿posiblemente asesino?). Tengo un ayudante de ModelDecorator. Quiero que tenga la siguiente API pública
class ModelDecorator<T>{
public static <T> ModelDecorator<T> create(Class<T> clazz);
public <SUPER> T from(SUPER fromInstance);
}
Entonces, dadas las clases A, B se extiende A, se puede usar así:
A a = new A();
B b = ModelDecorator.create(B.class).from(a);
Pero quiero tener límites en T y SUPER, así que me aseguro de que solo las subclases puedan ser instanciadas usando la API. En este momento, puedo hacer:
C c = new C();
B b = ModelDecorator.create(B.class).from(c);
Donde B NO hereda de C.
Obviamente, si pudiera:
public <SUPER super T> T from(SUPER fromInstance);
Eso resolvería mi problema.
la especificación habla de límites inferiores de parámetros de tipo, por ejemplo
4.10.2
una variable de tipo es un supertipo directo de su límite inferior.
5.1.10
una variable tipo nueva ... cuyo límite inferior
Parece que una variable de tipo solo tiene un límite inferior (no nulo) si es sintético como resultado de la captura de comodines. ¿Qué pasa si el lenguaje permite límites más bajos en todos los parámetros de tipo? Probablemente no cause muchos problemas, y se excluye solo para mantener los genéricos más sencillos (bueno ...) Actualización Se dice que la investigación teórica de los parámetros de tipo de límite inferior no se realiza a fondo.
Actualización: un documento que afirma que los límites inferiores están bien: "El tipo de interfaz de Java está roto: ¿podemos arreglarlo?" Por Daniel Smith
RETRACTAR: el siguiente argumento es incorrecto. El ejemplo de OP es legítimo.
Tu ejemplo particular no es muy convincente. Primero no es seguro. La lista devuelta es de hecho una List<String>
, no es seguro verla como otro tipo. Supongamos que su código compila:
List<CharSequence> l2 = createArrayListFullOfEmptyStrings(5);
entonces podemos agregarle no String, lo cual es incorrecto
CharSequence chars = new StringBuilder();
l2.add(chars);
Bueno, una List<String>
no es, pero algo así como una lista de CharSequence. Su necesidad se puede resolver usando comodines:
public static List<String> createArrayListFullOfEmptyStrings(int size)
// a list of some specific subtype of CharSequence
List<? extends CharSequence> l2 = createArrayListFullOfEmptyStrings(5);
// legal. can retrieve elements as CharSequence
CharSequence chars = l2.get(0);
// illegal, won''t compile. cannot insert elements as CharSequence
l2.add(new StringBuilder());
Básicamente, no es lo suficientemente útil.
Creo que su ejemplo señala la única ventaja de un límite inferior, una característica que las preguntas frecuentes llaman Restricted Instantiation
:
La conclusión es: todo lo que un límite "súper" le compraría es la restricción de que solo se pueden usar supertipos de Número como argumentos de tipo . ....
Pero como señalan las otras publicaciones, la utilidad de incluso esta característica puede ser limitada.
Debido a la naturaleza del polimorfismo y la especialización, los límites superiores son mucho más útiles que los límites inferiores como se describe en las preguntas más frecuentes ( acceso a miembros no estáticos y borrado de tipos ). Sospecho que la complejidad introducida por los límites inferiores no vale su valor limitado.
OP: Quiero agregar Creo que sí mostró que es útil, pero no lo suficientemente útil. Vamos con los casos de uso de asesinos irrefutables y respaldaré el JSR. :-)