0

Is it possible to put classes with the same superclass in an ArrayList and that I can retrieve the fields of the subclass?

public class Animal {

  private String name; 
  public String getName() { ... }

}

public class Dog extends Animal {

  private String tailLength;
  public String getTailLength() { ... }
}

public class Bird extends Animal {

  private String beakSize;
  public String getBeakSize() { ... }

}

Now I'd like to put them in an arrayList

private List<Animal> animals = new ArrayList<>();

animals.put(new Dog());
animals.put(new Bird());

I'm able successfully put them in the list, but when I retrieve the value from the list, I'm not able to get the beakSize from the bird class and the tailLength from the dog class.

Can you suggest of a strategy how to do this? Thanks!

Third Fon
  • 25
  • 5
  • 1
    `List` doesn't have method `put` – boden Oct 10 '19 at 10:10
  • 1
    You _could_ use `foo instanceof Bird` and then cast `Bird bird = (Bird) foo;`, then you can use it `bird.getBeakSize()`. However, your design looks flawed. Why do you want to mix animals together but then still access the specific features of individual animal subclasses? Think about better ways to design your model. Such that you have typesafety all the time. – Zabuzard Oct 10 '19 at 10:10
  • Related https://stackoverflow.com/questions/2745265/is-listdog-a-subclass-of-listanimal-why-are-java-generics-not-implicitly-po – dai Oct 10 '19 at 10:32
  • @Zabuza actually I wanted to display it as a json object, that's why I'm putting it in a list. I'm using Jackson for json mapping. animals : [ { name: "dog", tailLength: 5 }, { name: "bird", beakSize: 10 } ] – Third Fon Oct 10 '19 at 12:48

3 Answers3

4

Why isn't this working?

You've got list of type Animal. This means that the compiler has one and only one guarantee. That this object has the fields and methods associated with the Animal superclass. Test this out for yourself.

Animal myBird = new Bird();

myBird.getBeakSize(); // This won't compile.

The compiler doesn't know it's of type Bird because its reference type is Animal.

Different Approach

What you need to do is rethink your class design, such that your superclass has the necessary information for you to access.

If you need access to the specific subclass, then I would recommend keeping separate lists of each subclass, possibly inside of an object.

An alternative might be something like this:

public abstract class Animal {

  private String name; 
  public String getName() { ... }
  public abstract String getFaceDepth(); //

}

public class Bird {
  public String getFaceDepth() {
     // Return beak size here.
  }
}

Now, you'll notice here that face depth isn't particularly helpful. This is the problem of making general cases. Sometimes, they're so general, they lose the essence of the data they're representing. This can be a sign that your class structure needs to be thought about, depending on your problem.

christopher
  • 26,815
  • 5
  • 55
  • 89
  • actually I wanted to display it as a json object, that's why I'm putting it in a list. I'm using Jackson for json mapping. Can you suggest a better approach? The json below is my desired output. animals : [ { name: "dog", tailLength: 5 }, { name: "bird", beakSize: 10 } ] – Third Fon Oct 10 '19 at 12:50
  • Have you tested what Jackson does with your object? Does it only render out those fields available in the superclass or does it do a deep inspection and render out the subclass fields? – christopher Oct 10 '19 at 15:58
  • it renders both, the fields on the superclass and the subclass. – Third Fon Oct 13 '19 at 09:31
1

When you are retrieving elements from array, you can use 'instanceof' method:

Animal animal = // get element/iterate over array
if(animal instanceof Dog) {
  Dog dog = (Dog) animal;
  // continue here...
}
if(animal instanceof Bird) {
  Bird bird = (Bird) animal;
  // continue here...
}
lukaszwrzaszcz
  • 170
  • 1
  • 7
0

You need to have 2 interfaces

public interface Beak {
   String getBeakSize();
}

public interface Tail {
   String getTailSize();
}

Now your code would look like this

public class Animal {

  private String name; 
  public String getName() { ... }

}

public class Dog extends Animal implements Tail {
  private String tailLength;

  @Override
  public String getTailLength() { ... }
}

public class Bird extends Animal {
  private String beakSize;

  @Override
  public String getBeakSize() { ... }

}

You can add items as you were doing before.

private List<Animal> animals = new ArrayList<>();

animals.put(new Dog());
animals.put(new Bird());

But now, you can use the interfaces to find if the animal has tail or beak or both or nothing at all.

for(Animal animal in animals) {
     if(animal instanceof Tail) {
          System.out.println(((Tail)animal).getTailLength());
     }
     if(animal instanceof Beak) {
          System.out.println(((Beak)animal).getBeakLength());
     }
}

Why this is important? This way you can have another animal lets say Cat or another bird which also have tail. This piece will work with all of them.

Rahul
  • 4,699
  • 5
  • 26
  • 38