0

I have recently been moving through a couple of books in order to teach myself Java and have, fortunately, mostly due to luck, encountered very few difficulties. That has just changed.

I read a section on the following under inheritance and the whole superclass subclass setup

  • When a new superclass object is created, it is, like all objects, assigned a reference (superReference in this example)

  • If a new subclass object (with the defining subclass extending the superclass) is created, and then the superReference reference is set to refer to that instead of the original object, it is my understanding that, since the reference is made for a superclass, only members defined by the superclass may be accessed from the subclass.

First - is this correct?

Second: If I am overriding a method and therefore have one in the super and one in the sub, and I create a superclass object and then assign its reference, as I did above, to a subclass object, by the principle called something like Dynamic Method Dispatch, a called overridden method should default to accessing the subclass method right?

Well, my question is:

If a reference to a superclass-object is retooled for a subclass-object and will deny direct object.member access to subclass-defined members, only supporting superclass-defined members, how can, if a superclass reference is retooled for a subclass object, an overridden method apply to the subclass-object if access is limited by the superclass-originated-reference

2 Answers 2

1

If you look at the Java Language Specification (http://docs.oracle.com/javase/specs/) you will find a distinction between the static type and the dynamic type of object references.

The static type of any reference refers to the type declared in your program at development time.

The dynamic type of the reference refers to the type of object which is actually stored at the reference address at runtime.

So if you have a simple hierarchy, where A is the superclass and B is a subclass of A.

class A {
  public String a = "super";
  void m() {...}
}

class B extends A {
  public String a = "sub";
  @Override
  void m() {...}
}

A refA = new A();
A refB = new B();
B refB2 = new B();
refA.m();
refB.m();
System.out.println(refA.a);
System.out.println(refB.a);
System.out.println(refB2.a);

For example, refB's static type is A but its dynamic type is B. What does this imply at runtime?

  1. Instance variables are statically bound, i.e. you can only access the instance variables of the static type, so the first two println() statements will print "super", since A is the static type of refA and refB. The last println() statement will in contrast print "sub", because in this case B is the static type.
  2. Methods are dynamically bound, i.e. the invoked method is first searched in the dynamic type of the reference and if it isn't found, the class hierarchy is traversed up until a definition of the overriden method is found (this mechanism is called dynamic dispatch). This means that in case refA.m() method void m() of A is invoked and in case refB.m(), void m() of B is invoked.
0

You must distinguish between the (dynamic) run-time system in the VM, which holds actual instances of classes, and the (static) text of your program, which contains variables that hold references to objects. Thanks to run-time type information, an object always knows which class it belongs to; when the JVM executes the appropriate invoke* primitive, it will happily run any method of this class. The program text, on the other hand, only knows an upper bound on the type of any object that will be held by a reference.

The only difficulty with accessing methods in an object is to get the correct opcode issued. This is where the compiler comes in. The type system imposes limits on what your programs can do to references; in particular, it doesn't allow you to issue any method call that would be possible at run-time. Instead, it only allows method calls that it can prove to be correct at run-time. Because symbolic logic is incomplete (disclaimer: this is a terrible oversimplification!), no compiler can prove all things about your program that you can see to be true. Therefore it is common to get into situations where the programmer knows that a particular reference will be of a specific type, but the compiler cannot follow their logic and forbids a call that would cause no trouble.

This is a trade-off that buys improved program correctness (by disallowing a whole class of previously very common errors) at the cost of some flexibility. Note that if you're really, really sure that you know more than the compiler, you can cast a reference to a subtype and then invoke the method you wanted; if you were wrong, a ClassCastException occurs, but if you were right, the method is invoked by the run-time system without complaint.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.