Question

Today I come across a Function that makes me really wondering. So lets assume this simple structure for clearification.

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

Now it is absolut valid to assign a Dog to an Animal Pointer, but invalid to Assign an Animal to a Dog Pointer like this:

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!

Lets Assume an AnimalCage class, where the "Magic" happens:

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

With the use of the AnimalCage it is possible to do this:

//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");

so I can Do anything of this WITHOUT casting explicit. This leads me to the assumption, that Erasures aren't erased at Runtime, although I know better. Before I thought I have to give at least an Indicator on what to Cast like this Function:

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

I'm really wondering on how Java lets me assign Animals on Cats/Dogs, and on what specialization of Animal it has to Cast

Was it helpful?

Solution

I don't quite understand your question, but perhaps I can clarify some points:

  • Singnature such as <A extends Animal> A getAnimalByName(String name) involves a technique called type inference - i.e. actual type of A in particular invocation of getAnimalByName() is inferred from the left side of assignment.

    Note that it's a purely compile-time feature - code such as

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

    turns into the following code when compiled (due to type erasure):

    Animal getAnimalByName(String name) { ... }
    ...
    Dog dog = (Dog) getAnimalByName("foo");
    
  • As you can see, your code breaks type safety guarantees - it happens when you do a cast in return (A) a, and compiler emits a warning about it. It's a basic guarentee of generics - if your code compiles without warnings, it doesn't break type safety.

OTHER TIPS

I can't be 100% sure of this, but I suspect it has to do with Polymorphism and the fact that the compiler will look for "local" (in the object's immediate class environment) methods first for implementation before going up the chain to find the implementation.

Even though you may treat Cat as an Animal, the underlying code still tries to find the Cat implementation first before using the Animal implementation because when you instantiated the object the compiler knows the class.

Because getName() is defined for Animal, is assumes that it will be able to find at least THAT implementation (even if Cat changes it). The same is true of Dog.

You will never encounter ClassCastException in the following code, it shows that generic types are erased,

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

if you want to make it write do something as:

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;
} 
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top