Can Java Generics replace multiple similar classes?

1.8k Views Asked by At

Problem

I'm trying to use Java generics to replace classes with similar methods. All of the examples that I've found cover simple examples, but I'm not sure if Java Generics are intended to be used this way.

I have 2 parent classes, and 2 child classes that have almost identical methods. Both parent classes derive from different classes as well. Ultimately, I want to be able to use one block of code to create and manipulate one of the parent classes, then its child class without a lot of switch statements or other flow control with duplicate code.

This is what I had in mind, even though I haven't beeen able to get it to work this way yet, whether it be syntax, or just not a feature of Generics.

Parent Classes

public class FooParent
{
    private FooChild fooChild;
    public FooChild getChild()
    {
        return fooChild;
    }
}

public class BarParent
{
    private BarChild barChild;
    public BarChild getChild()
    {
        return barChild;
    }
}

Child Classes

public class FooChild
{
    public void print()
    {
        System.out.println("I'm a foo child");
    }
}

public class BarChild
{
    public void print()
    {
        System.out.println("I'm a bar child");
    }
}

Generic Classes

public class GenericParent<T>
{
    private T self;
    public GenericParent(T self)
    {
        this.self = self;
    }

    public GenericChild getChild()
    {
        return new GenericChild(self.getChild());
    }
}

public class GenericChild<T>
{
    private T self;
    public GenericChild(T self)
    {
        this.self = self;
    }

    public void print()
    {
        self.print();
    }
}

How I want to use them

public static void main(String args[])
{
    GenericParent parent;

    // Only the initialization of the parent variable needs specialized code
    switch(args[0])
    {
        case "foo":
            parent = new GenericParent(new FooParent());
        break;

        case "bar":
            parent = new GenericParent(new BarParent());
        break;
    }

    // From here on out, it's all generic
    parent.getChild().print();
}

Usage and desired output

java genericExample foo
> I'm a foo child

java genericExample bar
> I'm a bar child

Final Questions

Maybe "child" and "parent" are misnomers, because I know they're not actually inherited, but the bottom line is, the one class returns its "child" with certain methods. So this is a lot of code for a problem that may not actually be solvable this way, but hopefully you can answer me this:

  1. Is this something that Java Generics can accomplish?
  2. If not, is there a solution to this problem in Java?

Thanks!

Edit

My "Foo" and "Bar" classes are uneditable by me. My ultimate question is: can I store one instance of either class in a single variable without using a common parent class?

4

There are 4 best solutions below

0
On

Your parent seems a wrapper, a wrapper is a container, so yes it may be something that can benefit from a type parameter.

But I can't see any type parameter except in the constructor signature (and what is self? No bounds, no hints, no anything...), so using a generic type doesn't buy anything to you here. It's no use to introduce a type parameter if the methods you are interested in return void and declare an empty parameter list.

Here's the guidance: if methods in your classes would benefit from having a type parameter, ie if a type parameter is useful in any method return type or in the signature, then genericize your class. Otherwise, stick with what you currently have.

0
On

I think you want polymorphism, not generics:

public class test {
    public class FooParent implements hasPrintableChildren
    {
        private FooChild fooChild;
        public FooChild getChild()
        {
            return fooChild;
        }
    }

    public class BarParent implements hasPrintableChildren
    {
        private BarChild barChild;
        public BarChild getChild()
        {
            return barChild;
        }
    }


    public class FooChild implements canPrint
    {
        public void print()
        {
            System.out.println("I'm a foo child");
        }
    }

    public class BarChild implements canPrint
    {
        public void print()
        {
            System.out.println("I'm a bar child");
        }
    }

    public interface hasPrintableChildren{
        public canPrint getChild();
    }
    public interface canPrint{
        public void print();
    }



    public static void main(String args[])
    {
        hasPrintableChildren parent;
        // Only the initialization of the parent variable needs specialized code
        switch(args[0])
        {
            case "foo":
                parent = new FooParent();
            break;

            case "bar":
                parent = new BarParent();
            break;
        }

        // From here on out, it's all generic
        parent.getChild().print();
    }
}

OP clarified that he would be interested in the reflection option:

    public static void main(String args[]) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException
    {
        Object parent;
        // Only the initialization of the parent variable needs specialized code
        switch(args[0])
        {
            case "foo":
                parent = new FooParent();
            break;

            case "bar":
                parent = new BarParent();
            break;
        }

        // From here on out, it's all generic
        Object child = parent.getClass().getMethod("getChild").invoke(parent);
        child.getClass().getMethod("print").invoke(child);
    }

Note: I would not recommend this sort of hard coded reflection. Code like this generally stinks of a bigger design problem.

0
On

No. This is not usually something you would use Generics for, this is something you would use an Interface or an Abstract class for, and in your example, probably anonymous inner classes.

Here is an example that pretty much shows all that:

Main.java

public class Main {
    public static void main(String args[])
    {
        AbstractParent parent;
        // Only the initialization of the parent variable needs specialized code
        switch(args[0])
        {
            case "foo":
                parent = new FooParent();
                break;

            default:
                parent = new BarParent();
                break;
        }

        // From here on out, it's all generic
        parent.getChild().print();
    }
}

Child.java

public interface Child {
    void print();
}

AbstractParent.java

public abstract class AbstractParent {
    protected Child child;

    public Child getChild() {
        return child;
    }
}

BarParent.java

public class BarParent extends AbstractParent {
    public BarParent() {
        child = new Child() {
            @Override
            public void print() {
                System.out.println("I'm a bar child");
            }
        };
    }
}

FooParent.java

public class FooParent extends AbstractParent {
    public FooParent() {
        child = new Child() {
            @Override
            public void print() {
                System.out.println("I'm a foo child");
            }
        };
    }
}

With some of the new language features in Java 8, you can do even cooler things. But let's leave that for another time.

1
On

Yes, generics together with polymorphism can help you:

public class Foo {} // declared elsewhere


public class Bar {} // declared elsewhere


public abstract class GenericParent<T> {
    private T self;

    public GenericParent(T self) {
        this.self = self;
    }

    protected T getSelf() {
        return self;
    }

    public abstract GenericChild<T> getChild();
}


public class FooChild extends GenericChild<Foo> {
    public FooChild(Foo foo) {
        super(foo);
    }
}


public class BarChild extends GenericChild<Bar> {
    public BarChild(Bar bar) {
        super(bar);
    }
}


public class FooParent extends GenericParent<Foo> {

    public FooParent(Foo foo) {
        super(foo);
    }

    public FooParent() {
        this(new Foo()); 
    }

    @Override
    public GenericChild<Foo> getChild() {
        return new FooChild(getSelf());
    }
}


public class BarParent extends GenericParent<Bar> {

    public BarParent(Bar bar) {
        super(bar);
    }

    public BarParent() {
        this(new Bar());
    }

    @Override
    public GenericChild<Bar> getChild() {
        return new BarChild(getSelf());
    }
}

You also have to change your main method slightly:

public static void main(String args[]) {
    GenericParent<?> parent;

    // Only the initialization of the parent variable needs specialized code
    switch(args[0]) {
        case "foo":
            parent = new FooParent();
            break;

        case "bar":
            parent = new BarParent();
            break;
    }

    parent.getChild().print();
}