0

I have a problem regarding this code that contains a Counter class, and MyCounter1 class, and a MyCounter2 class in Java;

I am wondering why the two outputs in the main method result in

output 1: 

cnt1: 2 cnt2: 2 
cnt1: 5 cnt2: 0 


output 2: 
cnt1: 3 cnt2: 3
cat1: 5 cnt2: 0

As far as I understood, when I call the constructor mycounter1 in the first line of code (Counter m = new myCounter 1();) int the main method, I call the constructor of the base class Counter first, which calls the method inc().

since inc() method is overwritten in myCounter1 class i will have to add 2 to cnt1 and cnt2. since my cnt1 is 5, why is the output for cnt1 still 2 and not 5+2=7? and where does the second result come from with cnt=5 and cnt=0 ?

class Counter {
    int cnt1 = 5;
    int cnt2;

    void inc() {
        cnt1 = cnt1 + 1; 
        cnt2 = cnt2 + 1;
    }

    public Counter() {
        inc();           
        cnt1 = cnt2 = 0; 
    }
}
class MyCounter1 extends Counter {
    int cnt1 = 5;

    void inc() {
        cnt1 = cnt1 + 2; 
        cnt2 = cnt2 + 2; 
        System.out.println("cnt1: " + cnt1 + " cnt2: " + cnt2);
    }

    public MyCounter1() {
        System.out.println("cnt1: " + cnt1 + " cnt2: " + cnt2);
    }
}
class MyCounter2 extends MyCounter1 {
    int cnt1;

    void inc() {
        cnt1 = cnt1 + 3; 
        cnt2 = cnt2 + 3; 
        System.out.println("cnt1: " + cnt1 + " cnt2: " + cnt2);
    }
}


public static void main(String[] b) {
    Counter m = new MyCounter1(); ``
    m = new MyCounter2();       
}
3
  • so if there is no super(), i just directly call the call the inc() method that is defined in mycounter1 instead of the parent class? I still don't quite understand why its cnt1= 2 cnt2= 2 and cnt1= 0 cnt2=5 then If I implement the method defined in myCounter i have to add 2 to the cnt variables. and cnt1 is 5 inside that mycounter1 class and cnt2 is 0 (not initialized) . why is it not 7 and 2 then instead of 2 and 2? since 5+2=7? And why do i have cnt1 = 5 and cnt2= 0 as a second output ? Commented Dec 18, 2024 at 9:15
  • do i use the cnt1 from the Counter class or the cnt1 variable from the MyCounter1 class then for calculating cnt1 = cnt 1 + 2 ? (inc method in the MyCounter1 class, child class) They're both initialized with 5 anyway, so I don't quite get why the output of cnt 1 should be 2 and not 7 when calling Counter m = new MyCounter1() in main ? Commented Dec 18, 2024 at 9:49
  • 2
    the lesson here: DO NOT call any potentially overwritable method (inc) while in the constructor Commented Dec 18, 2024 at 11:56

2 Answers 2

2

The easiest way to figure out what is going on is to use the debugger in your IDE and single-step each line of code, examining the values of all the variables at each step.

The field cnt1 in MyCounter1 shadows the field with the same name in Counter. On the other hand, the method inc() in MyCounter1 overrides the method with the same name in Counter.

When you call new MyCounter1(), it implicitly calls super(). So Counter() is called, which then calls inc(). This is overridden by Counter1, so it calls Counter1's inc(). This runs cnt1 = cnt1 + 2;, where cnt1 refers to the field in Counter1, but the Counter1 instance has not yet fully initialized. The super class constructor runs before subclass fields get initialized. Therefore, at this point, cnt1 still has the initial value of 0, because it won't be set to 5 until after the super() constructor has returned.

Once the inc() has returned to the super constructor Counter(), the latter sets cnt1 = cnt2 = 0;. Here, cnt1 refers to the field in Counter, since it's being used in a Counter constructor. The cnt1 field in MyCounter1 is unaffected.

When the super constructor returns, all the MyCounter1 fields are then initialized. So cnt1 gets set to 5. The subclass constructor then runs and prints the values of cnt1 (in MyCounter1, since this shadows the super class's field) and cnt2; hence 5 and 0.

From this, you can figure out the rest. Basically, it is confusing, and a bad idea, to use subclass fields in a superclass constructor. Also, it's best to avoid shadowing, and it is a good idea to annotate overridden fields with @Override.

By the way, this answer is a very handy reference for the order of initialization of a class instance.

Sign up to request clarification or add additional context in comments.

2 Comments

Super helpful. So the variables in the inc method in MyCounter1 always refer to the variables in the same class, MyCounter1, not Counter? so when I'm at thestage where I look at the inc() defined in MyCounter1, I would add cnt1(0) and cnt2(0) by 2? and then print those numbers out? And from this point on i would go back to the super () in Counter? Why is my second output for the first line of code cnt1= 5 and cnt2= 0 then as well? Because in the public Counter() constructor I set cnt1=cnt2=0 or do I have to neglect the remaining part of the constructor in my parent class?
@MariaP. Good question, I've answered it by editing my original answer.
0

The execution order in inheritance & constructor is as follows:

A child constructor is called
    All child fields are "zeroed" (0, false, null, 0.0)
    The super constructor is called
        All super fields are zeroed
        All super field initializations (outside the constructor) are done
        The code of the super constructor is called
            (Note that in the next generation code may be called before super)
            A virtual method is called (without the child fully initialized!)

A better minimalistic example of what happens. (The shadowed variable is obvious and I drop.)

class Base {
    Base() {
        havoc(); // Will normally give a warning virtual-in-constructor.
    }
    void havoc() { // An overwritable virtual method.
    }
}

class Child extends Base {
    String a;         // Uninitialized, null
    String b = null;  // Initialized
    String c = "c";   // Initialized

    Child() {
        // Fields are a: null, b: null and c: null.
        // super(); called, you might call it here explicitly
        //   havoc is called in super (never do this)
        //   resulting for the moment: a: "a", b: "b", c: null
        // Field initializations done, b: null, c: "c"
        // The result: a; "a", b: null, c: "c" 
    }

    @Overwrite
    void havoc() {
        // On first call a: null, b: null, c: null
        a = "a";
        b = "b";
        // For the moment: a: "a", b: "b", c: null
    }
}

Code checkers will give warnings, and virtual methods in constructors are not only problematic because of exceptions, but also of these unexpected data changes.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.