Полезный пример с супер и безвестностью с расширением в Generics?
Я знаю, что есть много вопросов по этой теме, но, к сожалению, они не могут помочь мне устранить мои неясности. Прежде всего, посмотрите на следующий пример. Я не понимаю, почему следующий "add" -method someCage.add(rat1)
не работает и прерывается со следующим исключением:
Исключение в потоке "main" java.lang.Error: неразрешенная компиляция проблема: метод add (capture # 2-of? extends Animal) в типе Клетка не применяется для аргументы (Rat)
Это та же причина, почему Cage<Rat>
не является Cage<Animal>
? Если да, я не понимаю этого в этом примере, поэтому я не уверен, что именно делает компилятор. Вот пример кода:
package exe;
import cage.Cage;
import animals.Animal;
import animals.Ape;
import animals.Lion;
import animals.Rat;
public class Main {
public static void main(String[] args) {
Lion lion1 = new Lion(true, 4, "Lion King", 8);
Lion lion2 = new Lion(true, 4, "King of Animals", 9);
Ape ape1 = new Ape(true, 2, "Gimpanse", true);
Ape ape2 = new Ape(true, 2, "Orang Utan", true);
Rat rat1 = new Rat(true, 4, "RatBoy", true);
Rat rat2 = new Rat(true, 4, "RatGirl", true);
Rat rat3 = new Rat(true, 4, "RatChild", true);
Cage<Animal> animalCage = new Cage<Animal>();
animalCage.add(rat2);
animalCage.add(lion2);
Cage<Rat> ratCage = new Cage<Rat>();
ratCage.add(rat3);
ratCage.add(rat1);
ratCage.add(rat2);
// ratCage.add(lion1); //Not Possible. A Lion is no rat
Cage<Lion> lionCage = new Cage<Lion>();
lionCage.add(lion2);
lionCage.add(lion1);
Cage<? extends Animal> someCage = new Cage<Animal>(); //? = "unknown type that is a subtype of Animal, possibly Animal itself"
someCage = ratCage; //OK
// someCage = animalCage; //OK
someCage.add(rat1); //Not Possible, but why?
animalCage.showAnimals();
System.out.println("\nRatCage........");
ratCage.showAnimals();
System.out.println("\nLionCage........");
lionCage.showAnimals();
System.out.println("\nSomeCage........");
someCage.showAnimals();
}
}
Это класс клетки:
package cage;
import java.util.HashSet;
import java.util.Set;
import animals.Animal;
public class Cage<T extends Animal> { //A cage for some types of animals
private Set<T> cage = new HashSet<T>();
public void add(T animal) {
cage.add(animal);
}
public void showAnimals() {
for (T animal : cage) {
System.out.println(animal.getName());
}
}
}
Кроме того, я был бы доволен, если бы вы могли дать мне осмысленный "супер" пример с этим кодом-клеткой. До сих пор я не понимал, как его использовать. Есть много теоретических примеров, и я читал о концепции PECS, но так или иначе я не смог использовать ее в осмысленном вопросе. Что означало бы иметь "потребитель" (с супер) в этом примере?
Ответы
Ответ 1
Пример суперграничного
Введенный метод transferTo()
принимает Cage<? super T>
- клетку, которая содержит суперкласс из T
. Поскольку T является экземпляром его суперкласса, то ОК, чтобы положить T
в Cage<? super T>
.
public static class Cage<T extends Animal> {
private Set<T> pen = new HashSet<T>();
public void add(T animal) {
pen.add(animal);
}
/* It OK to put subclasses into a cage of super class */
public void transferTo(Cage<? super T> cage) {
cage.pen.addAll(this.pen);
}
public void showAnimals() {
System.out.println(pen);
}
}
Теперь посмотрим <? super T>
в действии:
public static class Animal {
public String toString() {
return getClass().getSimpleName();
}
}
public static class Rat extends Animal {}
public static class Lion extends Animal {}
public static class Cage<T extends Animal> { /* above */ }
public static void main(String[] args) {
Cage<Animal> animals = new Cage<Animal>();
Cage<Lion> lions = new Cage<Lion>();
animals.add(new Rat()); // OK to put a Rat into a Cage<Animal>
lions.add(new Lion());
lions.transferTo(animals); // invoke the super generic method
animals.showAnimals();
}
Вывод:
[Rat, Lion]
Другая важная концепция заключается в том, что, хотя верно, что:
Lion instanceof Animal // true
неверно, что
Cage<Lion> instanceof Cage<Animal> // false
Это не так, этот код будет компилироваться:
Cage<Animal> animals;
Cage<Lion> lions;
animals = lions; // This assignment is not allowed
animals.add(rat); // If this executed, we'd have a Rat in a Cage<Lion>
Ответ 2
Вы можете добавить Крыса в клетку <Rat> (конечно).
Вы можете добавить Крыса в клетку < Животное > , потому что Крыса "является" Животным (расширяет Животное).
Вы не можете добавить Крыса в клетку <? расширяет Animal > , потому что <? extends Animal > может быть <Lion> , который не является крысой.
Другими словами:
Cage<? extends Animal> cageA = new Cage<Lion>(); //perfectly correct, but:
cageA.add(new Rat()); // is not, the cage is not guaranteed to be an Animal or Rat cage.
// It might as well be a lion cage (as it is).
// This is the same example as in Kaj answer, but the reason is not
// that a concrete Cage<Lion> is assigned. This is something, the
// compiler might not know at compile time. It is just that
// <? extends Animal> cannot guarantee that it is a Cage<Rat> and
// NOT a Cage<Lion>
//You cannot:
Cage<Animal> cageB = new Cage<Rat>(); //because a "rat cage" is not an "animal cage".
//This is where java generics depart from reality.
//But you can:
Cage<Animal> cageC = new Cage<Animal>();
cageC.add(new Rat()); // Because a Rat is an animal.
Представьте, что у вас есть клетка <? extends Animal > , созданный абстрактным методом factory, который реализуется подклассом. В вашем абстрактном базовом классе вы не можете определить, какой тип на самом деле назначен, и компилятор не может, потому что, возможно, конкретный класс загружается только во время выполнения.
Это означает, что компилятор не может полагаться на Cage <? расширяет Animal > , чтобы не быть клеткой какого-либо другого конкретного подтипа, что сделало бы присвоение другого подтипа ошибке.
Ответ 3
Оба ответа до сих пор были замечательными. Я просто хотел бы добавить лакомый кусочек, чтобы помочь вам понять их.
Чтобы ответить Рон, вы можете подумать следующее:
"почему он someCage.add(rat1)
становится Cage<? extends Animal>.add(rat1)
? Не может ли какой-либо элемент Cage указать какой-либо клетке любого типа, который расширяет Animal (и теперь я указал, что он указывает на клетку крыс?)"
Полностью законный вопрос. Дело в том, что когда вы делаете someCage = ratCage
, поэтапная копия выполняется из ratCage в someCage. Поэтому на самом деле вы не просто установили someCage, чтобы теперь указать на ratCage. На самом деле someCage по-прежнему является Cage<? extends Animal>
. Вы не можете сделать someCage.add(rat1)
, потому что вы не знаете тип клетки, только тот тип, который был ограничен выше, Animal.
P.S.: Вы не можете добавить что-либо к someCage, так как его тип неизвестен
Ответ 4
Думаю, на ваш вопрос может ответить следующий фрагмент кода:
Cage<? extends Animal> cage = new Cage<Lion>();
cage.add(rat1);
Вы можете ясно видеть, что приведенный выше код не может быть действительным, поскольку вы знаете, что клетка в настоящий момент является клеткой льва, и вам не следует добавлять крысу в клетку льва.
Компилятор не имеет того значения, которое вы присвоили клетке, поэтому он не может разрешить cage.add(rat1)
, даже если вы назначили клетку крысы клетке.