Inheritance, and dynamic access to members/attributes and methods in Java-like languages 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.
I suspect that this is intimately related to shadowing of fields in Java.

When writing a derived class, I can, as in your example, write a field x that shadows the definition of x in the base class. This means that in the derived class, the original definition is no longer accessible via name x. The reason Java allows this is so that derived class implementations are less dependent on the base class implementation details, thereby (only partially) avoiding the fragile base class problem. For instance, if the base class did not originally have a field x, but then subsequently introduced one, Java's semantics avoid this breaking the derived class implementation.

The variable x in the derived class can have a completely different type than the variable in the base class – I tested this, it works. This is in stark contrast to method overriding, where the type needs to be sufficiently compatible (aka, the same). So when I have a variable of type A in your example, the only type I can derive for it is the one specified in class A (or above). As this can be different from the type declared in class B, it is only type safe to return the value of the field x from class A. (And naturally, the semantics must be the same regardless of what the type of the field is in class B.)