Полезный пример с супер и безвестностью с расширением в 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), даже если вы назначили клетку крысы клетке.