Cast, generics and clone problem


T

tamas.hauer

Hi,

I am trying to deep-copy a complex data type. To that end I
implemented clone() for all the types involved and I am having problems
with generics. It's much harder to explain than to show, thus let me
show a silly example which demonstrates it:

class Super implements Cloneable {
public Super clone() {
return new Super();
}
}

class Sub extends Super {
public Sub clone() {
return new Sub();
}
}

public <T extends Super> void test( T item ){
java.util.HashSet<T> set = new java.util.HashSet<T>( );
set.add( item.clone() ); // <<<<<<< ERROR
}

I understand the error, as the compiler thinks item.clone() returns
'Super' instead of 'T'. I can do a cast to 'T': set.add(
(T)(item.clone()) ); which works allright but gives me an unchecked
cast understandably.

So my question is: is there a better way to declare this clone() method
so that it can be used in generic contexts with <T extends super>, and
if there is no such thing, is there a way to get rid of the unchecked
cast warning just for this piece of code (without turning it off for
the whole file)?

Thanks,

Tamas
 
Ad

Advertisements

K

kamal

According to your question I think you will have to override the method
in class T as ;

public Super clone() {
return new T();
}

Now your issue must be gone away. Will you be able to do that at class
T?
 
T

Thomas G. Marshall

tamas.hauer coughed up:
Hi,

I am trying to deep-copy a complex data type. To that end I
implemented clone() for all the types involved and I am having
problems with generics. It's much harder to explain than to show,
thus let me show a silly example which demonstrates it:

class Super implements Cloneable {
public Super clone() {
return new Super();
}
}

class Sub extends Super {
public Sub clone() {
return new Sub();
}
}

