web-dev-qa-db-fra.com

Qu'est-ce qu'un exemple réel de générique <? super T>?

Je comprends que <? super T> représente une super classe de T (classe parente de T de tout niveau). Mais j'ai vraiment du mal à imaginer un exemple concret pour ce générique lié générique.

Je comprends ce que <? super T> signifie et j'ai vu cette méthode:

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));
  }
}

Je cherche un exemple de cas d'utilisation réelle où cette construction peut être utilisée et non pour une explication de ce que c'est.

73
BanzaiTokyo

L'exemple le plus simple auquel je puisse penser est:

public static <T extends Comparable<? super T>> void sort(List<T> list) {
    list.sort(null);
}

extrait du même Collections. De cette manière, un Dog peut implémenter Comparable<Animal> et si Animal implémente déjà que, Dog ne doit rien faire.

EDIT pour un exemple réel:

Après quelques courriels de ping-pong, je suis autorisé à présenter un exemple réel tiré de mon lieu de travail (yay!).

Nous avons une interface appelée Sink (peu importe ce qu’elle fait), l’idée est que accumule des choses. La déclaration est assez triviale (simplifiée):

interface Sink<T> {
    void accumulate(T t);
}

Évidemment, il existe une méthode d'assistance qui prend un List et draine ses éléments en un Sink (c'est un peu plus compliqué, mais pour simplifier):

public static <T> void drainToSink(List<T> collection, Sink<T> sink) {
    collection.forEach(sink::accumulate);
}

C'est simple non? Bien...

Je peux avoir un List<String>, mais je veux l'asservir à un Sink<Object> - c'est une chose assez commune à faire pour nous; mais cela échouera:

Sink<Object> sink = null;
List<String> strings = List.of("abc");
drainToSink(strings, sink);

Pour que cela fonctionne, nous devons modifier la déclaration en:

public static <T> void drainToSink(List<T> collection, Sink<? super T> sink) {
    ....
}
46
Eugene

Supposons que vous ayez cette hiérarchie de classes: Cat hérite de Mammal, qui hérite à son tour d’Animal.

List<Animal> animals = new ArrayList<>();
List<Mammal> mammals = new ArrayList<>();
List<Cat> cats = ...

Ces appels sont valables:

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 

Mais ces appels sont not valides:

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

Ainsi, la signature de la méthode garantit simplement que vous copiez d'une classe plus spécifique (inférieure dans la hiérarchie d'héritage) vers une classe plus générique (supérieure dans la hiérarchie d'héritage), et non l'inverse.

15
Benoit

Par exemple, examinez la méthode 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;
}
_

Ici, les éléments peuvent être insérés dans toute collection dont le type d'élément est un sur-type du type T de l'élément.

Sans caractère générique de limite inférieure:

_public static <T> boolean addAll(Collection<T> c, T... elements) { ... }
_

les éléments suivants auraient été invalides:

_List<Number> nums = new ArrayList<>();
Collections.<Integer>addAll(nums , 1, 2, 3);
_

parce que le terme _Collection<T>_ est plus restrictif que _Collection<? super T>_.


Autre exemple:

Predicate<T> interface en Java, qui utilise un caractère générique _<? super T>_ dans les méthodes suivantes:

_default Predicate<T> and(Predicate<? super T> other);

default Predicate<T>  or(Predicate<? super T> other);
_

_<? super T>_ permet de chaîner un plus large éventail de prédicats différents, par exemple:

_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
_
5
Oleksandr Pyrohov

Supposons que vous ayez une méthode:

passToConsumer(Consumer<? super SubType> consumer)

alors vous appelez cette méthode avec n'importe quel Consumer pouvant consommer SubType:

passToConsumer(Consumer<SuperType> superTypeConsumer)
passToConsumer(Consumer<SubType> subTypeConsumer)
passToConsumer(Consumer<Object> rootConsumer)

Par exemple:

class Animal{}

class Dog extends Animal{

    void putInto(List<? super Dog> list) {
        list.add(this);
    }
}

Je peux donc mettre la Dog dans List<Animal> ou 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 vous changez la méthode putInto(List<? super Dog> list) en 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>

ou 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>
3
xingbin

Considérons cet exemple simple:

List<Number> nums = Arrays.asList(3, 1.2, 4L);
Comparator<Object> numbersByDouble = Comparator.comparing(Object::toString);
nums.sort(numbersByDouble);

Espérons que ceci est un cas assez convaincant: vous pouvez imaginer vouloir trier les nombres à des fins d'affichage (pour lesquelles la chaîne de chaînes est un ordre raisonnable), mais Number n'est pas en soi comparable.

Ceci est compilé car integers::sort prend un Comparator<? super E>. Si cela ne prenait que Comparator<E> (où E dans ce cas est Number), le code ne pourrait pas être compilé car Comparator<Object> n'est pas un sous-type de Comparator<Number> (pour des raisons que votre question indique que vous comprenez déjà, je n'entrerai pas dans).

1
yshavit

J'ai écrit une webradio et j'avais donc la classe MetaInformationObject, qui était la super-classe des listes de lecture PLS et M3U. J'ai eu un dialogue de sélection, donc j'ai eu:

public class SelectMultipleStreamDialog <T extends MetaInformationObject>
public class M3UInfo extends MetaInformationObject
public class PLSInfo extends MetaInformationObject

Cette classe avait une méthode public T getSelectedStream().
L'appelant a donc reçu un T qui était du type concret (PLS ou M3U), mais devait travailler sur la superclasse. Il y avait donc une liste: List<T super MetaInformationObject>. où le résultat a été ajouté.
C’est ainsi qu’un dialogue générique pourrait gérer les implémentations concrètes et que le reste du code pourrait fonctionner dans la super-classe.
Espérons que cela rend un peu plus clair.

1
ItFreak

Les collections sont un bon exemple ici.

Comme indiqué dans 1 , List<? super T> vous permet de créer List qui contiendra des éléments de type, moins dérivés que T, afin de pouvoir contenir des éléments hérités de T, qui sont du type T et dont T hérite de.

Par contre, List<? extends T> vous permet de définir une List pouvant uniquement contenir des éléments héritant de T (dans certains cas, pas même du type T).

C'est un bon exemple:

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));
  }
}

Ici, vous voulez projeter List de type moins dérivé sur List de type moins dérivé. Ici, List<? super T> nous assure que tous les éléments de src seront valides dans la nouvelle collection.

1 : Différence entre <? Super T> et <? Étend T> en Java

1
Michał Turczyn

Quelques exemples concrets me viennent à l’esprit. Le premier que j'aime aborder est l'idée qu'un objet du monde réel est utilisé pour une fonctionnalité "improvisée". Imaginez que vous ayez une clé à douille:

public class SocketWrench <T extends Wrench>

Le but évident d'une clé à douille est qu'il soit utilisé comme un Wrench. Cependant, si vous considérez qu'une clé peut être utilisée pour pincer un clou, vous pouvez avoir une hiérarchie d'héritage qui ressemble à ceci:

public class SocketWrench <T extends Wrench>
public class Wrench extends Hammer

Dans ce scénario, vous pourrez appeler socketWrench.pound(Nail nail = new FinishingNail()), même si cela est considéré comme une utilisation atypique pour un SocketWrench.

Pendant tout ce temps, la SocketWrench aurait accès à des méthodes telles que applyTorque(100).withRotation("clockwise").withSocketSize(14) si elle était utilisée en tant que SocketWrench au lieu d'un Wrench, au lieu d'un Hammer.

0
John Stark

Disons que vous avez:

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

Puis:

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
0
memo