views:

5687

answers:

7

Hullo all,

Wondering if there are any Java hackers who can clue me in at to why the following doesn't work:

public class Parent {
    public Parent copy() {
       Parent aCopy = new Parent();
       ...
       return aCopy;
    }
}

public class ChildN extends Parent {
    ...
}

public class Driver {
     public static void main(String[] args) {
         ChildN orig = new ChildN();
         ...
         ChildN copy = orig.getClass().cast(orig.copy());
     }
}

The code is quite happy to compile, but decides to throw a ClassCastException at runtime D=

Edit: Whoah, really quick replies. Thanks guys! So it seems I cannot downcast using this method... is there any other way to do downcasting in Java? I did think about having each ChildN class overwrite copy(), but wasn't enthusiastic about adding the extra boilerplate code.

+6  A: 

The cast is effectively trying to do this:

ChildN copy = (ChildN) orig.copy();

(It's working out the cast to perform at execution time, but that's what it will be because orig.getClass() will be ChildN.class) However, orig.copy() doesn't return an instance of ChildN, it returns an instance of just Parent, so it can't be cast to ChildN.

Jon Skeet
+4  A: 

If ChildN does not override copy() to return an instance of ChildN, then you are trying to downcast an object of type parent to type ChildN

erikkallen
+5  A: 

Is like trying to do this:

  public Object copy(){
       return new Object();
  }

And then attempt to:

  String s = ( String ) copy();

Your Parent class and ChildN class have the same relationship as Object and String

To make it work you would need to do the following:

public class ChildN extends Parent {
    public Parent copy() {
        return new ChildN();
    }
}

That is, override the "copy" method and return the right instance.


EDIT

As per your edit. That is actually possible. This could be one possible way:

public class Parent {
    public Parent copy() {
        Parent copy = this.getClass().newInstance();
        //...
        return copy;
    }
}

That way you don't have to override the "copy" method in each subclass. This is the Prototype design pattern.

However using this implementation you should be aware two checked exceptions. Here's the complete program that compiles and runs without problems.

public class Parent {
    public Parent copy()  throws InstantiationException, IllegalAccessException  {
       Parent copy = this.getClass().newInstance();
       //...
       return copy;
    }
}
class ChildN  extends Parent {}

class Driver {
     public static void main(String[] args) throws  InstantiationException ,  IllegalAccessException  {
         ChildN orig = new ChildN();
         ChildN copy = orig.getClass().cast(orig.copy());
         System.out.println( "Greetings from : " + copy );
    }
}
OscarRyz
Teach by example! This answer is _very_ clear to me. Now can Child.copy also be declared to return a ChildN iso a Parent? (I know in C++ it can)
xtofl
Not in Java. At least not in Java source code. To confuse the issue slightly it *is* allowed in Java byte code...
Matthew Murdoch
It's allowed in source code in Java 5+.
Jon Skeet
+2  A: 

java.lang.Class#cast(Object) throws a ClassCastException if Class#isInstance() returns false. From the javadoc for that method:

Determines if the specified Object is assignment-compatible with the object represented by this Class. This method is the dynamic equivalent of the Java language instanceof operator... Specifically, if this Class object represents a declared class, this method returns true if the specified Object argument is an instance of the represented class (or of any of its subclasses); it returns false otherwise.

Since Parent is not a subclass of child, isInstance() returns false so cast() throws the exception. This may violate the principle of least astonishment but it's working the way it's documented - cast() can only upcast, not downcast.

sk
+2  A: 

Could it be that you simply want a copy/clone of your object.

In that case, implement the Cloneable interface and override clone() as necessary.

public class Parent implement Cloneable {
   public Object clone() throws CloneNotSupportedException {
     Parent aCopy = (Parent) super.clone();
   ...
   return aCopy;
   }
}

public class ChildN extends Parent {
    ...
}

public class Driver {
     public static void main(String[] args) {
         ChildN orig = new ChildN();
         ...
         ChildN copy = orig.getClass().cast(orig.clone());
     }
}

This does exactly what your "copy()" method tried to do in the Java way.

(Yes, you could do fancy introspection games too, but those methods fail or become ugly as soon as you do not have a public default-constructor. Clone works in any case no matter what constructors you have.)

Thomas Morgner
Also works! I've heard that clone() has a bad reputation, but not sure of the exact reasons.
This could be the "other" possible way. There's no need to change the method signature ( using clone and return Object ) copy as name and Parent as return should do.
OscarRyz
+1  A: 

(Can't add code in a comment, so I'll add here)

Regarding Cloneable: if you're implementing Cloneable, implement it as follows; much cleaner to call...

public class Foo implements Cloneable {
    public Foo clone() {
        try {
            return (Foo) super.clone();
        } catch (CloneNotSupportedException e) {
            return null; // can never happen!
    }
}

[EDIT: I've also seen other folks use

throw new AssertionError("This should never happen! I'm Cloneable!");

in the catch block.]

Scott Stanchfield
Indeed, this is is how I ended up doing it! Although my comment was more along the lines of // JAVA EPIC FAIL :)
A: 

The reason downcasting doesnt work is because, when you cast a parent object to a child type, theres no way you can invoke child type's methods on the parent object. But it works the other way...

Sathish