Pranav Shah
Pranav Shah

Reputation: 3323

Is there anyway to make this work without explicit casting? (trying a Generic Parent-Child relationship)

The Interfaces:

package sandBox.ps.generics.compositePattern;

import java.util.Collection;

interface AnimalInterface {
    String getID();

    /*
     * getAnimals can be:
     * 1. AnimalInterface
     * 2. Anything that implements/extends AnimalInterface (i.e. AnimalInterface or DogInterface)
     */
    Collection<? extends AnimalInterface> getAnimals();
}

interface DogInterface extends AnimalInterface {
    String getBreed();
}

The classes:

package sandBox.ps.generics.compositePattern;

import java.util.Collection;
import java.util.Collections;

class AnimalClass implements AnimalInterface {
    private final String id;
    private final Collection<? extends AnimalInterface> animals;

    AnimalClass(final String id,
        final Collection<? extends AnimalInterface> animals) {
    this.id = id;
    this.animals = animals;
    }

    @Override
    public String getID() {
    return this.id;
    }

    @Override
    public Collection<? extends AnimalInterface> getAnimals() {
    return this.animals;
    }
}

class DogClass extends AnimalClass implements DogInterface {

    private final String breed;

    DogClass(final String id, final String breed) {
    super(id, Collections.<AnimalInterface> emptyList());
    this.breed = breed;

    }

    @Override
    public String getBreed() {
    return this.breed;
    }

}

Testing the classes:

package sandBox.ps.generics.compositePattern;

import java.util.ArrayList;
import java.util.Collection;

public class TestClass {
    public void testA() {
    // Dog Collection (Child)
    final DogInterface dog = new DogClass("1", "Poodle");
    final Collection<DogInterface> dogCol = new ArrayList<DogInterface>();
    dogCol.add(dog);

    // Animal Collection of Dogs (Parent)
    final AnimalInterface animal = new AnimalClass("11", dogCol);
    final Collection<AnimalInterface> parent = new ArrayList<AnimalInterface>();
    parent.add(animal);
    // Animal Collection of Animals (Grand-Parent)
    final AnimalInterface grandParent = new AnimalClass("11", parent);

    // Get Dog
    for (final AnimalInterface parents : grandParent.getAnimals()) {
        /* I know the this code would work.
         * My question is, is there anyway to do this without explicit casting
        for (final AnimalInterface child : parents.getAnimals()) {
    if (child instanceof DogInterface) {
        System.out.println(((DogInterface)child).getBreed());
    }
    }
        */

        /*  HERE:   This is the part I am trying to solve.
         *  Do I use extends or super for AnimalInterface
         *  Is there any option such that I don't need to do a casting of some type
         */
        for (final DogInterface child : parents.getAnimals()) {
        System.out.println(child.getBreed());
        }       
    }
    }
}

Questions:

  1. The last lines of the test class try to access the animals.
  2. What I am trying to figure out is that, is there anyway to avoid explicit casting?
  3. Is there any combination of extends, super or some other generic term that would make this work?
  4. If casting is the only option where should it be done?
  5. I already know this would work:
for (final AnimalInterface child : parents.getAnimals()) {
    if (child instanceof DogInterface) {
       System.out.println(((DogInterface)child).getBreed());
    }
}

Upvotes: 1

Views: 220

Answers (3)

Philipp Wendler
Philipp Wendler

Reputation: 11433

Yes, you can do it. You need to give AnimalInterface a generic parameter that tells you what types of animals you get from getAnimals(), and then you need to use AnimalInterface<DogInterface> for animal and AnimalInterface<AnimalInterface<DogInterface>> for grandParent.

The complete code is this:

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;

interface AnimalInterface<A extends AnimalInterface<?>> {
  String getID();

  /*
   * getAnimals can be:
   * 1. AnimalInterface
   * 2. Anything that implements/extends AnimalInterface (i.e. AnimalInterface or DogInterface)
   */
  Collection<A> getAnimals();
}

