Reputation: 3336
I have a question on copy construction in Java. Consider the following class;
In the copy constructor I can say new(Integer(other.id)) to get a new integer object being passed to the constructor, but I can't say new T(other.data) as the compiler will say cannot instantiate the type T. How can I make sure that when the generic item is copy constructed that it will not just pass a reference such that the 2 objects will share the underlying data.
Also, in the getLinks method it is doing a new and creating a new object of the list but is that going to deep copy and create new object of the items contained in the list or will it just contain references to the existing objects list items such that you have 2 lists both pointing to the same data. See below the comments / code. Thanks in advance for your expertise.
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();
}
}
Upvotes: 2
Views: 1940
Reputation: 18403
new T(other.data)
as you tried, but you can clone()
other.data if T implements Cloneable
getLinks()
will create a new list with reference to object contained to links
, you have to different lists with same reference inside (so change one reference object property will reflect to other list object because they are the same object)ArrayList<> links = new ArrayList<>();
from Oracle doc:Initializer blocks for instance variables look just like static initializer blocks, but without the static keyword:
{
// whatever code is needed for initialization goes here
}
The Java compiler copies initializer blocks into every constructor. Therefore, this approach can be used to share a block of code between multiple constructors.
copy
) that try to use all possible strategies to copy generic object; the best approch is to define your own interface to separate your own stategy and simulate a sort of copy-constructor (you can reuse copy
method if you want), else via serialization or, as last try, using cloning (but clone()
is full of pitfall).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...
}
Upvotes: 1
Reputation: 3336
Upvoted all helpful answers, but I am answering my own question below which shows the updated code. I wanted to see how someone would implement a copy for a generic but no one posted code for that so I rolled my own. See below my answer.
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();
}
}
Upvotes: 0
Reputation: 21858
First Question: You cannot instantiate a generic instance (in other words call T
's constructor). You should either define T implements Cloneable
and call clone
or use another interface of your own if T
is always under your control. There are many pitfalls to this method, I'd suggest you first read about this interface and familiarize yourself with the pitfalls (you can find a great chapter on this, in "Effective Java" book). Also, it is not always that you can guarantee that this class will use T
types which are Cloneable
.
About links
- you're instantiating it in the beginning and then override it in the constructor - Why? Remove the initialization. The way your getLinks
works is not by creating a deep copy. Meaning - you'll get a new list, the list itself will be different from the original list, but the items will be shallow copies.
About your last question - as I already said, it's redundant. Remove the initialization at the beginning. You're creating an object, never use it and leave it for garbage collection. What you can do to avoid calling this in every constructor is something like this:
public DigraphNode() {
links = new ArrayList<DigraphNode<T> >();
}
And have other constructors call this constructor, for example:
public DigraphNode(T val) {
this();
this.data = val;
}
Upvotes: 1