jay
jay

Reputation: 1524

Java Copy constructors with generics

This has probably been asked a million times before, but I'm having trouble wrapping my head around writing a copy constructor on an abstract class with a bounded type parameter. I have some code that looks like this:

public abstract class Superclass<T> {
    Set<? extends Variable<T>> vars;

    public abstract Superclass<? extends T> copy();

    class Variable<T> {
        T value;
    }
}

class Foo extends Superclass<Integer> {
    public Foo copy() {
        Foo _newFoo = Foo();
        Set<FooVariable> _newVars = new HashSet<FooVariable>();
        _newVars.addAll(this.vars);
        _newFoo.vars = _newVars;
    }

    class FooVariable extends Variable<Integer> { /* ... */ }
}

class Bar extends Superclass<String> {
    public Bar copy() {
        Bar _newBar = Bar();
        Set<BarVariable> _newVars = new HashSet<BarVariable>();
        _newVars.addAll(this.vars);
        _newBar.vars = _newVars;
    }

    class BarVariable extends Variable<String> { /* ... */ }
}

Since the copy method for both Foo and Bar is the same except for the variable types, I'd like to be able to move that code into a concrete method in the superclass. But I can't figure out (a) how to have the concrete public Superclass<? extends T> copy method return a Foo instance if called on a Foo and a Bar instance if called on a Bar and (b) populate the vars set with FooVariables or BarVariables as appropriate.

Can anybody please help and tell me what I'm missing? Thanks.

Upvotes: 13

Views: 2021

Answers (3)

Ondrej Bozek
Ondrej Bozek

Reputation: 11481

What about this kind of Superclass?

public abstract class Superclass<T> {

    Set<? extends Variable<T>> vars;

    public Superclass<? extends T> copy() {
        Superclass<T> _newSuperclass = this.getNewInstance();
        Set<Variable<T>> _newVars = new HashSet<Variable<T>>();
        _newVars.addAll(this.vars);
        _newSuperclass.vars = _newVars;
        return _newSuperclass;
    }

    public abstract Superclass<T> getNewInstance();

    class Variable<T> {

        T value;
    }
}

The point is that you just need to implement getNewInstance() in subclasses instead of constructor.

So Foo would look just like:

class Foo extends Superclass<Integer> {

    @Override
    public Superclass<Integer> getNewInstance() {
        return new Foo();
    }

    class FooVariable extends Variable<Integer> { /* ... */ }
}

Upvotes: 2

darijan
darijan

Reputation: 9775

Tested this code, it has a couple of warnings, but it does what you want:

public <C extends Superclass<? extends T>> C copy() throws InstantiationException, IllegalAccessException {
    C result= (C) this.getClass().newInstance();
    HashSet newVars= new HashSet();
    newVars.addAll(this.vars);
    result.vars= newVars;
    return result;
}

One remark: this is not a copy constructor. This is just a copy method. Constructor does not have a return type and its name is equal to class name.

Upvotes: 0

rgettman
rgettman

Reputation: 178263

Introduce a second generic type parameter to represent the Variable<T>, U.

Then, Foo.FooVariable<T> and Bar.BarVariable<T> satisfy the bounds of U and can be returned from the copy method.

EDIT

I've changed the code to move the implementation of copy into the superclass. It relies on a newInstance method (introduced already by @OndrejBozek).

public abstract class Superclass<T, U extends Variable<T>> {
    Set<U> vars;

    class Variable<T> {
        T value;
    }

    public Superclass<T, U> copy() {
        Superclass<T, U> _newSuperclass = newInstance();
        Set<U> _newVars = new HashSet<U>();
        _newVars.addAll(vars);
        _newSuperclass.vars = _newVars;
       return _newSuperclass;
    }

    public abstract Superclass<T, U> newInstance();
}

class Foo extends Superclass<Integer, Foo.FooVariable> {
    public Foo newInstance() { return new Foo(); }

    class FooVariable extends Variable<Integer> { /* ... */ }
}

class Bar extends Superclass<String, Bar.BarVariable> {
    public Bar newInstance() { return new Bar(); }

    class BarVariable extends Variable<String> { /* ... */ }
}

Upvotes: 0

Related Questions