0

Here is the gist of what I am trying to do:

abstract class Animal {
    abstract static Animal fromInput(String input); // <- error

    static List<Animal> makeFive() {
        List<Animal> animals = new ArrayList<Animal>();
        animals.add(Animal.fromInput("Input 1"));
        animals.add(Animal.fromInput("Input 2"));
        animals.add(Animal.fromInput("Input 3"));
        animals.add(Animal.fromInput("Input 4"));
        animals.add(Animal.fromInput("Input 5"));
        return animals;
    }
    // ^^^ how to this rewrite this so Dog.makeFive() ^^^ //
    // ^^^ makes Dogs and Cat.makeFive() makes Cats?  ^^^ //
}

class Dog extends Animal {
    @Override static Dog fromInput(String input) {
        Dog dog = new Dog();
        // do some initial dog stuff with input
        return dog;
    }
}

class Cat extends Animal {
    @Override static Cat fromInput(String input) {
        Cat cat = new Cat();
        // do some initial cat stuff with input
        return cat;
    }
}

How to write this correctly?

I want to be able to call Dog.makeFive() to give me a List<Dog> or Cat.makeFive() to give me a List<Cat> without having to re-define makeFive() in Dog, Cat, and every other animal class.

EDIT: I know the use of static within an abstract class is wrong. My question is how to work around that so one is able to call Dog.makeFive() or Cat.makeFive() while only defining makeFive() once.

