Creating generic class to pass an generic type (Object or List)

Viewed 844

I'm trying to solve a problem where I have to create a class that takes an object or list of objects as generic type. I don't want to create multiple classes because I have all other fields commons.

Here is what I did but I'm not sure if it is safe in long run.

Main class.

public class TestGeneric{

    public static void main(String[] args) {

        List<Cat> cats = new ArrayList<>();
        cats.add(new Cat("Tom"));
        cats.add(new Cat("Jerry"));
        cats.add(new Cat("Billi"));

        Dog dog = new Dog("Bow");

        new Rat<>(cats).instOf();
        System.out.println();
        new Rat<>(dog).instOf();
    }
}

Generic class in which I want to pass a list or single object in generic object.

class Rat<T> {
    private T obj ;

    public Rat(T t) {
        this.obj = t;
    }

    public void instOf() {
        if(obj instanceof Dog) {
            System.out.println((obj.getClass()).getName());
        }
        if(obj instanceof Cat) {
            System.out.println("Instance of Cat");
        }
        if(obj instanceof List) {
            for(Object object : ((List) obj).toArray()) {
                System.out.println(((Cat)object).getName());
            }
        }
    }
}

class to be passed as single object or list.

class Dog {
    private String name;

    public Dog() {}

    public Dog(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Dog [name=" + name + "]";
    }

}

another class to be passed as single object or list.

class Cat {
    private String name;

    public Cat() {}

    public Cat(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Dog [name=" + name + "]";
    }   
}

Please suggest if this is the right way to do this OR is there other better approach to do this?

2 Answers

Simply: bad idea.

A single instance can easily be represented as a list with one entry. There is even the Collections.singletonList() library method to turn single objects into lists.

You are about to invent a complicated design that puts a ton of knowledge into that Rat class. You should rather go and have

  • an Animal interface or base class (for the common methods in Cat/Dog)
  • simply have the Rat class then work with List<Animal>, or maybe List<? extends Animal>

Meaning: you are over-specializing your Rat class. Seriously: don't allow "single objects", and lists. Just go with lists, and (if at all) differentiate on the number of elements in that list. Your approach is overly complicated, and will surprise your readers (in a bad way). Don't do that.

For the example you have presented, you should supply a Consumer<T>, to do the "stuff" you want to do depending upon type:

class Rat<T> {
    private T obj ;
    private Consumer<T> consumer;

    public Rat(T t, Consumer<T> consumer) {
        this.obj = t;
    }

    public void instOf() {
      consumer.accept(obj);
    }
}

Then just pass in a suitable Consumer instance.

This is assuming, of course, that you need obj for something else in the Rat class; if not, just ditch the generics and pass a Runnable:

class Rat {
    private Runnable r;

    public Rat(Runnable r) {
        this.r = r;
    }

    public void instOf() {
      r.run();
    }
}
Related