Cast to concrete class and call method in Java

2019-04-28 20:51发布

问题:

Lets say we have a baseclass called A and some subclasses (B,C,D, etc.). Most subclasses have the method do() but the baseclass does not. Class AA provides a method called getObject(), which will create an object of type B, or C or D, etc., but returns the object as type A.

How do I cast the returned object to the concrete type and call its do() method, if this method is available?

EDIT: I'm not allowed to change the implementation of Class A, the subclasses or AA, since im using a closed Source API.. And yeah, it does have some design issues, as you can see.

回答1:

I think a better idea is to actually have class A define the do() method either as an abstract method or as a concrete empty method. This way you won't have to do any cast.

If you are not allowed to change any of the classes than you could define a class MyA extends A which defines the do() method and MyB, MyC,... and a MyAA that would basically do what AA does, just that it returns objects of type MyB, MyC....

If this is not ok then I don't see another way than checking if the returned object is of type B and do a cast to B and so on.



回答2:

You can test with instanceof and call the do() methods:

A a = aa.getObject();
if (a instanceof B) {
   B b = (B) a;
   b.do();
}
// ...


回答3:

Assuming A defines do, and it is not private, you can just call it without a cast, no matter the subclass that AA returns. That's one of the features of polymorphism. At runtime, the interpreter will use the correct (i.e. the implementation of the actual class) version of do.



回答4:

First of all it would be a better approach to make Class A as an abstract Class with do() as an Abstract method in it......

Moreover if you still want the way you want to do it..then

Do an explicit cast.

B b = (B) a; // a is a casted back to its concrete type.

Moreover you should keep in mind this very important behaviour of the Compiler.

The Object Reference Variable of Super Type must have the method to be called, whether the Sub Type Object has or not.

Eg:

A a = new B();

- To call a method, do() on Object Reference Variable of Type A, class A must have the go() method.



回答5:

If you are not allowed change A but you can change the subclasses then you can make an interface with the method do() and let all the subclass implement that interface.

public interface Doer {
    public void do();
}

public class B extends A implements Doer {
    //implement do method
}

//.. same for other subclass

Then you don't need a cast. Otherwise you will need some explicit downcasts.



回答6:

What you are describing seems to me like you want to invoke Derived Class methods on Base class reference..

But for that, you need to have your methods in your base class also..
So, you need to declare your method do() in your base class A also.. If you don't want to give an implementation, let it be abstract, or let it be an empty method.. It will not matter..

Now, if you do the same thing you're explaining.. You won't need to do a typecast..

Because, appropriate Derived Class method will be invoked based upon - which derived class object does your base class reference point to

public abstract class A {
   public abstract void do();
}

public class B extends A {
   public void do() {
       System.out.println("In B");
   }
}

public class Test {
    public static void main(String[] args) {
        A obj = returnA();

        obj.do();  // Will invoke class B's do() method
    }

    /** Method returning BaseClass A's reference pointing to subclass instance **/
    public static A returnA() {
         A obj = new B();
         return obj;
    }
}

Ok, just now saw your edit, that you are not allowed to change your classes..

In that case, you will actually need to do a typecast based on the instance of returned reference..

So, in main method above, after A obj = returnA(); this line add the following line: -

if (obj instanceof B) {
    B obj1 = (B) obj;

}

But, in this case, you would need to check instanceof on each of your subclasses.. That can be a major problem..



回答7:

Best way to do it have A class that method. But since you are not allowed to change any class. I would advice you to create a wrapper instance around all classes using reflections.

Static method in Below class is used just to show how to do it. You can have separate instance variable which can Wrap A in E.

public class E {

public static void doMethod(A a) {
    Class<?> class1 = a.getClass();
    Method method;
    try {
        method = class1.getDeclaredMethod("doMethod", null);// B, C, D has doMethod
        method.invoke(a, null);
        // I know to many exceptions
    } catch (SecurityException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (IllegalArgumentException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }

}
}

Second option is instance of for which you will have to check for the type and then cast it.



回答8:

You can do this with a little work if the method invocations return instances of the class in question, which is your specific question (above).

import static java.lang.System.out;

public class AATester {
   public static void main(String[] args){
      for(int x: new int[]{ 0, 1, 2 } ){
         A w = getA(x);
         Chain.a(w.setA("a")).a(
            (w instanceof C ? ((C) w).setC("c") : null );
         out.println(w);
      }
   }

   public static getA(int a){//This is whatever AA does.
      A retval;//I don't like multiple returns.
      switch(a){
         case 0:  retval = new A(); break;
         case 1:  retval = new B(); break;
         default: retval = new C(); break;
      }
      return retval;
   }
}

Test class A

public class A {
   private String a;
   protected String getA() { return a; }
   protected A setA(String a) { this.a=a; return this; }//Fluent method
   @Override
   public String toString() {
      return "A[getA()=" + getA() + "]";
   }
}

Test class B

public class B {
   private String b;
   protected String getB() { return b; }
   protected B setB(String b) { this.b=b; return this; }//Fluent method
   @Override
   public String toString() {
      return "B[getA()=" + getA() + ", getB()=" + getB() + "]\n  " 
      + super.toString();
  }
}

Test Class C

public class C {
   private String c;
   protected String getC() { return c; }
   protected C setC(String c) { this.c=c; return this; }//Fluent method
   @Override
   public String toString() {
      return "C [getA()=" + getA() + ", getB()=" + getB() + ", getC()=" 
             + getC() + "]\n  " + super.toString();
   }
}

The Chain class

/**
 * Allows chaining with any class, even one you didn't write and don't have 
 * access to the source code for, so long as that class is fluent.
 * @author Gregory G. Bishop ggb667@gmail.com (C) 11/5/2013 all rights reserved. 
 */
public final class Chain {
   public static <K> _<K> a(K value) {//Note that this is static
      return new _<K>(value);//So the IDE names aren't nasty
   }
}

Chain's helper class.

/** 
 * An instance method cannot override the static method from Chain, 
 * which is why this class exists (i.e. to suppress IDE warnings, 
 * and provide fluent usage). 
 *
 * @author Gregory G. Bishop ggb667@gmail.com (C) 11/5/2013 all rights reserved.
 */
final class _<T> {
   public T a;//So we can reference the last value if desired.
   protected _(T t) { this.a = T; }//Required by Chain above
   public <K> _<K> a(K value) {
      return new _<K>(value);
   }
}

Output:

    A [get(A)=a]
    B [get(A)=a, getB()=null]
      A [getA()=a]
    C [getA()=a, getB()=null, getC()=c)]
      B [get(A)=a, getB()=null]
      A [get(A)=a]