interface DogInterface extends AnimalInterface<DogInterface> {
  String getBreed();
}

class AnimalClass<A extends AnimalInterface<?>> implements AnimalInterface<A> {
  private final String id;
  private final Collection<A> animals;

  AnimalClass(final String id, final Collection<A> animals) {
    this.id = id;
    this.animals = animals;
  }

  @Override
  public String getID() {
    return this.id;
  }

  @Override
  public Collection<A> getAnimals() {
    return this.animals;
  }
}

class DogClass extends AnimalClass<DogInterface> implements DogInterface {

  private final String breed;

  DogClass(final String id, final String breed) {
    super(id, Collections.<DogInterface> emptyList());
    this.breed = breed;
  }

  @Override
  public String getBreed() {
    return this.breed;
  }

}

class TestClass {
    public void testA() {
    // Dog Collection (Child)
    final DogInterface dog = new DogClass("1", "Poodle");
    final Collection<DogInterface> dogCol = new ArrayList<DogInterface>();
    dogCol.add(dog);

    // Animal Collection of Dogs (Parent)
    final AnimalInterface<DogInterface> animal = new AnimalClass<DogInterface>("11", dogCol);
    final Collection<AnimalInterface<DogInterface>> parent = new ArrayList<AnimalInterface<DogInterface>>();
    parent.add(animal);
    // Animal Collection of Animals (Grand-Parent)
    final AnimalInterface<AnimalInterface<DogInterface>> grandParent = new AnimalClass<AnimalInterface<DogInterface>>("11", parent);

    // Get Dog
    for (final AnimalInterface<DogInterface> parents : grandParent.getAnimals()) {
      for (final DogInterface child : parents.getAnimals()) {
        System.out.println(child.getBreed());
      }
    }
  }
}

Upvotes: 0

jdb
jdb

Reputation: 4509

You can always use a visitor:

DogBreedVisitor dogBreedVisitor = new DogBreedVisitor();
for (final AnimalInterface child : parents.getAnimals()) {
    child.visit(dogBreedVisitor);
}

Here is a very simple implementation:

interface AnimalInterface {
    void visit(AnimalVisitor visitor);
}

interface DogInterface extends AnimalInterface {
    String getBreed();
}

interface AnimalVisitor {
    public void visit(DogInterface dog);
    public void visit(AnimalInterface animal);
}

class DogBreedVisitor implements AnimalVisitor {
    @Override
    public void visit(DogInterface dog) {
        System.out.println(dog.getBreed());
    }
    @Override
    public void visit(AnimalInterface animal) {
       // ignore
    }
}

class DogClass implements DogInterface {
    @Override
    public String getBreed() {
        return "my breed";
    }
    @Override
    public void visit(AnimalVisitor visitor) {
        visitor.visit(this);
    }
}

class AnimalClass implements AnimalInterface {
    @Override
    public void visit(AnimalVisitor visitor) {
        visitor.visit(this);
    }
}

Upvotes: 2

Sean Landsman
Sean Landsman

Reputation: 7179

I'm afraid if you want to invoke a subclass method on a superclass reference, then casting is indeed necessary.

That said, I would tackle this a different way but perhaps re-architecting your program so that it isnt necessary at the TestClass level to know what sort of animal you're dealing.

For example, you could do something along the lines of

for (final AnimalInterface child : parents.getAnimals()) {
    System.out.println(child.getType()); // or something similar

The point being that you could push the responsibility of knowing what to retur is to the actual classes.

Classes where "breed" was the natural answer could return the breed, for others it could return something else.

In this case you may not want every animal to have this method (getType) so you could also perhaps have a hasType (or whatever) first.

Finally, if you find you do at some point really need to determine the actual type in question you could further push that to another class that only does this determination, leaving is so that only 1 class has this knowledge.

I hope this all makes sense - it's a bit of a rambling answer...

Upvotes: 2

Related Questions