Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does the "this" keyword in Java inheritance work?

In the below code snippet, the result is really confusing.

public class TestInheritance {
    public static void main(String[] args) {
        new Son();
        /*
        Father father = new Son();
        System.out.println(father); //[1]I know the result is "I'm Son" here
        */
    }
}

class Father {
    public String x = "Father";

    @Override
    public String toString() {
       return "I'm Father";
    }

    public Father() {
        System.out.println(this);//[2]It is called in Father constructor
        System.out.println(this.x);
    }
}

class Son extends Father {
    public String x = "Son";

    @Override
    public String toString() {
        return "I'm Son";
    }
}

The result is

I'm Son
Father

Why is "this" pointing to Son in the Father constructor, but "this.x" is pointing to "x" field in Father. How is the "this" keyword working?

I know about the polymorphic concept, but won't there be different between [1] and [2]? What's going on in memory when new Son() is triggered?

like image 748
Garnett Avatar asked Apr 14 '14 08:04

Garnett


People also ask

How does the this keyword work in Java?

Within an instance method or a constructor, this is a reference to the current object — the object whose method or constructor is being called. You can refer to any member of the current object from within an instance method or a constructor by using this .

Is this keyword used in inheritance?

The keyword used for inheritance is extends.

How super and this are useful with inheritance in Java?

This is used when we want to make a call to the parent class method. So when the parent and the child class have the same name for a method, so to resolve the ambiguity we make use of the super keyword to access the parent class method in the base class.

What is inheritance in Java?

This process is known as Inheritance in Java. In order to inherit from a class, the keyword ‘extends’ is used. Inheritance in Java permits the reusability of code so that a class only needs to write the unique features, and the rest of the code can be extended from the other class.

What is the use of this keyword in Java?

There can be a lot of usage of Java this keyword. In Java, this is a reference variable that refers to the current object. Here is given the 6 usage of java this keyword. this can be used to refer current class instance variable. this () can be used to invoke current class constructor. this can be passed as an argument in the method call.

Which keyword is used to inherit the properties of a class?

The class which inherits the properties of other is known as subclass (derived class, child class) and the class whose properties are inherited is known as superclass (base class, parent class). extends is the keyword used to inherit the properties of a class.

Which class inherits the properties of other class in Java?

The class which inherits the properties of other is known as subclass (derived class, child class) and the class whose properties are inherited is known as superclass (base class, parent class).


4 Answers

All member functions are polymorphic in Java by default. That means when you call this.toString() Java uses dynamic binding to resolve the call, calling the child version. When you access the member x, you access the member of your current scope (the father) because members are not polymorphic.

like image 186
Manu343726 Avatar answered Oct 19 '22 16:10

Manu343726


Two things are going on here, let's look at them:

First of all, you are creating two different fields. Taking a look at a (very isolated) chunks of the bytecode, you see this:

class Father {
  public java.lang.String x;

  // Method descriptor #17 ()V
  // Stack: 2, Locals: 1
  public Father();
        ...
    10  getstatic java.lang.System.out : java.io.PrintStream [23]
    13  aload_0 [this]
    14  invokevirtual java.io.PrintStream.println(java.lang.Object) : void [29]
    17  getstatic java.lang.System.out : java.io.PrintStream [23]
    20  aload_0 [this]
    21  getfield Father.x : java.lang.String [21]
    24  invokevirtual java.io.PrintStream.println(java.lang.String) : void [35]
    27  return
}

class Son extends Father {

  // Field descriptor #6 Ljava/lang/String;
  public java.lang.String x;
}

Important are lines 13, 20 and 21; the others represent the System.out.println(); itself, or the implicit return;. aload_0 loads the this reference, getfield retrieves a field value from an object, in this case, from this. What you see here is that the field name is qualified: Father.x. In the one line in Son, you can see there is a separate field. But Son.x is never used; only Father.x is.

Now, what if we remove Son.x and instead add this constructor:

public Son() {
    x = "Son";
}

First a look at the bytecode:

class Son extends Father {
  // Field descriptor #6 Ljava/lang/String;
  public java.lang.String x;

