Что такое пример реальной жизни generic
Я понимаю, что <? 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 ответов:
самый простой пример, который я могу думать, это:
public static <T extends Comparable<? super T>> void sort(List<T> list) { list.sort(null); }
взяты из той же
Collections
. Таким образомDog
можно реализоватьComparable<Animal>
и еслиAnimal
уже реализует, чтоDog
не нужно ничего делать.редактировать для реального примера:
после некоторых электронных пинг-понгов мне разрешено представить реальный пример с моего рабочего места (yay!).
у нас есть интерфейс, который называется
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) { .... }
Предположим, у вас есть эта иерархия классов: Кошка наследует от млекопитающего, которое в свою очередь наследует от животного.
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
например, посмотрите в
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>
wildcard в следующих способов: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
Предположим, у вас есть способ:
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>
Я написал webradio, так что у меня был класс
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>
. где результат был добавлен.
Вот как общий диалог может обрабатывать конкретные реализации, а остальная часть кода может работа над суперклассом.
Надеюсь, что это делает его немного более ясным.
простой пример:
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>
(из-за причины, по которым ваш вопрос указывает, что вы уже понимаете, поэтому я не буду вдаваться в подробности).
коллекции служат хорошим примером здесь.
как говорится в 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
будет действовать в новой коллекции.
скажем, у вас есть:
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
несколько примеров из реальной жизни приходят в голову для этого. Первое, что мне нравится поднимать,-это идея использования реального объекта для "импровизированной" функциональности. Представьте, что у вас есть торцевой ключ:
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
.