public <T extends Super> void test( T item ){
java.util.HashSet<T> set = new java.util.HashSet<T>( );
set.add( item.clone() ); // <<<<<<< ERROR


Don't ever specify an error like this. Post the actual error from the java
compiler verbatim. And then indicate which line the line number refers to
if unclear.
 
A

Alan Moore

Hi,

I am trying to deep-copy a complex data type. To that end I
implemented clone() for all the types involved and I am having problems
with generics. It's much harder to explain than to show, thus let me
show a silly example which demonstrates it:

class Super implements Cloneable {
public Super clone() {
return new Super();
}
}

class Sub extends Super {
public Sub clone() {
return new Sub();
}
}

public <T extends Super> void test( T item ){
java.util.HashSet<T> set = new java.util.HashSet<T>( );
set.add( item.clone() ); // <<<<<<< ERROR
}

I understand the error, as the compiler thinks item.clone() returns
'Super' instead of 'T'. I can do a cast to 'T': set.add(
(T)(item.clone()) ); which works allright but gives me an unchecked
cast understandably.

Try adding "implements Cloneable" to the definition of Sub.
 
C

Chris Uppal

tamas.hauer said:
So my question is: is there a better way to declare this clone() method
so that it can be used in generic contexts with <T extends super>, and
if there is no such thing, is there a way to get rid of the unchecked
cast warning just for this piece of code (without turning it off for
the whole file)?

[You seem to have been a bit unlucky in your replies so far...]

There are a couple of approaches, neither of which is entirely satisfactory.
Actually, the simplest approach might be just to do an explicit cast and live
with any warnings (who cares about warnings anyway ?)

Anyway, one approach starts by defining an interface something like:

public interface
TypedCloneable<E extends TypedCloneable<E>>
{
E typedClone();
}

after which you can define a class that implements it:

public class Base
implements TypedCloneable<Base>, Cloneable
{
public Base
typedClone()
{
Base clone = null;
try
{
clone = (Base)this.clone();
}
catch (CloneNotSupportedException e)
{
// ooer...
}
return clone;
}
}

Once you have that you can say things like:

public static <E extends TypedCloneable<E>> void
test(E it)
{
Set<E> set = new HashSet<E>();
set.add(it.typedClone());
}

But there are problems with the approach. For one thing it is impossible to
declare a subclass of Base that also has a type-safe clone operation:

public class Derived
extends Base
implements TypedCloneable<Derived>
{
//...

is rejected because you can't implement the same generic interface twice with
different parameters. So the pattern is only of limited use....

Another approach is to use a helper method somewhere. E.g. if you define:

public class Utils
{
public static <X extends Base> X
clone(X it)
{
return (X)(it.clone());
}
}

And you have Base defined as:

public class Base
implements Cloneable
{
public Object
clone()
{
try
{
return (Base)(super.clone());
}
catch (CloneNotSupportedException e)
{
// ooer...
return null;
}
}
}

And also Derived:

public class Derived
extends Base
{
public Derived clone()
{
return (Derived)(super.clone());
}
}

Then you can say things like:

public static <E extends Base> void
test(E it)
{
Set<E> set = new HashSet<E>();
set.add(Utils.clone(it));
}

which the compiler will accept quite happily.

There are a couple of things to note here. One is that the compiler doesn't
appear to issue a warning for the specific construction (from a method in
Base):

(Base)(super.clone());

which is handy, if a bit puzzling. The other is that the cast in
Utils.clone(X) will cause a compiler warning, but that's only one warning (from
a file that need have no /other/ contents) which is better than getting a
warning at every point where you want to use clone().

-- chris
 
A

Andrew McDonagh

Alan said:
Try adding "implements Cloneable" to the definition of Sub.

Why would he need to do that? seeing as Super implements the interface,
Sub does as well.
 
Ad

Advertisements

A

Alan Moore

Why would he need to do that? seeing as Super implements the interface,
Sub does as well.

You're right. I thought it was throwing a runtime error because
clone() was returning the wrong type, but on testing I see it's
compile-time. Apparently, the compiler can't see that the object
being added to the set will always be the correct type.

Is it really necessary to be so specific in declaring the type of the
set? Why not just declare it as a HashSet<Super>?
 
T

tamas.hauer

Thanks for the recent replies.

The problem with declaring HashSet<Super> is that it does not work well
with the complex data type I am having. A simplified part of my data
model has some basic types, say Node, then Set<Node>, Set<Set<Node>>,
and so on. (There are also some other types like Tuple<Set<Node>>,
Set<Tuple<Node>> etc...). Its a tree and I keep traversing it up and
down. (I mentioned that OP is just a silly example to illustrate my
question, in my code HashSet is actually a more complex type)

For particular operations, I iterate through container nodes and if I
declare the Sets as Set<some super type> then I end up with lots of
cast...

Tamas
 
T

tamas.hauer

Thanks a lot Chris,

"Actually, the simplest approach might be just to do an explicit cast
and live with any warnings (who cares about warnings anyway ?) "

I do care about warnings. In fact I warmly welcome the generics of
Java with all its warnings that help me a lot. I am happy to switch of
warnings though once I am confident that I know where they come from,
that's why I also asked if it is possible in Java on a case-by-case
basis somehow (like #pragma in C++) -- I guess not :-(.

BTW there is a solution to get rid of a compiler warning at every point
where clone() is used: by delegating the cast to a static method of a
helper class (which results in just one warning).

Tamas
 
T

tamas.hauer

tamas.hauer coughed up:
Don't ever ...

Hmmm. I humbly apologize. Below are the line numbers then, hope this
helps:

------------------------------------------------------------------------------------
1 public class Test {
2 class Super implements Cloneable {
3 public Super clone() {
4 return new Super();
5 }
6 }
7
8 class Sub extends Super {
9 public Sub clone() {
10 return new Sub();
11 }
12 }
13
14 public <T extends Super> void test( T item ){
15 java.util.HashSet<T> set = new java.util.HashSet<T>( );
16 set.add( item.clone() );
17 }
18 }
------------------------------------------------------------------------------------
Test.java:16: cannot find symbol
symbol : method add(Test.Super)
location: class java.util.HashSet<T>
set.add( item.clone() );
^
1 error
 
T

tamas.hauer

Chris,

Sorry that I posted so quickly without reading your reply carefully up
to the end. I do realize that you suggested the exact same thing...

Tamas
 
Ad

Advertisements

G

Guest

According to your question I think you will have to override the method in
class T as ;

public Super clone() {
return new T();
}
Now your issue must be gone away. Will you be able to do that at class T?

Currently, we are unable to initialize objects of an (at-compile-time)
unknown generic type. IOW, you can't treat Type the same as Class.

HTH,
La'ie Techie
 
T

Thomas G. Marshall

tamas.hauer coughed up:
Hi,

I am trying to deep-copy

You sure you want a deep copy ?

a complex data type. To that end I
implemented clone() for all the types involved and I am having
problems with generics. It's much harder to explain than to show,
thus let me show a silly example which demonstrates it:

class Super implements Cloneable {
public Super clone() {
return new Super();

You understand that this is not a deep nor shallow copy, but a new object
entirely, right? The newly created object will have none of the state of
the original.

In the pre-generics days, a shallow-copy cloning was done like this:

Class MyClass implements Cloneable
{
public Object clone() { return super.clone(); }
}

No "new", but a direct call to one of the superclass's (most likely
Object's) clone() methods. You rely on Object.clone() to provide the
copying mechanism.

If you wanted a "deep copy", then you needed something with reflection to
recur into the data structure of the object.

....[rip]...
 
T

tamas.hauer

class Super implements Cloneable {
You understand that this is not a deep nor shallow copy,

I do. It has nothing to do with my actual copy. I had said I crafted
a <i>silly example</i> which shows nothing but my question about
generics.
 
T

Thomas G. Marshall

tamas.hauer coughed up:
I do. It has nothing to do with my actual copy. I had said I crafted
a <i>silly example</i> which shows nothing but my question about
generics.


Well, then I'll ask again: are you sure what you want is a deep copy? If
so, then many of the solutions presented to you in this thread will not
apply. Particularly anthing that looks like a call to super.clone().
 
Ad

Advertisements

T

Thomas G. Marshall

Thomas G. Marshall coughed up:
tamas.hauer coughed up:

You sure you want a deep copy ?



You understand that this is not a deep nor shallow copy, but a new
object entirely, right? The newly created object will have none of
the state of the original.

In the pre-generics days, a shallow-copy cloning was done like this:

Class MyClass implements Cloneable

Oops: add "throws CloneNotSupportedException"...

{
public Object clone() { return super.clone(); }
}

I thought I'd add something about this cloning method on the off chance that
there is someone out there new enough to java to be confused by it. This is
without regard to generics.

1. The reason that you have to spell out this method in your own class even
though it is defined within Object is that Object.clone() is a protected
method.

2. There are two common ways to implement your own clone() method. The
first is as above (I had forgotten the throws clause in my haste):

public Object clone() throws CloneNotSupportedException
{
return super.clone();
}

This is as simple as it gets, but still requires the calling code to catch
the exception. Since your calling code most likely already understands that
this method is cloneable, a very common technique is to simple place
superfluous try/catch within the method itself:

public Object clone()
{
try
{
return super.clone();
}
catch (CloneNotSupportedException ignore) { return null; }
}

This way anything calling clone() can do so without worrying about catching
the exception which is only thrown when the cloning is not supported, which
is moot since you are writing the code yourself to allow it. Note that the
"return null;" is required /somewhere/ at the end of the method, only to
satisfy the compiler---to ensure to it that there is always a return
somewhere. It would never be called.



No "new", but a direct call to one of the superclass's (most likely
Object's) clone() methods. You rely on Object.clone() to provide the
copying mechanism.

If you wanted a "deep copy", then you needed something with
reflection to recur into the data structure of the object.

...[rip]...
 
Ad

Advertisements


Top