2

我对 Java 中的复制构造有疑问。考虑以下课程;

在复制构造函数中,我可以说 new(Integer(other.id)) 来获取传递给构造函数的新整数对象,但我不能说 new T(other.data) 因为编译器会说无法实例化类型T. 我如何确保当通用项目被复制构造时,它不会只传递一个引用,这样 2 个对象将共享基础数据。

此外,在 getLinks 方法中,它正在执行一个新对象并创建列表的新对象,但它是深度复制并创建列表中包含的项目的新对象,还是仅包含对现有对象列表项目的引用,例如你有 2 个列表都指向相同的数据。请参阅下面的评论/代码。提前感谢您的专业知识。

class DigraphNode<T>  
{
    Integer id;
    T data;
    ArrayList<DigraphNode<T> > links;

    public DigraphNode(Integer i) 
    {
        id = i; 
        links = new ArrayList<DigraphNode<T> >();
    }
    public DigraphNode(Integer i, T d) 
    { 
        id = i; data = d; 
        links = new ArrayList<DigraphNode<T> >();
    }

    public DigraphNode(DigraphNode<T> other)
    {
        other.id = new Integer(other.id);
        other.data = other.data; // line in question
        this.links=other.getLinks(); // also will create a new list with references
                                     // or will it deep copy the items contained in the list?
                                     // see getLinks() method below
    }

    public void setData (T d ) { data =  d; }
    public void addLink (DigraphNode<T> n) { links.add(n); }
    public void addLinks (ArrayList<DigraphNode<T> > ns) { links.addAll(ns); }

    public ArrayList<DigraphNode<T> > getLinks()
    {
        return new ArrayList<DigraphNode<T> >(links); 
    }

    public void printNode()
    {
        System.out.print("Id: " + id + " links: ");
        for ( DigraphNode<T> i : links )
        {
            System.out.print(i.id + " ");
        }
        System.out.println();
    }
}
4

3 回答 3

1

  1. 你不能new T(other.data)像你尝试的那样实例化,但你可以clone()other.data 如果T implements Cloneable
  2. 每次调用getLinks()都会创建一个引用包含的对象的新列表links,您必须在内部具有相同引用的不同列表(因此更改一个引用对象属性将反映到另一个列表对象,因为它们是同一个对象)
  3. 关于ArrayList<> links = new ArrayList<>();来自 Oracle 文档:

    实例变量的初始化块看起来就像静态初始化块,但没有 static 关键字:
    {
    // 初始化所需的任何代码都在这里
    }
    Java 编译器将初始化块复制到每个构造函数中。因此,这种方法可用于在多个构造函数之间共享代码块。

编辑:
您可以定义一个静态方法 ( copy),尝试使用所有可能的策略来复制通用对象;最好的方法是定义自己的接口来分离自己的状态并模拟一种复制构造函数(copy如果需要,可以重用方法),或者通过序列化或最后一次尝试使用克隆(但clone()充满陷阱) .
你也可以使用这个库:


interface MyCloneableInterface<T> {
  T duplicate(T object) throws CopyException;
}
public static <T> T copy(T data) throws CopyException  {
  if(data == null) return null;
  if(data instanceof MyCloneableInterface) {
    return ((MyCloneabeInterface)data).duplicate(data);
  }
  if(data instanceof Serializable) {
    try {
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      ObjectOutputStream oos = new ObjectOutputStream(baos);
      oos.writeObject(this);

      ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
      ObjectInputStream ois = new ObjectInputStream(bais);
      return (CloneExample) ois.readObject();
    }
    catch(...) {//rethrow}
  }
  if(data instanceof Cloneable) {
    try {
      return (T)data.clone();
    }
    catch(CloneNotSupportedException e) {//rethrow}
  }
  // else you can look for copy-constructor via reflection or
  // cloning object field-by-field via reflection...
}
于 2013-08-17T23:38:53.367 回答
1

第一个问题:你不能实例化一个泛型实例(换句话说 callT的构造函数)。如果始终在您的控制之下,您应该定义T implements Cloneable并调用clone或使用您自己的另一个接口。T这种方法有很多陷阱,我建议您首先阅读有关此接口的内容并熟悉其中的陷阱(您可以在“Effective Java”一书中找到关于此的精彩章节)。此外,您并不总是可以保证此类将T使用Cloneable.

关于links- 你在开始时实例化它,然后在构造函数中覆盖它 - 为什么?删除初始化。您的getLinks工作方式不是创建深层副本。含义-您将获得一个新列表,该列表本身将与原始列表不同,但项目将是浅拷贝

关于你的最后一个问题——正如我已经说过的,这是多余的。去掉一开始的初始化。您正在创建一个对象,永远不要使用它并将其留给垃圾收集。为了避免在每个构造函数中调用 this,你可以做的事情是这样的:

public DigraphNode() {
    links = new ArrayList<DigraphNode<T> >();
}

并让其他构造函数调用此构造函数,例如:

public DigraphNode(T val) {
    this();
    this.data = val;
}
于 2013-08-17T23:39:23.323 回答
0

赞成所有有用的答案,但我在下面回答我自己的问题,该问题显示了更新的代码。我想看看有人会如何实现一个通用的副本,但没有人为此发布代码,所以我推出了自己的代码。请看下面我的回答。

import java.lang.reflect.*;
import java.util.*;

class MissingDigraphNodeException extends Exception 
{
    private static final long serialVersionUID = 1000L;
    public MissingDigraphNodeException(String message)
    {
        super(message);
    }
}

class CopyException extends Exception 
{
    private static final long serialVersionUID = 2000L;
    public CopyException(String message)
    {
        super(message);
    }
}

class DigraphNode<T>  
{
    Integer id;
    T data;
    ArrayList<DigraphNode<T> > links;

    public DigraphNode(Integer i) 
    {
        id = i; 
        links = new ArrayList<DigraphNode<T> >();
    }
    public DigraphNode(Integer i, T d) 
    { 
        id = i; data = d; 
        links = new ArrayList<DigraphNode<T> >();
    }

    public DigraphNode(DigraphNode<T> other)
    {
        try
        {
            this.data = copy(other.data);
        }
        catch (CopyException e)
        {
            e.printStackTrace();
        }
        this.links=other.getLinks();
        this.id = new Integer(other.id);
    }

    // is there a better way to copy a generic?
    @SuppressWarnings("unchecked")
    public T copy( T source ) throws CopyException
    {
        Class<?> clzz = source.getClass();
        Method meth;
        Object dupl = null;
        try {
            meth = clzz.getMethod("clone", new Class[0]);
            dupl = meth.invoke(source, new Object[0]);
        } catch (Exception e) 
        {
            e.printStackTrace();
            throw new CopyException("Error: Copying Generic of T");
        }   
        return (T) dupl;
    }

    public void setData (T d ) { data =  d; }
    public void addLink (DigraphNode<T> n) { links.add(n); }
    public void addLinks (ArrayList<DigraphNode<T> > ns) { links.addAll(ns); }

    public ArrayList<DigraphNode<T> > getLinks()
    {
        // return a new copy of the list
        ArrayList<DigraphNode<T> > l = new ArrayList<DigraphNode<T> >(); 
        for ( DigraphNode<T> i : links )
        {
            i.links.add(new DigraphNode<T>(i)); // use copy constructor
        }
        return l;
    }

    public void printNode()
    {
        System.out.print("Id: " + id + " links: ");
        for ( DigraphNode<T> i : links )
        {
            System.out.print(i.id + " ");
        }
        System.out.println();
    }
}
于 2013-08-18T08:01:41.537 回答