How to get a clone of the super class?

L

Lethal Possum

Hello everyone,

Let's say I have a class B that extends a class A. Now I have an
instance of B and I need to "clone" it but only as an instance of A,
not B. I can't just cast my instance of B into A, I need a new
instance of A, and only A. I need that the new object's getClass()
method to return A.class.

Is there an easy way to do this? I'd prefer not to copy each field one
by one as there is many of them and maintenance would be difficult
(i.e. how do I make sure someone adding a field to class A will update
my method accordingly).

I know I could probably achieve this by reflection, iterating on every
field of class A, but some fields of A need to be copied in a specific
way. This is already done properly by the clone() method of A so I
would really like to leverage that code.

I am not sure my problem is very clear so I wrote a short piece of
code to demonstrate it. I also included all the solution that I
already know not to work:

== Start of code ==

public class A implements Cloneable {

private boolean _copy = false;

public boolean isCopy() {
return _copy;
}

public Object clone() throws CloneNotSupportedException {
A a = (A)super.clone();
a._cloned = true;
return a;
}

}

public class B extends A {

public A test1() throws Exception {
A a = (A)this;
return (A)a.clone();
}

public A test2() throws Exception {
A a = (A)clone();
return a;
}

public A test3() throws Exception {
A a = (A)super.clone();
return a;
}

public A test4() throws Exception {
A a = (A)clone();
return (A)a.clone();
}

}

public class test {

public static void main (String[] args) throws Exception {
B b = new B();
A a1 = b.test1();
System.out.println(a1.getClass().toString() + " copy=" +
a1.isCopy());
A a2 = b.test2();
System.out.println(a2.getClass().toString() + " copy=" +
a2.isCopy());
A a3 = b.test3();
System.out.println(a3.getClass().toString() + " copy=" +
a3.isCopy());
A a4 = b.test4();
System.out.println(a4.getClass().toString() + " copy=" +
a4.isCopy());
}

}

== End of code ==

All that I get is:

class B copy=true

When I would like to get:

class A copy=true

Any idea?

Thanks,

Tom
 
L

Lethal Possum

Hello everyone,

Let's say I have a class B that extends a class A. Now I have an
instance of B and I need to "clone" it but only as an instance of A,
not B. I can't just cast my instance of B into A, I need a new
instance of A, and only A. I need that the new object's getClass()
method to return A.class.

Is there an easy way to do this? I'd prefer not to copy each field one
by one as there is many of them and maintenance would be difficult
(i.e. how do I make sure someone adding a field to class A will update
my method accordingly).

I know I could probably achieve this by reflection, iterating on every
field of class A, but some fields of A need to be copied in a specific
way. This is already done properly by the clone() method of A so I
would really like to leverage that code.

I am not sure my problem is very clear so I wrote a short piece of
code to demonstrate it. I also included all the solution that I
already know not to work:

== Start of code ==

public class A implements Cloneable {

  private boolean _copy = false;

  public boolean isCopy() {
    return _copy;
  }

  public Object clone() throws CloneNotSupportedException {
    A a = (A)super.clone();
    a._cloned = true;
    return a;
  }

}

public class B extends A {

  public A test1() throws Exception {
    A a = (A)this;
    return (A)a.clone();
  }

  public A test2() throws Exception {
    A a = (A)clone();
    return a;
  }

  public A test3() throws Exception {
    A a = (A)super.clone();
    return a;
  }

  public A test4() throws Exception {
    A a = (A)clone();
    return (A)a.clone();
  }

}

public class test {

  public static void main (String[] args) throws Exception {
    B b = new B();
    A a1 = b.test1();
    System.out.println(a1.getClass().toString() + " copy=" +
a1.isCopy());
    A a2 = b.test2();
    System.out.println(a2.getClass().toString() + " copy=" +
a2.isCopy());
    A a3 = b.test3();
    System.out.println(a3.getClass().toString() + " copy=" +
a3.isCopy());
    A a4 = b.test4();
    System.out.println(a4.getClass().toString() + " copy=" +
a4.isCopy());
  }

}

== End of code ==

All that I get is:

class B copy=true

When I would like to get:

class A copy=true

Any idea?

Thanks,

Tom

I just noticed a typo in my code, "a._cloned = true;" should be
"a._copy = true;".

Sorry,

Tom
 
L

Lew

