通用克隆/复制

时间:2016-12-19 18:37:24

标签: java clone

我目前正在尝试为我的if (savedList == null && supplierList == null) { return false; } if (savedList == null || supplierList == null) { return true; } 类编写自定义克隆方法。
有点像这样:

GameMap

问题是,这个类包含几个列表。 现在我尝试做的是使用泛型,使用自定义@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; } 方法将新实例的每个列表设置为自身的克隆版本。
我希望能起作用:

cloneList

不幸的是,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; } 中的clone()Object,这意味着此代码会抛出编译错误。 (请注意,我覆盖了4个列表中任何一个元素的所有类的克隆方法,因此如果protected没有受到保护,那么这应该有效)

有人知道如何解决这个问题吗?我已经考虑过复制构造函数,但我并不想编写自己的List实现 - 如果我这样做,我不妨编写4个非泛型clone()方法。所有关于干净的代码:D

理论上解决问题的另一种方法是将所有四个类作为某个超类的任何列表子类中的元素,所以我然后使用以下代码:

cloneList

不幸的是,我的newList.add( (T) ((Superclass) element).clone() ); 类已经是另一个类的子类,因此这种可能性也不会起作用,因为java不支持多重继承。
//当我输入超类的东西时,我意识到让4个类实现一个接口也会起作用,所以我想这将是我的方法。如果我能以另一种方式解决问题,我仍然感兴趣。

提前致谢!

PS:如果可能的话,我宁愿避免使用外部库。

4 个答案:

答案 0 :(得分:2)

我能想到的最简单的解决方案是传递一个UnaryOperator来调用clone()方法作为参数:

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);

使用流的另一种方式:

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

反思也是可能的,但它可能有点过于冗长:

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);
    }
}

答案 1 :(得分:1)

你可以使用反射来实现这一目标,但它会非常难看。它可能看起来像这样(没有测试可编译性;我正等着赶飞机):

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;
}

答案 2 :(得分:1)

实现克隆的方式不是Object.clone()的工作方式。

如果您希望通过创建和复制初始化新对象来克隆自己的自定义方法,则可以始终创建自己的Cloneable接口并使所有类实现它。

如果您确实想使用Object.clone(可以提高性能),那么:

  • 您的对象必须实现Cloneable。否则,在调用Object.clone()

  • 时会出现异常
  • 您重写Object.clone(),使其公开。

  • 迂腐,你应该保留throws条款,但我觉得删除它是合理的。如果你的班级是最终的,那么一定要删除throws条款。

  • 您可以通过调用super.clone()而不是“new”实例来开始克隆。 Object.clone()将返回实例的浅表副本,这意味着任何引用都指向与原始实例相同的实例,而不是副本。例如,浅层克​​隆中的列表与原始列表中的列表相同。

  • 请注意,您需要捕获为Object.clone()声明的异常,您不需要对它执行任何操作,因为您的类实现了Cloneable,因此永远不会抛出它。

  • 现在您需要克隆列表。如果您使用实际的ArrayList而不是List接口,它已经是可克隆的,因此您只需调用ArrayList.clone()即可获得列表的浅表副本。

  • 然后,您需要遍历列表并克隆每个对象。这将是有效的,因为它们都可以克隆。

如果您决定创建新对象,则可以像以下一样轻松复制列表:

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

编辑:要在不知道列表中包含的类型的情况下完成所有这些操作,您可以:

  • 反复调用克隆方法

  • 使所有对象实现一个声明克隆方法的接口

  • 使所有对象遵循Javabeans约定并使用反射。通过no-arg构造函数创建实例,并使用inoke getter和setter来复制内容。

第一个选项还有另一个答案。

第三个是在几个库中实现的,包括Spring,你可以查看它们的来源。

我提供了第二个例子。

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

使所有需要的对象实现MyCloneable。

示例clone()实现如下:

@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;
}

答案 3 :(得分:0)

根据您的评论,您的潜在问题似乎是重新启动游戏。

目前,您考虑了克隆的解决方案来解决此问题。

让我向您介绍解决此问题的另一种解决方案:在system中创建restart()方法。

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); } ,意图更清晰(GameMaprestart更具表现力)。此外,通过将clone与其默认值分离,您可以更轻松地创建具有不同设置的游戏。