Java; casting base class to derived class

2019-04-18 22:41发布

问题:

Why can't I cast a base class instance to a derived class?

For example, if I have a class B which extends a class C, why can't I do this?

B b=(B)(new C());

or this?

C c=new C();
B b=(B)c;

Alright let me be more specific as to what I'm trying to do. Here's what I have:

public class Base(){
    protected BaseNode n;
    public void foo(BaseNode x){
        n.foo(x);
    }
}


public class BaseNode(){
    public void foo(BaseNode x){...}
}

Now I want to create a new set of classes which extend Base and Basenode, like this:

public class Derived extends Base(){
    public void bar(DerivedNode x){
        n.bar(x);//problem is here - n doesn't have bar
    }
}

public class DerivedNode extends BaseNode(){
    public void bar(BaseNode){
        ...
    }
}

So essentially I want to add new functionality to Base and BaseNode by extending them both, and adding a function to both of them. Furthermore, Base and BaseNode should be able to be used on their own.

I'd really like to do this without generics if possible.


Alright so I ended up figuring it out, partly thanks to Maruice Perry's answer.

In my constructor for Base, n is instantiated as a BaseNode. All I had to do was re-instantiate n as a DerivedNode in my derived class in the constructor, and it works perfectly.

回答1:

You need to use the instanceof keyword to check the type of object referenced by n and typecast the object and call the bar() method. Checkout Derived.bar() method bellow

public class Test{
    public static void main(String[] args){
        DerivedNode dn = new DerivedNode();
        Derived d = new Derived(dn);
        d.bar( dn );
    }
}

class Base{
    protected BaseNode n;
    public Base(BaseNode _n){
        this.n = _n;
    }

    public void foo(BaseNode x){
        n.foo(x);
    }
}


class BaseNode{
    public void foo(BaseNode x){
        System.out.println( "BaseNode foo" );
    }
}

class Derived extends Base{
    public Derived(BaseNode n){
        super(n);
    }

    public void bar(DerivedNode x){
        if( n instanceof DerivedNode ){
            // Type cast to DerivedNode to access bar
            ((DerivedNode)n).bar(x);
        }
        else {
            // Throw exception or what ever
            throw new RuntimeException("Invalid Object Type");
        }
    }
}

class DerivedNode extends BaseNode{
    public void bar(BaseNode b){
        System.out.println( "DerivedNode bar" );
    }
}


回答2:

because if B extends C, it means B is a C and not C is a B.

rethink what you are trying to do.



回答3:

The existing answers are fine in terms of an abstract argument, but I'd like to make a more concrete one. Suppose you could do that. Then this code would have to compile and run:

// Hypothetical code
Object object = new Object();
InputStream stream = (InputStream) object; // No exception allowed?
int firstByte = stream.read();

Where exactly would the implementation of the read method come from? It's abstract in InputStream. Where would it get the data from? It simply isn't appropriate to treat a bare java.lang.Object as an InputStream. It's much better for the cast to throw an exception.

In my experience it's tricky to get "parallel class hierarchies" like the one you're describing to work. You may find that generics help, but it can get hairy very quickly.



回答4:

You can create a constructor for B that takes C as a parameter. See this post for ideas to do what you're trying to do.



回答5:

You can't do that because C does not necessarily implement the behaviours you created when you extended it in B.

So, say C has a method foo(). Then you know that you can call foo() on a B, as B extends C, so you can cast accordingly a treat a B as if it was a C with (C)(new B()).

However - if B has a method bar(), nothing in the subclass relationship says that you can call bar() on C too. Thus you cannot treat a C as if it were a B, and so you cannot cast.



回答6:

In your exemple, you can cast n into a DerivedNode if you are certain that n is an instance of DerivedNode, or you can use generics:

public class Base<N extends BaseNode> {
    protected N n;
    public void foo(BaseNode x){
        n.foo(x);
    }
}


public class BaseNode {
    public void foo(BaseNode x){...}
}

public class Derived extends Base<DerivedNode> {
    public void bar(DerivedNode x){
        n.bar(x); // no problem here - n DOES have bar
    }
}

public class DerivedNode extends BaseNode {
    public void bar(BaseNode){
        ...
    }
}


回答7:

Base classes shouldn't know anything about classes derived from them, otherwise the problems highlighted above will arise. Downcasting is a 'code smell', and downcasting in the base class to a derived class is particularly 'smelly'. Such designs can lead to difficult to resolve circular dependencies too.

If you want a base class to make use of derived class implementations use the Template method pattern i.e add a virtual or abstract method in your base class and override and implement it in the derived class. You can then safely call this from the base class.



回答8:

Because if B extends C, then B might have stuff that isn't in C (like instance variables you initialize in the constructor that are not in new C())