Как Java решает этот нетипизированный универсальный метод?

Сегодня я сталкиваюсь с функцией, которая заставляет меня действительно удивляться. Итак, давайте предположим эту простую структуру для прояснения.

public class Animal{

  public String getName(){ return null; }

}

public class Dog extends Animal{

  @Override
  public String getName(){
    //I'm aware that not any Dog's name is 'Pluto', but its just a Sample ;)
    return "Pluto"
  }

}

public class Cat extends Animal{

  protected final String mName;  

  public Cat(String name){
    mName = name;
  }

  @Override
  public String getName(){
     //cats have different names, because the internet loves cats
    return mName;
  }

  public void miao(){
    //just a dummy
  }
}

Теперь это абсолютно справедливо назначить Dog для Animal Указатель, но недействителен для назначения Animal к Dog Указатель, как это:

Animal animal = new Dog(); //valid, any Dog is at least an Animal
Dog dog = new Animal();  // invalid, of course not any Animal is a Dog!

Давайте предположим, что класс AnimalCage, где происходит "Магия":

public class AnimalCage{

  private ArrayList<Animal> mCage = new ArrayList<Animal>();

  public addAnimal(Animal animal){
    mCage.add(animal);
  }

  // HERE is where the "Magic" happens:
  public <A extends Animal> A getAnimalByName(String name){
    //try catch block not mandatory
    try{
      for (Animal a: mCage){
        if (name.equals(a.getName()) return (A)a; 
      }
    } catch(ClassCastException cce){}
    return null;
  }
}

С использованием AnimalCage можно сделать это:

//all valid
AnimalCage cage = new AnimalCage();
Dog dog = new Dog();
Cat cat = new Cat("Mauzi");
Cat cat2 = new Cat("Garfield");
cage.add(dog);
cage.add(cat);
cage.add(cat2);
// and later get it back
//will return dog
Dog pluto = cage.getAnimalByName("Pluto"); 
//will find nothing and return null
Dog snoopy = cage.getAnimalByName("Snoopy);
//will raise ClassCastException and return null 
snoopy = cage.getAnimalByName("Mauzi"); 
//will return Mauzi
Animal mauzi = cage.getAnimalByName("Mauzi");

так что я могу сделать что-нибудь из этого БЕЗ приведения явного. Это приводит меня к предположению, что Erasures не стираются во время выполнения, хотя я знаю лучше. Прежде чем я подумал, я должен дать хотя бы индикатор того, что разыгрывать, как эта функция:

public <A extends Animal> A getAnimalByName(String name, Class<A> animalClass){
  try{
    for (Animal a: mCage){
      if (name.equals(a.getName()) return (A)a;          }
  } catch(ClassCastException cce){}
  return null;
}

//use
Dog dog = cage.getAnimalByName("Pluto", Dog.class);

Мне действительно интересно, как Java позволяет мне назначать Animals для Cats/Dogs, и какова специализация Animal для каста.

3 ответа

Решение

Я не совсем понимаю ваш вопрос, но, возможно, я могу уточнить некоторые моменты:

  • Singnature, такие как <A extends Animal> A getAnimalByName(String name) включает в себя метод, называемый вывод типа - то есть фактический тип A в частности, вызов getAnimalByName() выводится с левой стороны задания.

    Обратите внимание, что это чисто функция времени компиляции - такой код, как

    <A extends Animal> A getAnimalByName(String name) { ... }
    ...
    Dog dog = getAnimalByName("foo");
    

    при компиляции превращается в следующий код (из-за стирания типа):

    Animal getAnimalByName(String name) { ... }
    ...
    Dog dog = (Dog) getAnimalByName("foo");
    
  • Как видите, ваш код нарушает гарантии безопасности типов - это происходит, когда вы выполняете приведение return (A) aи компилятор выдает предупреждение об этом. Это базовая гарантия обобщений - если ваш код компилируется без предупреждений, это не нарушает безопасность типов.

Вы никогда не встретите ClassCastException в следующем коде, это показывает, что универсальные типы стираются,

public <A extends Animal> A getAnimalByName(String name, Class<A> animalClass){
    try {
        for (Animal a: mCage){
            if (name.equals(a.getName()) return (A)a;          
        }
    } catch(ClassCastException cce){}
    return null;
} 

если вы хотите сделать это, напишите что-то вроде:

public <A extends Animal> A getAnimalByName(String name, Class<A> animalClass){
    try {
        for (Animal a: mCage){
            if (name.equals(a.getName()) 
                return animalClass.cast(a);          
        }
    } catch(ClassCastException cce){}
    return null;
} 

Я не могу быть на 100% уверен в этом, но я подозреваю, что это связано с полиморфизмом и тем фактом, что компилятор будет сначала искать "локальные" (в среде непосредственного класса объекта) методы для реализации, прежде чем подниматься по цепочке к найти реализацию.

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

Поскольку getName() определен для Animal, предполагается, что он сможет найти, по крайней мере, реализацию TH (даже если Cat ее изменит). То же самое относится и к псу.

Другие вопросы по тегам