3

Simplified demo code to show my problem.

class Base {
   public String toString() { return "Base"; }
};
class A extends Base {
   public String toString() { return "A"; }
};

class Test {
  public void test1() {
    A a = new A();
    Base b = (Base)a;    // cast is fine, but b is the same instance as a
    System.out.println(b.toString()); // want "Base", but get "A"
  } 

  private String testB(Base b) {
    return b.toString();   // this should return "Base"
  }   
  public void test2() {
    System.out.println( testB(new A()) );  // want "Base", but get "A"
  } 

};

I tried the cast approach (test1) , and the helper method (test2).

Up to now, I found to need a copy constructor for Base to create a real Base object.

Is there a method that does not need a duplicate object?


Some background info:

I get an instance of A, and I know its base class has a nice method, which I'd like to use instead of the overwritten version. I'd prefer to neither modify class A nor B (although a copy c'tor were a good enhancement anyway ;) )

datafiddler
  • 1,755
  • 3
  • 17
  • 30
  • Java uses dynamic dispatch, allowing polymorphic behaviour. There is no (easy) way to achieve what you want. – Turing85 May 02 '18 at 14:27
  • From the posted examples it appears "A" is never wanted. If true, remove the overridden `toString()` from `A` so `Base`'s implementation is always used. – Andrew S May 02 '18 at 14:30
  • @Andrew: I thought tat was too clear. `a.toString()` in test1 should of course yield "A" – datafiddler May 02 '18 at 14:37
  • Seems like an [XY-problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). Care to explain what you are trying to achieve? – Turing85 May 02 '18 at 14:46
  • @Touring85: I get an A instance and I know it's extending Base. Unfortunately I want to access a Base.method, which is overwritten in A. (See my edit of the original question) – datafiddler May 02 '18 at 15:20
  • You violate the Liskov substitution principle in your design, then ask for a workaround for the very thing you broke. Fix your design : replace inheritance with composition. – bowmore May 07 '18 at 09:07

2 Answers2

7

From class A directly, you can use super.toString(); to execute toString() on Base.

However, from outside class A, you can't call the superclass implementation in this way, doing so would break encapsulation. If you want to expose the superclass implementation then you still can, but you have to provide a separate method on A that exposes it directly.

Even using a trivial reflection based approach, you still won't be able to access it:

If the underlying method is an instance method, it is invoked using dynamic method lookup

System.out.println(Base.class.getMethod("toString", null).invoke(new A(), null)); //Prints "A"

...and using MethodHandles.lookup().findSpecial won't work either from outside the child class, as that has to be invoked where you have private access (otherwise you'll just get an IllegalAccessException.)

I concede that there may well be some weird and wonderful way of doing it directly in Java that I haven't thought of without bytecode manipulation, but suffice to say even if you can do it that way, you certainly shouldn't for anything but a quirky technical demonstration.

Michael Berry
  • 70,193
  • 21
  • 157
  • 216
  • yes, that was my third idea, which I didn't like, for reasons you state in your "however" paragraph better than I could formulate my discomfort – datafiddler May 02 '18 at 14:34
  • "*Even using a reflection based approach, you still won't be able to access it [...]*" - See [this answer found by @AustinSchäfer](https://stackoverflow.com/questions/5411434/how-to-call-a-superclass-method-using-java-reflection/15674467#15674467) for a counter-example (not directly reflection, but method invoking). – Turing85 May 02 '18 at 14:44
  • @Turing85 I don't believe that is a counter example, as written - it only works there because the MethodHandles are being created from a subclass of `Base` directly. If you try to move the reflective code there out into a separate class, as in this example, you'll likely get a "no private access for invokespecial" error. (Granted it may also be possible to hack around this, so I've reworded my answer slightly, but it's certainly not doable in this context just with the code in that answer.) – Michael Berry May 02 '18 at 14:55
1

You need to create the B instance(copy constructor), if you are using the A instance you will always get "A" no matter if you cast it or no.

cshapdev
  • 142
  • 3
  • 8
  • That's my conclusion too: either Base needs a copy constructor, or A needs a method `String toBaseString() { return super.toString(); }` – datafiddler May 02 '18 at 14:49