The Java naming conventions call for no underscore in this variable name.

It initializes to 'false', then you set it to 'false'. This is redundant but
some consider it useful for internal documentation and don't mind the extra
assignment of 'false'.

Upcasts are superfluous. 'this' already /is-an/ 'A', and doesn't lose its
'B'-ness by having an 'A' pointer reference it.

Same remark - upcasts are superfluous.

Once again the cast is superfluous. It does nothing to upcast an instance to
a type that it already is.

This fails for the same reason that the call to 'super.clone()' in 'A'
succeeds. From the Javadocs for 'clone()' (which surely you have read while
researching this question - right?):
this method creates a new instance of the class of this object

Since the class of this object is 'B', the cloned instance is a 'B'.

You can't very well rely on this behavior in 'A' and simultaneously wish it
wouldn't work that way in 'B'.

Not only is the upcast superfluous, and not only does 'a' continue to point to
a 'B' instance (casts don't change the runtime type of an object), but now
you've created two instances of 'B' and thrown one away.
}

}

public class test {

public static void main (String[] args) throws Exception {
B b = new B();
A a1 = b.test1();
System.out.println(a1.getClass().toString() + " copy=" +
a1.isCopy());
A a2 = b.test2();
System.out.println(a2.getClass().toString() + " copy=" +
a2.isCopy());
A a3 = b.test3();
System.out.println(a3.getClass().toString() + " copy=" +
a3.isCopy());
A a4 = b.test4();
System.out.println(a4.getClass().toString() + " copy=" +
a4.isCopy());
}

}

== End of code ==

All that I get is:

class B copy=true

Not possible. You have four 'println()' calls. You must have gotten four
lines of output.

The presence of the '_copy' variable (whose name violates the naming
conventions, btw) is against the spirit of 'clone()', but never mind. As the
Javadocs say, for the clone to be value-equal "is not an absolute requirement."

'clone()' is the wrong method for what you are asking. It is designed to
return an instance of the cloned object's class. You are trying to violate
that contract. Create your own method in 'A' to make a copy that instantiates
its own 'A' instance and copies over the desired state without using 'clone()'.
 
M

markspace

Lethal said:
Hello everyone,

Let's say I have a class B that extends a class A. Now I have an
instance of B and I need to "clone" it but only as an instance of A,
not B. I can't just cast my instance of B into A, I need a new
instance of A, and only A. I need that the new object's getClass()
method to return A.class.


I'm not sure what you're trying to do. Is there a way you can be more
specific with your actual example? This one is pretty hypothetical.

Here's my hypothetical thought:


package test;

public class CloneTest implements Cloneable {

public static void main( String[] args )
{
CloneChild b = new CloneChild();
CloneTest a = b.clone();
System.out.println( a );
}

@Override
public final CloneTest clone() {
try
{
return (CloneTest) super.clone();
}catch( CloneNotSupportedException ex )
{
// Cannot get here
throw new AssertionError( ex );
}
}
}

class CloneChild extends CloneTest {

}
 
L

Lethal Possum

Hi Lew,

The Java naming conventions call for no underscore in this variable name.
Noted.


It initializes to 'false', then you set it to 'false'.  This is redundant but
some consider it useful for internal documentation and don't mind the extra
assignment of 'false'.

Your point is valid. In this case I don't mind as this code has no
purpose except to be an example.
Upcasts are superfluous.  'this' already /is-an/ 'A', and doesn't lose its
'B'-ness by having an 'A' pointer reference it.


Same remark - upcasts are superfluous.


Once again the cast is superfluous.  It does nothing to upcast an instance to
a type that it already is.

This fails for the same reason that the call to 'super.clone()' in 'A'
succeeds.  From the Javadocs for 'clone()' (which surely you have read while
researching this question - right?):


Since the class of this object is 'B', the cloned instance is a 'B'.

You can't very well rely on this behavior in 'A' and simultaneously wish it
wouldn't work that way in 'B'.