Ray Zhang
  • 1,411
  • 4
  • 18
  • 36
  • 2
    `static` methods are not polymorphic. You need to remove the `static` keyword, and call the method on an object. – azurefrog May 30 '19 at 21:16
  • 1
    Possible duplicate of [Why can't static methods be abstract in Java](https://stackoverflow.com/questions/370962/why-cant-static-methods-be-abstract-in-java) – Samuel Philipp May 30 '19 at 21:16
  • OK, but can you show to write it so one can call `Dog.makeFive()` or `Cat.makeFive()` while only defining `makeFive()` once? – Ray Zhang May 30 '19 at 21:25
  • @RayZhang you would define `makeFive()` 1x in the super class – andrewdleach May 30 '19 at 21:29
  • Possible duplicate of [Is List a subclass of List? Why are Java generics not implicitly polymorphic?](https://stackoverflow.com/questions/2745265/is-listdog-a-subclass-of-listanimal-why-are-java-generics-not-implicitly-po) – andrewdleach May 30 '19 at 21:40
  • This question seems to be devolving into a question of generics and polymorphism. Voting to close as a duplicate – andrewdleach May 30 '19 at 21:41
  • It's possible my question was not worded clearly and I apologize for it. But the answers being provided show over-riding `makeFive()` in each child class while my question is whether the task can be done with `makeFive()` only defined once (such as in a parent abstract class). The logic for `makeFive()` is the same no matter what animal so I don't want to duplicate it, the only difference between animals is the result of `makeFive()` should contain that particular animal. – Ray Zhang May 30 '19 at 21:46

5 Answers5

1

I want to be able to call Dog.makeFive() to give me a List or Cat.makeFive() to give me a List without having to re-define makeFive() in Dog, Cat, and every other animal class.

I feel your pain! It looks so much like it should be possible, but as the other answers and comments say, Java for whatever reason does not allow it.

So IMHO, the best workaround is to have the single makeFive method in Animal, and pass the destination class to it.

So your caller would be :

List<Dog> dogList = Animal.makeFive(Dog.class);

The implementation would be :

public abstract class Animal {

    public abstract void initialise(String input);

    static <T extends Animal> List<T> makeFive(Class<T> clazz) {
        List<T> animals = new ArrayList<T>();
        animals.add(makeNew(clazz, "Input 1"));
        animals.add(makeNew(clazz, "Input 2"));
        animals.add(makeNew(clazz, "Input 3"));
        animals.add(makeNew(clazz, "Input 4"));
        animals.add(makeNew(clazz, "Input 5"));
        return animals;
    }

    private static <T extends Animal> T makeNew(Class<T> clazz, String input) {
        T newAnimal;
        try {
            newAnimal = clazz.newInstance();
            newAnimal.initialise(input);
        } catch (InstantiationException | IllegalAccessException e) {
            newAnimal = null;  // Change This !!!  Maybe throw exception, or add "throws" to method declaration
        }
        return newAnimal;
    }

}

And finally, have each animal type perform it's initialisation :

class Dog extends Animal {

    @Override
    public void initialise(String input) {
        // do some initial dog stuff with input
    }
}

This approach does require that each subclass (Dog, Cat, etc) have a no-argument constructor, but that's a fairly common requirement for various objects in the Java ecosystem.

Alternatively, it could be possible to have the Animal.makeNew method use reflection to find an appropriate constructor, but I personally would advise against that since it becomes difficult to maintain.

racraman
  • 4,988
  • 1
  • 16
  • 16
0

This would satisfy the requirements of your question:

public interface Animal<T> {

    List<T> makeFive();
}

And then Dog and Cat would both implement Animal

public class Dog implements Animal<DogType> {

   public Dog(){
      super();
   }


   @Override
   public List<DogType> makeFive(){
     // do stuff;
   }

}

public class Cat implements Animal<CatType> {

   public Cat(){
      super();
   }


    @Override
   public List<CatType> makeFive(){
     // do stuff;
   }
}

Alternatively, if you're using Java 8+ you can make an Animal an interface and define makeFive as a default method

public interface Animal<T> {
    List<T> makeFive();

}
andrewdleach
  • 2,458
  • 2
  • 17
  • 25
0
public abstract class Animal {

    abstract List<? extends Animal> makeFive();

}

class Dog extends Animal {

    @Override
    List<Dog> makeFive() {
        return null;
    }
}

class Cat extends Animal {

    @Override
    List<Cat> makeFive() {
        return null;
    }
}
Qingfei Yuan
  • 1,196
  • 1
  • 8
  • 12
0

You can remove the static keyword from the signature of the function and define the fromInput method as abstract --

abstract class Animal<T> {
    abstract T fromInput(String input);

    List<T> makeFive() {
        List<T> animals = new ArrayList<>();
        animals.add(fromInput("Input 1"));
        animals.add(fromInput("Input 2"));
        animals.add(fromInput("Input 3"));
        animals.add(fromInput("Input 4"));
        animals.add(fromInput("Input 5"));
        return animals;
    }
}

class Dog extends Animal<Dog> {
    @Override
    Dog fromInput(String input) {
        Dog dog = new Dog();
        // do some initial dog stuff with input
        return dog;
    }
}

class Cat extends Animal<Cat> {
    @Override
    Cat fromInput(String input) {
        Cat cat = new Cat();
        // do some initial cat stuff with input
        return cat;
    }
}

You can then use it to implement the invocation as follows --

public class Solution {

    public static void main(String[] args) {
        /*
         * Dog(s)
         */
        List<Dog> dogs = new Dog().makeFive();
        for (Dog dog : dogs) {
            System.err.println(dog.getClass());
        }

        /*
         * Cat(s)
         */
        List<Cat> cats = new Cat().makeFive();
        for (Cat cat : cats) {
            System.err.println(cat.getClass());
        }
    }

}
Ameya Pandilwar
  • 2,638
  • 28
  • 28
  • How do you call `makeFive()` in this example? By instantiating a new object such as `new Dog().makeFive()`? It seems incorrect to do it this way since the result of `makeFive()` does not depend on the particular instance of the new `Dog`. – Ray Zhang May 30 '19 at 21:50
0

A super class may not invoke instances of their sub classes unless they are explicitly declared in the super class. Otherwise the super classes don't even know about them. So for every instance of Dog or Cat that you want in the list, you need to add them from your sub class.

In your original design, you called makeFive once which contained a single instance of Dog or Cat. How would you expect the parent class of Animal to be able to instantiate different ones with different state?

Even if you had an interface to support this, each subclass would have to implement that interface to support its type.

But you already implement fromInput in each subclass so why not ignore that method and just implement (or override) makeFive()? It seems like you're implementing one method to keep from having to implement another.

Finally, there is one subtle problem with this. Unless you want List<Animal> to contain the same Dog or Cat object, you also need to copy the newly created List<Animal> to each Dog or Cat instance. Otherwise, they will be accessing an empty (and probably null) list.

WJS
  • 36,363
  • 4
  • 24
  • 39