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.
f
andx
, without explicitly mentioning this, IMO C# version is better (more acceptable in OO view point), instead of doing this, definex
,f
as 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. $\endgroup$