Not only is the upcast superfluous, and not only does 'a' continue to point to
a 'B' instance (casts don't change the runtime type of an object), but now
you've created two instances of 'B' and thrown one away.

I completely understand all the reasons why upcasting the result of a
B.clone() will never give me an instance of class A. That what I meant
by "all the solutions that I already know not to work". I should
probably have said "understand not to work".
  }
}
public class test {
  public static void main (String[] args) throws Exception {
    B b = new B();
    A a1 = b.test1();
    System.out.println(a1.getClass().toString() + " copy=" +
a1.isCopy());
    A a2 = b.test2();
    System.out.println(a2.getClass().toString() + " copy=" +
a2.isCopy());
    A a3 = b.test3();
    System.out.println(a3.getClass().toString() + " copy=" +
a3.isCopy());
    A a4 = b.test4();
    System.out.println(a4.getClass().toString() + " copy=" +
a4.isCopy());
  }
}
== End of code ==
All that I get is:
class B copy=true

Not possible.  You have four 'println()' calls.  You must have gotten four
lines of output.

Sorry if I was not clear, what I meant is that I get 4 times:

class B copy=true
The presence of the '_copy' variable (whose name violates the naming
conventions, btw) is against the spirit of 'clone()', but never mind.  As the
Javadocs say, for the clone to be value-equal "is not an absolute requirement."

'clone()' is the wrong method for what you are asking.  It is designed to
return an instance of the cloned object's class.  You are trying to violate
that contract.  Create your own method in 'A' to make a copy that instantiates
its own 'A' instance and copies over the desired state without using 'clone()'.

Creating a method that copies over the desired state will be my last
resort because the practical maintenance of the method will be very
difficult: the actual classes have dozen of fields and other
developers work in theses classes too. When one developer had a new
field, I can count on him (most of the time) to update the clone
method for example but I think there is very little chance that he
will know to update my copy method. Hence my hope to find a solution
based on clone(). Or another solution that I didn't think of but would
be self-maintaining.

Thanks

Tom
 
L

Lew

According to Lethal Possum:
Is there an easy way to do this ['clone()' a subtype of 'A'
and lose the subtype information]?

Thomas said:
Generally speaking, no.

In Java, the implementation of a class is the guardian of what can be
part of an instance of that class. To get an instance of A you have to
go through a constructor of A. If A does not override clone(), then
calling super.clone() from an instance of B will fallback on
Object.clone(), which will build a new instance of B, not a new instance
of A. Basically, to get what you want, you will need a bit of
cooperation from class A.

In the OP's case 'A' follows best practice and its override calls
'super.clone()', presumably all the way up the ladder to 'Object'.
If class A cooperates (i.e. you are developing the source code of A as
well), then the simplest way seems to add a A.dup() method which returns
a new instance of A (created with an explicit 'new A()') and manually
filled with the proper data. Otherwise, you could probably try to abuse
serialisation (serialise your instance of B into some bytes, and
cut&glue it into a proper serialisation for A) but this is hardly "easy"
and I guess that there are a huge number of pesky details to take into
account.

Could the OP use a private 'dup()' method in 'A' to implement 'A#clone()'
without calling on A's 'super.clone()'?

There are a host of problems with this but it could work for the specialized
situation, perhaps. 'A' would have to be a direct descendant of 'Object' to
have a hope of getting away with it, I think.

Personally, I would just use a conventional 'clone()'. What does it matter to
the caller who's looking for an 'A' that the type is actually 'B extends A'?
That is the usual pattern for hidden implementation classes. The details of
the implementation class are inaccessible to the caller who got an 'A', and
the caller interacts with the type just as if it were any other kind of 'A'.
 
M

markspace

Lethal said:
Creating a method that copies over the desired state will be my last
resort because the practical maintenance of the method will be very
difficult: the actual classes have dozen of fields and other
developers work in theses classes too. When one developer had a new
field, I can count on him (most of the time) to update the clone
method for example but I think there is very little chance that he
will know to update my copy method.


Try to make the copy method as obvious as possible:

public class CopyMe {

int field1;
int field2;

public class CopyMe() {}

public class CopyMe( CopyMe copy ) {
this.field1 = copy.field1;
this.field2 = copy.field2;
}
}

class Child extends CopyMe {}


Making your "copy method" a constructor gives a bigger hint to a future
developer that intervention is needed. Really this is not any worse
than overriding equals(). Developers have to know to update that when
they add a field, and they should know to update a constructor to
initialize fields too.

Adding big comments like /* ANY NEW FIELDS MUST BE INITIALIZED HERE */
doesn't hurt either.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,770
Messages
2,569,583
Members
45,073
Latest member
DarinCeden

Latest Threads

Top