PixelMaster
PixelMaster

Reputation: 954

Generic cloning / copying

I'm currently trying to write a custom clone-method for a class GameMap of mine.
Somewhat like this:

@Override
public GameMap clone()
{
    GameMap cloned = new GameMap(width, height, base.clone());
    cloned.setCash(cash);
    //the following are ArrayLists, obviously
    cloned.setPaths(cloneList(paths));
    cloned.setTowers(cloneList(towers));
    cloned.setEnemies(cloneList(enemies));
    cloned.setWaves(cloneList(waves));
    return cloned;
}

Problem is, this class contains several lists. Now what I tried to do was to set each list of the new instance to a cloned version of itself, using a custom cloneList method, using generics.
What I hoped would work:

private <T> List<T> cloneList(List<T> list)
{
    List<T> newList = new ArrayList<T>(list.size());
    for (T element : list)
    {
        newList.add(element.clone());
    }
    return newList;
}

Unfortunately, clone() in Object is protected, which means that this code throws compile errors. (Note that I override the clone method for all classes that are elements in any of the 4 lists, so if clone() wasn't protected, this should work)

Does anybody have any idea how to fix this problem? I've thought about copy constructors, but I don't really want to write my own List implementation - If I did that, I might as well write 4 non-generic cloneList methods. All about clean code :D

Another way how I could theoretically solve the problem is by making all four classes that are elements in any of the lists subclasses of some superclass, so I'd then use the following code:

newList.add( (T) ((Superclass) element).clone() );

Unfortunately, my Enemy class IS already a subclass of another class, so that possibility won't work either, since java doesn't support multiple inheritance.
//As I typed the superclass thing, I realized that making the 4 classes implement an interface would work as well, so I guess that will be my way to go. I'm still interested if I could solve the problem another way, though.

Thanks in advance!

PS: I'd rather avoid external libraries, if possible.

Upvotes: 2

Views: 3733

Answers (4)

Spotted
Spotted

Reputation: 4091

From your comment, it looks like your underlying problem is to restart the game.

At the moment, you considered the solution of cloning to resolve this problem.

Let me introduce you another solution to solve this problem: create a restart() method in GameMap.

public final void restart() {
    //Constants DEFAULT_X may come from a configuration file for instance
    this.cash = DEFAULT_CASH;
    this.paths.setAll(DEFAULT_PATHS);
    this.towers.setAll(DEFAULT_TOWERS);
    this.enemies.setAll(DEFAULT_ENEMIES);
    this.waves.setAll(DEFAULT_WAVES);
}

With this solution you avoid duplicating the whole GameMap, the intent is clearer (restart is more expressive than clone). Moreover, by decoupling GameMap from its default values, you make it easier to create a game with different settings.

Upvotes: 0

errantlinguist
errantlinguist

Reputation: 3818

You could use reflection to accomplish this, but it would be very ugly. It might look something like this (not tested for compilability; am waiting to catch a plane):

private static <T> List<T> cloneList(List<T> list, Class<? extends T> elementClass) {
    final List<T> result = new ArrayList<>(list.size());
    try {
        for (final T element : list) {
            final Method cloneMethod = element.getClass().getMethod("clone");
            final Object clone = cloneMethod.invoke();
            final T downcastClone = elementClass.cast(clone);
            result.add(downcastClone);
        }
    } catch (ClassCastException | SecurityException | NoSuchMethodException e) {
      // The class is not cloneable or is not a subtype of the class passed as a parameter 
      throw new IllegalArgumentException(e);
    }
    return result;
}

Upvotes: 1

Juan
Juan

Reputation: 430

The way you are implementing clone is not how Object.clone () is intended to work.

If you want your own custom method to clone by creating and copy-initialising the new object, you can always create your own Cloneable interface and make all your classes implement it.

If you actually would like to use Object.clone (which can give some performance improvement), then:

  • Your objects must implement Cloneable. Otherwise, you get an exception when invoking Object.clone ()

  • You override Object.clone () making it public.

  • Being pedantic, you should preserve the throws clause, but I find it reasonable to remove it. If your class is final, then by all means remove throws clause.

  • You start your cloning by invoking super.clone () instead of "new" instance. Object.clone () will return a shallow copy of your instance, meaning that any reference points to the same instance as the original, not to a copy. For example, the lists in your shallow clone will be the same ones as in the original.

  • Note that you need to catch the exception declared for Object.clone (), you needn't do anything with it, as your class implements Cloneable and hence will never throw it.

  • Now you need to cline the lists. If you work with the actual ArrayList instead of List interface, it is already cloneable, so you can just invoke ArrayList.clone () to get a shallow copy of the list.

  • Then you need to iterate over the lists and clone each object. That will work, as all them will be cloneable.

If you decide instead to create new objects, youcan copy lists as easily as:

List <MyClass> copy = new ArrayList <>(original);

Edit: To do all this without knowing the types contained in the list, you can:

  • Reflectively invoke clone method

  • Make all objects implement an interface which declares the clone method

  • Make all objects follow Javabeans conventions and use reflection. Create instances through no-arg constructor and inoke getters and setters to copy contents.

There's another answer for the first option.

The third one is implemented in several libraries, including Spring, and you can check their sourced.

I provide an example for the second one.

public interface MyCloneable extends Cloneable {
   Object clone ();
}

Make all desired objects implement MyCloneable.

Sample clone () implementation follows:

@Override
public MyClass clone () {
   final MyClass copy;
   try {
      copy = (MyClass) super.clone ();
   } catch (final CloneNotSupportedException e) {
      throw new AssertionError (e);
   }
   copy.base = ((MyCloneable)base).clone ();
   copy.myArrayList = myArrayList.clone ();
   for (int i = 0; i < copy.myArrayList.size (); ++i) {
      final MyCloneable e = (MyCloneable) copy.myArrayList.get (i);
      copy.myArrayList.set (i, e.clone ());
   }
   // More cloning
   return copy;
}

Upvotes: 1

Bubletan
Bubletan

Reputation: 3863

The easiest solution I could think of was passing a UnaryOperator that calls the clone() method as a parameter:

private static <T> List<T> cloneList(List<T> list, UnaryOperator<T> cloner) {
    List<T> newList = new ArrayList<T>(list.size());
    for (T element : list) {
        newList.add(cloner.apply(element));
    }
    return newList;
}

// usage:
newList = cloneList(oldList, MyCloneable::clone);

Another way using a stream:

newList = oldList.stream().map(MyCloneable::clone).collect(Collectors.toList());

Reflection is also possible but it might get a little too verbose:

private static final MethodHandle mhClone;
static {
    try {
        Method mClone = Object.class.getDeclaredMethod("clone");
        mClone.setAccessible(true);
        mhClone = MethodHandles.lookup().unreflect(mClone);
    } catch (Exception e) {
        throw new ExceptionInInitializerError(e);
    }
}
private static <T> T clone(T t) throws CloneNotSupportedException {
    try {
        return (T) mhClone.invoke(t);
    } catch (CloneNotSupportedException e) {
        throw e;
    } catch (Throwable e) {
        throw new AssertionError(e);
    }
}

Upvotes: 2

Related Questions