  // Method descriptor #8 ()V
  // Stack: 2, Locals: 1
  Son();
     0  aload_0 [this]
     1  invokespecial Father() [10]
     4  aload_0 [this]
     5  ldc <String "Son"> [12]
     7  putfield Son.x : java.lang.String [13]
    10  return
}

Lines 4, 5 and 7 look good: this and "Son" are loaded, and the field is set with putfield. Why Son.x? because the JVM can find the inherited field. But it's important to note that even though the field is referenced as Son.x, the field found by the JVM is actually Father.x.

So does it give the right output? Unfortunately, no:

I'm Son
Father

The reason is the order of statements. Lines 0 and 1 in the bytecode are the implicit super(); call, so the order of statements is like this:

System.out.println(this);
System.out.println(this.x);
x = "Son";

Of course it's gonna print "Father". To get rid of that, a few things could be done.

Probably the cleanest is: don't print in the constructor! As long as the constructor hasn't finished, the object is not fully initialized. You are working on the assumption that, since the printlns are the last statements in your constructor, your object is complete. As you have experienced, this is not true when you have subclasses, because the superclass constructor will always finish before your subclass has a chance to initialize the object.

Some see this as a flaw in the concept of constructors itself; and some languages don't even use constructors in this sense. You could use an init() method instead. In ordinary methods, you have the advantage of polymorphism, so you can call init() on a Father reference, and Son.init() is invoked; whereas, new Father() always creates a Father object. (of course, in Java you still need to call the right constructor at some point).

But I think what you need is something like this:

class Father {
    public String x;

    public Father() {
        init();
        System.out.println(this);//[2]It is called in Father constructor
        System.out.println(this.x);
    }

    protected void init() {
        x = "Father";
    }

    @Override
    public String toString() {
        return "I'm Father";
    }
}

class Son extends Father {
    @Override
    protected void init() {
        //you could do super.init(); here in cases where it's possibly not redundant
        x = "Son";
    }

    @Override
    public String toString() {
        return "I'm Son";
    }
}

I don't have a name for it, but try it out. It will print

I'm Son
Son

So what's going on here? Your topmost constructor (that of Father) calls an init() method, which is overridden in a subclass. As all constructor call super(); first, they are effectively executed superclass to subclass. So if the topmost constructor's first call is init(); then all of the init happens before any constructor code. If your init method fully initializes the object, then all constructors can work with an initialized object. And since init() is polymorphic, it can even initialize the object when there are subclasses, unlike with the constructor.

Note that init() is protected: subclasses will be able to call and override it, but classes in other package won't be able to call it. That's a slight improvement over public and should be considered for x too.

like image 43
Silly Freak Avatar answered Oct 19 '22 15:10

Silly Freak


As other stated, you cannot override fields, you can only hide them. See JLS 8.3. Field Declarations

If the class declares a field with a certain name, then the declaration of that field is said to hide any and all accessible declarations of fields with the same name in superclasses, and superinterfaces of the class.

In this respect, hiding of fields differs from hiding of methods (§8.4.8.3), for there is no distinction drawn between static and non-static fields in field hiding whereas a distinction is drawn between static and non-static methods in method hiding.

A hidden field can be accessed by using a qualified name (§6.5.6.2) if it is static, or by using a field access expression that contains the keyword super (§15.11.2) or a cast to a superclass type.

In this respect, hiding of fields is similar to hiding of methods.

A class inherits from its direct superclass and direct superinterfaces all the non-private fields of the superclass and superinterfaces that are both accessible to code in the class and not hidden by a declaration in the class.

You can access Father's hidden fields from Son's scope using super keyword, but the opposite is impossible since Father class is not aware of its subclasses.

like image 7
ssssteffff Avatar answered Oct 19 '22 15:10

ssssteffff


While methods can be overridden, attributes can be hidden.

In your case, the attribute x is hidden: in your Son class, you can't access the Father's x value unless you use the super keyword. The Father class doesn't know about the Son's x attribute.

In the opposit, the toString() method is overriden: the implementation that will always be called is the one of the instantiated class (unless it does not override it), i.e. in your case Son, whatever the variable's type (Object, Father...).

like image 6
sp00m Avatar answered Oct 19 '22 16:10

sp00m