java - pila - ¿Cuál es un ejemplo de genérico<en la vida real? super T>?
pila generica java (9)
Entiendo que
<? super T>
<? super T>
representa cualquier super clase de
T
(clase padre de
T
de cualquier nivel).
Pero realmente me cuesta imaginar un ejemplo de la vida real para este comodín genérico.
Entiendo que
<? super T>
<? super T>
significa y he visto este método:
public class Collections {
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (int i = 0; i < src.size(); i++)
dest.set(i, src.get(i));
}
}
Estoy buscando un ejemplo de un caso de uso en la vida real donde se pueda usar esta construcción y no una explicación de lo que es.
Algunos ejemplos de la vida real vienen a la mente para esto. El primero que me gusta mencionar es la idea de un objeto del mundo real que se utiliza para la funcionalidad "improvisada". Imagina que tienes una llave de tubo:
public class SocketWrench <T extends Wrench>
El propósito obvio de una llave de cubo es usarla como
Wrench
.
Sin embargo, si considera que una llave podría usarse en un pellizco para golpear un clavo, podría tener una jerarquía de herencia que se parece a esto:
public class SocketWrench <T extends Wrench>
public class Wrench extends Hammer
En este escenario, podría llamar a
socketWrench.pound(Nail nail = new FinishingNail())
, aunque eso se consideraría un uso atípico para un
SocketWrench
.
Mientras tanto,
SocketWrench
tendría acceso para poder llamar a métodos como
applyTorque(100).withRotation("clockwise").withSocketSize(14)
si se está utilizando como
SocketWrench
lugar de solo como una
Wrench
, en lugar de un
Hammer
.
Considere este ejemplo simple:
List<Number> nums = Arrays.asList(3, 1.2, 4L);
Comparator<Object> numbersByDouble = Comparator.comparing(Object::toString);
nums.sort(numbersByDouble);
Esperemos que este sea un caso un tanto convincente: se podría imaginar querer ordenar los números para propósitos de visualización (para los cuales el toString es un orden razonable), pero el
Number
no es en sí mismo comparable.
Esto se compila porque
integers::sort
toma un
Comparator<? super E>
Comparator<? super E>
.
Si solo tomó un
Comparator<E>
(donde
E
en este caso es
Number
), entonces el código no se compilará porque el
Comparator<Object>
del
Comparator<Number>
no es un subtipo del
Comparator<Number>
del
Comparator<Number>
(debido a razones que su pregunta ya indica que entiendo, por lo que no voy a entrar).
Di que tienes:
class T {}
class Decoder<T>
class Encoder<T>
byte[] encode(T object, Encoder<? super T> encoder); // encode objects of type T
T decode(byte[] stream, Decoder<? extends T> decoder); // decode a byte stream into a type T
Y entonces:
class U extends T {}
Decoder<U> decoderOfU;
decode(stream, decoderOfU); // you need something that can decode into T, I give you a decoder of U, you''ll get U instances back
Encoder<Object> encoderOfObject;
encode(stream, encoderOfObject);// you need something that can encode T, I give you something that can encode all the way to java.lang.Object
El ejemplo más fácil que se me ocurre es:
public static <T extends Comparable<? super T>> void sort(List<T> list) {
list.sort(null);
}
Tomado de las mismas
Collections
.
De esta manera, un
Dog
puede implementar un
Comparable<Animal>
y, si
Animal
ya lo implementa, el
Dog
no tiene que hacer nada.
EDITAR para un ejemplo real:
Después de algunos ping-pongs de correo electrónico, puedo presentar un ejemplo real de mi lugar de trabajo (¡yay!).
Tenemos una interfaz llamada
Sink
(no importa lo que haga), la idea es que se
acumula
cosas.
La declaración es bastante trivial (simplificada):
interface Sink<T> {
void accumulate(T t);
}
Obviamente, hay un método auxiliar que toma una
List
y drena sus elementos a un
Sink
(es un poco más complicado, pero para hacerlo simple):
public static <T> void drainToSink(List<T> collection, Sink<T> sink) {
collection.forEach(sink::accumulate);
}
Esto es simple verdad? Bien...
Puedo tener una
List<String>
, pero quiero transferirla a un
Sink<Object>
: es algo bastante común para nosotros;
pero esto va a fallar:
Sink<Object> sink = null;
List<String> strings = List.of("abc");
drainToSink(strings, sink);
Para que esto funcione, necesitamos cambiar la declaración a:
public static <T> void drainToSink(List<T> collection, Sink<? super T> sink) {
....
}
Escribí un webradio, así que tuve la clase
MetaInformationObject
, que era la superclase para las listas de reproducción PLS y M3U.
Tuve un diálogo de selección, así que tuve:
public class SelectMultipleStreamDialog <T extends MetaInformationObject>
public class M3UInfo extends MetaInformationObject
public class PLSInfo extends MetaInformationObject
Esta clase tenía un método
public T getSelectedStream()
.
Así que la persona que llamó recibió una T que era del tipo concreto (PLS o M3U), pero necesitaba trabajar en la superclase, así que había una lista:
List<T super MetaInformationObject>
.
donde se agregó el resultado.
Así es como un diálogo genérico podría manejar las implementaciones concretas y el resto del código podría funcionar en la superclase.
Espero que eso lo haga un poco más claro.
Las colecciones sirven como un buen ejemplo aquí.
Como se indica en
1
,
List<? super T>
List<? super T>
permite crear una
List
que contendrá elementos de tipo, que son menos derivados que
T
, por lo que puede contener elementos que heredan de
T
, que son tipo de
T
y de los que
T
hereda.
Por otro lado,
List<? extends T>
List<? extends T>
permite definir una
List
que puede contener solo elementos que heredan de
T
(en algunos casos ni siquiera del tipo
T
).
Es un buen ejemplo:
public class Collections {
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (int i = 0; i < src.size(); i++)
dest.set(i, src.get(i));
}
}
Aquí desea proyectar
List
de tipo menos derivado a
List
de tipo menos derivado.
Aquí
List<? super T>
List<? super T>
nos asegura que todos los elementos de
src
serán válidos en la nueva colección.
Por ejemplo, observe la implementación del método Collections.addAll:
public static <T> boolean addAll(Collection<? super T> c, T... elements) {
boolean result = false;
for (T element : elements)
result |= c.add(element);
return result;
}
Aquí, los elementos se pueden insertar en cualquier colección cuyo tipo de elemento sea un supertipo del tipo
T
del elemento.
Sin un comodín acotado más bajo:
public static <T> boolean addAll(Collection<T> c, T... elements) { ... }
lo siguiente hubiera sido inválido:
List<Number> nums = new ArrayList<>();
Collections.<Integer>addAll(nums , 1, 2, 3);
porque el término
Collection<T>
es más restrictivo que
Collection<? super T>
Collection<? super T>
.
Otro ejemplo:
Interfaz de
Predicate<T>
en Java, que utiliza un
<? super T>
<? super T>
comodín en los siguientes métodos:
default Predicate<T> and(Predicate<? super T> other);
default Predicate<T> or(Predicate<? super T> other);
<? super T>
<? super T>
permite encadenar una gama más amplia de predicados diferentes, por ejemplo:
Predicate<String> p1 = s -> s.equals("P");
Predicate<Object> p2 = o -> o.equals("P");
p1.and(p2).test("P"); // which wouldn''t be possible with a Predicate<T> as a parameter
Supongamos que tiene esta jerarquía de clases: Cat hereda de Mammal, que a su vez hereda de Animal.
List<Animal> animals = new ArrayList<>();
List<Mammal> mammals = new ArrayList<>();
List<Cat> cats = ...
Estas llamadas son válidas:
Collections.copy(animals, mammals); // all mammals are animals
Collections.copy(mammals, cats); // all cats are mammals
Collections.copy(animals, cats); // all cats are animals
Collections.copy(cats, cats); // all cats are cats
Pero estas llamadas no son válidas:
Collections.copy(mammals, animals); // not all animals are mammals
Collections.copy(cats, mammals); // not all mammals are cats
Collections.copy(cats, animals); // mot all animals are cats
Así que la firma del método simplemente asegura que usted copie desde una clase más específica (más baja en la jerarquía de herencia) a una clase más genérica (más arriba en la jerarquía de herencia), y no al revés.
Supongamos que tienes un método:
passToConsumer(Consumer<? super SubType> consumer)
luego llama a este método con cualquier
Consumer
que pueda consumir
SubType
:
passToConsumer(Consumer<SuperType> superTypeConsumer)
passToConsumer(Consumer<SubType> subTypeConsumer)
passToConsumer(Consumer<Object> rootConsumer)
Por ejemplo:
class Animal{}
class Dog extends Animal{
void putInto(List<? super Dog> list) {
list.add(this);
}
}
Así que puedo poner el
Dog
en la
List<Animal>
o en la
List<Dog>
:
List<Animal> animals = new ArrayList<>();
List<Dog> dogs = new ArrayList<>();
Dog dog = new Dog();
dog.putInto(dogs); // OK
dog.putInto(animals); // OK
Si cambia el
putInto(List<? super Dog> list)
a
putInto(List<Animal> list)
:
Dog dog = new Dog();
List<Dog> dogs = new ArrayList<>();
dog.putInto(dogs); // compile error, List<Dog> is not sub type of List<Animal>
o
putInto(List<Dog> list)
:
Dog dog = new Dog();
List<Animal> animals = new ArrayList<>();
dog.putInto(animals); // compile error, List<Animal> is not sub type of List<Dog>