7
$\begingroup$

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.

$\endgroup$
5
  • 3
    $\begingroup$ This is related to your programming language, how deals with variable initialization, if you do so in C#: ideone.com/q6KpKk you will get another result, your problem is you overrided f and x, without explicitly mentioning this, IMO C# version is better (more acceptable in OO view point), instead of doing this, define x,f as virtual, and you will get consistence result by overriding them in B. But if you ask this in Stackoverflow, you will get more attention and I'm sure nice answer. $\endgroup$
    – user742
    Nov 20, 2012 at 13:53
  • $\begingroup$ @SaeedAmiri thanks for the C#, I expected it to be the same there. If I don't get good answers here, I'll crosspost to Stackoverflow. $\endgroup$ Nov 20, 2012 at 14:02
  • $\begingroup$ Please past a link when you crosspost. $\endgroup$
    – Nejc
    Nov 20, 2012 at 14:13
  • 1
    $\begingroup$ @SaeedAmiri: One cannot override fields in Java. One can only shadow fields. $\endgroup$ Nov 20, 2012 at 15:13
  • $\begingroup$ I might be wrong but I read recently that Java allows only "method overriding" and "field overriding" is not supported. So when you create an instance of class A then for the field it will look for the closest definition of entity x which it will find in class A and hence the result. People can correct me if I am wrong here. $\endgroup$ Apr 13, 2016 at 6:38

3 Answers 3

6
$\begingroup$

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.)

$\endgroup$
6
  • $\begingroup$ Hello @DaveClarke your explanation why ab.x should return A's attribute x is cogent. But that very same reason is also valid for members at type A, and indeed C# and C++ do invoke A's method f in ab.f(). $\endgroup$ Nov 20, 2012 at 16:44
  • 2
    $\begingroup$ Historically, C++ allowed both dynamic (virtual) and static dispatch. Java designers deemed this to be too complex and, following languages like Eiffel and Smalltalk, supported only semantics for method dispatch. Again, one can argue that this is both simpler and more natural. $\endgroup$ Nov 20, 2012 at 18:14
  • $\begingroup$ Hello @DaveClarke, I agree that virtualising every method is simpler. But it would be even more uniform if members were also virtualised (and then could not have arbitrary types). After all, one could see methods as higher-order members. I'm not saying this is the right thing to do, but I wonder if there is a crip, conceptual reason for making such a drastic distinction between methods and members. $\endgroup$ Nov 20, 2012 at 18:33
  • $\begingroup$ Ultimately virtualising members/fields would not really mean anything. There would be one field x for the entire class hierarchy. What does it mean to override x? Simply to give it a new value. So the issue is really, why did Java choose all virtual methods and shadowing of fields? $\endgroup$ Nov 20, 2012 at 19:18
  • $\begingroup$ But, @DaveClarke, the Scala example above appears to me to show that Scala does indeed virtualise its members (you have to override them to achieve typability. So some language make that choice. Maybe there aren't any clearcut, simple arguments to be found in this space? $\endgroup$ Nov 21, 2012 at 9:56
4
$\begingroup$

In Java, there is no 'static type of an object' - there is the type of the object, and there is the type of the reference. All Java methods are 'virtual' (to use C++ terminology), i.e. they are resolved based on the object's type. And btw. '@Override' has no effect whatsoever on the compiled code - it is just a safeguard that generates a compiler error if the so annotated method does not override a superclass' method.

Field access on the other hand is never resolved dynamically - or, to put it differently, Java uses no polymorphism when accessing fields. In the (pathological) case that a subclass has a field x with the same name as a superclass field, the subclass has two fields 'x'. Obviously, there is name shadowing - the name 'x' can be bound to only one of these fields in a given context.

Enter name resolution syntax - when you write b.x, the compiler resolves this to the field declared in the subclasss, but when you write ab.x, the compiler resolves that to the field declared in A - both fields being members of class B. You can access both fields from code in class B using super.x:

class B { ... void g() {System.out.println("both: " + x + " and " + super.x);} }

So at runtime, it makes no difference at all whether the subclass' field was named 'x' or 'y' - in both cases, the subclass has two distince fields with no relationship between them.

$\endgroup$
1
  • $\begingroup$ You are right @Arno but why does Java make these choices, and why do C# and Scala make different choices? Is there a good clearcut reason for this? $\endgroup$ Nov 22, 2012 at 16:46
1
$\begingroup$

Remembering that inheritance is a form of saying B "is a" A, except the functionality of A might be extended, inherited member variables can be considered attributes of the object. Intuitively, the way we are taught about inheritance (admittedly from a Java-ish point of view), member variables are like attributes, and common attributes are not meant to be extended, rather only functionality is.

For example, suppose you have a Person class, and the person has a name variable. The name intuitively won't get changed, even if we extend Person to Student and Professor. However methods, for example like Student.do_work() do get extended, to do "homework", while the Professor class might grade tests in Professor.do_work().

Of course you can break this intuition, and you can essentially make member variables equivalent to properties, or use getters, which can be overridden etc., but I think what I said above was the intuition of doing it this way. In fact, I'd say it would be unconventional in most cases to have a member variable of the same name, in a derived class.

$\endgroup$

Your Answer

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

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