java tipos ¿Cuál es un ejemplo de genérico<en la vida real? super T>?




tipos de datos genericos (8)

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.


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

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.


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).


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>

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) {
    ....
}

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 .


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.


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




super