Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to write copy constructors for classes with interface member variables in Java?

How would you write a copy constructor for a class with interface member variables?

For instance:

public class House{

    // IAnimal is an interface
    IAnimal pet;

    public House(IAnimal pet){
        this.pet = pet;
    }

    // my (non-working) attempt at a copy constructor
    public House(House houseIn){
        // The following line doesn't work because IAnimal (an interface) doesn't 
        // have a copy constructor
        this.pet = new IAnimal(houseIn.pet);
    }
}

Am I forced to have a concrete Animal? If so, it seems reusing the class for houses with dogs vs. houses with cats becomes convoluted!

like image 869
chessofnerd Avatar asked Jun 12 '13 21:06

chessofnerd


2 Answers

You have one of three choices:

  1. Have a method on IAnimal to deeply clone the object (used by libraries such as the DOM interfaces like Node.cloneNode(boolean))
  2. Create a copy constructor in all implementations of IAnimal that takes the concrete type and make that a requirement in the interface contract, then use reflection to access it
  3. Create a copy factory that will copy each implementation manually
  4. Use a 3rd-party library that implements deep cloning for you with its own contracts, such as no-args constructors, non-final fields, Serializable classes, etc., like the ones listed here

Copy Method

For #1, do something like:

public interface IAnimal {
    IAnimal cloneDeep();
}

Implement that in your concrete types, then invoke that method to copy it:

this.pet = pet.cloneDeep();

Then document the requirement in the interface, saying something along the lines of:

Implementations of this interface must return an object that is not == to this instance, and must be deeply cloned so that manipulation of this object does not lead to manipulation of the returned one and vice versa.

Implementations will have to follow this contract in order to be compliant with the interface, but this won't be enforced at compile time.

Copy Constructor

Try to access a copy constructor reflectively, then state that a copy constructor is required in all concrete implementations in the interface, which becomes part of the interface contract. Each implementation would then look like this:

public class Dog implements IAnimal {

    private String name;

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

And then all you need is a single method to copy every implementation:

public static <A extends IAnimal> A copy(A animal) {
    Class<?> animalType = animal.getClass();
    // This next line throws a number of checked exceptions you need to catch
    return (A) animalType.getConstructor(animalType).newInstance(animal);
}

One you have this, add a statement to this effect in your interface's documentation:

Implementations of this interface must define a copy constructor that takes an argument of the same type or supertype of their class. This constructor must make a deep copy of the argument so that manipulation of this object does not lead to manipulation of the returned one and vice versa.

Again, this is runtime enforced. The copy method above throws NoSuchMethodException errors when the constructor doesn't exist.

Copy Factory

This takes the IAnimal and uses instanceof to decide which method to pass it to, like:

public static IAnimal copyAnimal(IAnimal animal) {
    if (animal instanceof Dog)
        return copyDog((Dog) animal);
    if (animal instanceof Cat)
        return copyCat((Cat) animal);
    //...
    else
        throw new IllegalArgumentException("Could not copy animal of type: "
                + animal.getClass().getName());
}

Then do the deep copying in the copy methods for each type manually.

like image 161
Brian Avatar answered Sep 20 '22 03:09

Brian


If I understand your problem, Since you can't specify constructors in an interface you would need to declare a deep-copy method in your interface and implement it in your classes. You can't instantiate an interface. You possibly would also want to deep-copy anything in House as well, depending on your needs.

public interface IAnimal {
    ...
    IAnimal deepCopy(); 
}


public House(House houseIn){
    this.pet = houseIn.pet.deepCopy();
}

The problem of course, is that it's up to you to make that not do something wrong. It kinda smells like you don't really want an interface here, but rather an abstract class.

like image 43
Brian Roach Avatar answered Sep 20 '22 03:09

Brian Roach