I have a question about inheritance in Java-like OO programming languages. It came up in my compiler class, when I explained how to compile methods and their invocation. I was using Java as example source language to compile.
Now consider this Java program.
class A {
public int x = 0;
void f () { System.out.println ( "A:f" ); } }
class B extends A {
public int x = 1;
void f () { System.out.println ( "B:f" ); } }
public class Main {
public static void main ( String [] args ) {
A a = new A ();
B b = new B ();
A ab = new B ();
a.f();
b.f();
ab.f();
System.out.println ( a.x );
System.out.println ( b.x );
System.out.println ( ab.x ); } }
When you run it, you get the following result.
A:f
B:f
B:f
0
1
0
The interesting cases are those that happen with the object ab of
static type A, which is B dynamically. As ab.f() prints out
B:f
it follows that method invocations are not affected by the compile-time type of the object the method is invoked with. But
System.out.println ( ab.x ) prints out 0, so member access is affected by compile-time types.
A student asked about this difference: should not the access of members and methods be consistent with each other? I could not come up with a better answer than "that's the semantics of Java".
Would you know a crisp conceptual reason why members and methods are different in this sense? Something I could give my students?
Edit: Upon further investigation, this seems to be a Java idiosyncrasy: C++ and C# act differently, see e.g. Saeed Amiri's comment below.
Edit 2: I just tried out the corresponding Scala program:
class A {
val x = 0;
def f () : Unit = { System.out.println ( "A:f" ); } }
class B extends A {
override val x = 1;
override def f () : Unit = { System.out.println ( "B:f" ); } }
object Main {
def main ( args : Array [ String ] ) = {
var a : A = new A ();
var b : B = new B ();
var ab : A = new B ();
a.f();
b.f();
ab.f();
System.out.println ( "a.x = " + a.x );
System.out.println ( "b.x = " + b.x );
System.out.println ( "ab.x = " + ab.x ); } }
And to my surprise this results in
A:f
B:f
B:f
a.x = 0
b.x = 1
ab.x = 1
Note that the overrise modifiers are necessary. This surprises me because Scala compiles to the JVM, and moreover, when I compile and execute the Java program at the top using the Scala compiler/runtime, it behaves like the Java program.
fandx, without explicitly mentioning this, IMO C# version is better (more acceptable in OO view point), instead of doing this, definex,fas virtual, and you will get consistence result by overriding them inB. But if you ask this in Stackoverflow, you will get more attention and I'm sure nice answer. – Saeed Amiri Nov 20 '12 at 13:53