Wednesday 2 April 2014

Conflict between "instanceof" and using a right-hand-side passed in argument.

Came across a problem I never knew about which boils down to when to use instanceof vs myVar.getClass().equals(...) .

This gets complicated pretty quickly, depending on what you want.

My particular problem was as follows:

Class Animal
Class Dog extends Animal
Class Bird extends Animal
Class Cat extends Animal

class PetStore
{
 List<Animal> animalsInStore;
 
 public List<Animal> getListOfAnimalsByType(Class<?> classType)
 {
  ...
 }
}


And with the classes above, this was the goal:
... void main()
{
 List<Dog> availableDogs = (List<Dog>)(List<?>) getListOfAnimalsByType(Dog.class);
 printAvailableAnimals("Here are all the dogs you can buy.", availableDogs);
}




So basically it all came down to how the method getListOfAnimalsByType() was implemented. I initially tried this:

public List<Animal> getListOfAnimalsByType(Class<?> classType)
{
 List<Animal> results = new ArrayList<Animal>();
 for(Animal animal : animalsInStore)
 {
  if( animal instanceof classType.getClass() )
   results.add(animal);
 }
 return results;
}

But immediately I got a syntax error.
See while it would have been fine to do something like this:

animal instanceof Dog.class
 dogs.add(animal)

animal instanceof Cat.class
 cats.add(animal)

animal instanceof Bird.class
 birds.add(animal)


In this case, the method  getListOfAnimalsByType() had no idea which sub class it would have to compare it to. I could have written instanceof Dog and Cat and Bird but as you can see that would have been way too much code to write.
So the fix for this example is:

for(Animal animal : animalsInStore)
{
 if( animal.getClass().equals(classType.gettClass()) )
  results.add(animal);
}


So this was a clear cut example of when to use getClass().equals() over instanceof but its not always so simple.

Other use cases to consider:

Depending on some use cases, it can be hairy which solution is best. Look at the following examples:

class Animal
{
 @Overwrite
 public boolean equals(Object o)
 {
  if (this.getClass() != o.getClass())
   return false 
  
  if (!(o instanceof Animal))
   return false ;
  
  Animal animal = (Animal) o;
  
  //some test that this is an Animal
  if(isThisARealAnimal(animal))
   return true;
  return false;
 }
}


class Dog extends Animal
{
}

Dog q = new Dog("Rover");
Animal w = new Animal("Rover");
if(q.equals(w))
{
 /*
 This will always be false when comparing Class instances unless you comment out
 the code "if (this.getClass() != o.getClass())" in animal.equals() .
 */
}


So again, depending on what you want, you gotta be aware of the differences.

Notes:
The top-answer of this stackoverflow question helped me make some sense of this.
The solution for casting one type of List to another (List<?>) I found here .