Что такое реальный пример родового <? супер T>? - PullRequest
0 голосов
/ 05 сентября 2018

Я понимаю, что <? super T> представляет любой суперкласс T (родительский класс T любого уровня). Но я действительно изо всех сил пытаюсь представить какой-нибудь реальный пример из жизни с этим общим подстановочным знаком.

Я понимаю, что означает <? super T>, и я видел этот метод:

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

Я ищу пример реального варианта использования , где можно использовать эту конструкцию, а не объяснение того, что это такое.

Ответы [ 9 ]

0 голосов
/ 05 сентября 2018

Рассмотрим этот простой пример:

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

Надеюсь, это несколько убедительный случай: вы можете представить, что хотите отсортировать числа для целей отображения (для которых toString - разумный порядок), но Number само по себе не является сопоставимым.

Компилируется, потому что integers::sort занимает Comparator<? super E>. Если бы потребовалось всего Comparator<E> (где E в данном случае Number), то код не сможет скомпилироваться, поскольку Comparator<Object> не является подтипом Comparator<Number> (по причинам, указанным в вашем вопросе уже понимаю, поэтому не буду вдаваться).

0 голосов
/ 06 сентября 2018

Коллекции служат хорошим примером здесь.

Как указано в 1 , List<? super T> позволяет вам создать List, который будет содержать элементы типа, которые являются менее производными, чем T, поэтому он может содержать элементы, которые наследуются от T , это тип T и T наследуется от.

С другой стороны, List<? extends T> позволяет вам определить List, который может содержать только элементы, которые наследуются от T (в некоторых случаях даже не типа T).

Это хороший пример:

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

Здесь вы хотите проецировать List менее производного типа на List менее производного типа. Здесь List<? super T> заверяет нас, что все элементы из src будут действительны в новой коллекции.

1 : Разница между <? супер T> и <? расширяет T> в Java

0 голосов
/ 05 сентября 2018

Несколько реальных примеров приходят на ум для этого. Первое, что мне нравится поднимать, это идея объекта реального мира, используемого для «импровизированной» функциональности. Представьте, что у вас есть торцевой гаечный ключ:

public class SocketWrench <T extends Wrench>

Очевидное назначение торцевого ключа, поэтому его следует использовать как Wrench. Однако, если вы считаете, что ключ может быть использован в крайнем случае, чтобы колотить в гвоздь, у вас может быть иерархия наследования, которая выглядит следующим образом:

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

В этом сценарии вы сможете вызвать socketWrench.pound(Nail nail = new FinishingNail()), даже если это будет считаться нетипичным использованием для SocketWrench.

Несмотря на это, SocketWrench будет иметь возможность вызывать методы, подобные applyTorque(100).withRotation("clockwise").withSocketSize(14), если он используется как SocketWrench вместо Wrench вместо Hammer.

0 голосов
/ 05 сентября 2018

Например, посмотрите на метод Collections.addAll implmenetation:

public static <T> boolean addAll(Collection<? super T> c, T... elements) {
    boolean result = false;
    for (T element : elements)
        result |= c.add(element);
    return result;
}

Здесь элементы могут быть вставлены в любую коллекцию, тип элемента которой является супертипом типа T элемента.

Без подстановочных знаков снизу:

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

следующее недействительно:

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

потому что термин Collection<T> является более ограничительным, чем Collection<? super T>.


Другой пример:

Predicate<T> интерфейс в Java, который использует подстановочный знак <? super T> в следующих методах:

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

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

<? super T> позволяет связывать более широкий диапазон различных предикатов, например:

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
0 голосов
/ 05 сентября 2018

Самый простой пример, который я могу вспомнить:

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

взято из того же Collections. Таким образом, Dog может реализовать Comparable<Animal>, и если Animal уже реализует это, Dog не должен ничего делать.

РЕДАКТИРОВАТЬ для реального примера:

После некоторых электронных пинг-понгов мне разрешено представить реальный пример с моего рабочего места (ууу!).

У нас есть интерфейс под названием Sink (не имеет значения, что он делает), идея в том, что накапливает вещей. Объявление довольно тривиально (упрощенно):

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

Очевидно, что есть вспомогательный метод, который принимает List и сливает его элементы в Sink (это немного сложнее, но для простоты):

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

Это просто верно? Ну ...

У меня может быть List<String>, но я хочу использовать его до Sink<Object> - это довольно распространенная вещь для нас; но это не удастся:

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

Чтобы это работало, нам нужно изменить объявление на:

public static <T> void drainToSink(List<T> collection, Sink<? super T> sink) {
    ....
}
0 голосов
/ 05 сентября 2018

Скажем, у вас есть:

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

А потом:

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 голосов
/ 05 сентября 2018

Предположим, у вас есть иерархия классов: Кошка наследуется от Млекопитающего, которое, в свою очередь, наследуется от Животного.

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

Эти звонки действительны:

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 

Но эти звонки не действительны:

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

Таким образом, сигнатура метода просто гарантирует, что вы копируете из более определенного (более низкого в иерархии наследования) класса в более общий класс (более высокий в иерархии наследования), а не наоборот.

0 голосов
/ 05 сентября 2018

Я написал веб-радио, поэтому у меня был класс MetaInformationObject, который был суперклассом для плейлистов PLS и M3U. У меня был диалог выбора, поэтому у меня было:

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

У этого класса был метод public T getSelectedStream().
Таким образом, вызывающий абонент получил T, который имел конкретный тип (PLS или M3U), но должен был работать с суперклассом, поэтому был список: List<T super MetaInformationObject>. где был добавлен результат.
Вот как общий диалог может обрабатывать конкретные реализации, а остальная часть кода может работать на суперклассе.
Надеюсь, это сделает это немного яснее.

0 голосов
/ 05 сентября 2018

Предположим, у вас есть метод:

passToConsumer(Consumer<? super SubType> consumer)

тогда вы вызываете этот метод с любым Consumer, который может потреблять SubType:

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

Например:

class Animal{}

class Dog extends Animal{

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

Так что я могу поместить Dog в List<Animal> или List<Dog>:

List<Animal> animals = new ArrayList<>();
List<Dog> dogs = new ArrayList<>();

Dog dog = new Dog();
dog.putInto(dogs);  // OK
dog.putInto(animals);   // OK

Если вы измените метод putInto(List<? super Dog> list) на 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>

или 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>
...