Why do this() and super() have to be the first statement in a constructor?

前端 未结 21 1951
北荒
北荒 2020-11-21 23:10

Java requires that if you call this() or super() in a constructor, it must be the first statement. Why?

For example:

public class MyClass {
    publi         


        
相关标签:
21条回答
  • 2020-11-21 23:27

    Actually, super() is the first statement of a constructor because to make sure its superclass is fully-formed before the subclass being constructed. Even if you don't have super() in your first statement, the compiler will add it for you!

    0 讨论(0)
  • 2020-11-21 23:31

    So, it is not stopping you from executing logic before the call to super. It is just stopping you from executing logic that you can't fit into a single expression.

    Actually you can execute logic with several expessions, you just have to wrap your code in a static function and call it in the super statement.

    Using your example:

    public class MySubClassC extends MyClass {
        public MySubClassC(Object item) {
            // Create a list that contains the item, and pass the list to super
            super(createList(item));  // OK
        }
    
        private static List createList(item) {
            List list = new ArrayList();
            list.add(item);
            return list;
        }
    }
    
    0 讨论(0)
  • 2020-11-21 23:32

    You asked why, and the other answers, imo, don't really say why it's ok to call your super's constructor, but only if it's the very first line. The reason is that you're not really calling the constructor. In C++, the equivalent syntax is

    MySubClass: MyClass {
    
    public:
    
     MySubClass(int a, int b): MyClass(a+b)
     {
     }
    
    };
    

    When you see the initializer clause on its own like that, before the open brace, you know it's special. It runs before any of the rest of the constructor runs and in fact before any of the member variables are initialized. It's not that different for Java. There's a way to get some code (other constructors) to run before the constructor really starts, before any members of the subclass are initialized. And that way is to put the "call" (eg super) on the very first line. (In a way, that super or this is kind of before the first open brace, even though you type it after, because it will be executed before you get to the point that everything is fully constructed.) Any other code after the open brace (like int c = a + b;) makes the compiler say "oh, ok, no other constructors, we can initialize everything then." So it runs off and initializes your super class and your members and whatnot and then starts executing the code after the open brace.

    If, a few lines later, it meets some code saying "oh yeah when you're constructing this object, here are the parameters I want you to pass along to the constructor for the base class", it's too late and it doesn't make any sense. So you get a compiler error.

    0 讨论(0)
  • 2020-11-21 23:32

    Tldr:

    The other answers have tackled the "why" of the question. I'll provide a hack around this limitation:

    The basic idea is to hijack the super statement with your embedded statements. This can be done by disguising your statements as expressions.

    Tsdr:

    Consider we want to do Statement1() to Statement9() before we call super():

    public class Child extends Parent {
        public Child(T1 _1, T2 _2, T3 _3) {
            Statement_1();
            Statement_2();
            Statement_3(); // and etc...
            Statement_9();
            super(_1, _2, _3); // compiler rejects because this is not the first line
        }
    }
    

    The compiler will of course reject our code. So instead, we can do this:

    // This compiles fine:
    
    public class Child extends Parent {
        public Child(T1 _1, T2 _2, T3 _3) {
            super(F(_1), _2, _3);
        }
    
        public static T1 F(T1 _1) {
            Statement_1();
            Statement_2();
            Statement_3(); // and etc...
            Statement_9();
            return _1;
        }
    }
    

    The only limitation is that the parent class must have a constructor which takes in at least one argument so that we can sneak in our statement as an expression.

    Here is a more elaborate example:

    public class Child extends Parent {
        public Child(int i, String s, T1 t1) {
            i = i * 10 - 123;
            if (s.length() > i) {
                s = "This is substr s: " + s.substring(0, 5);
            } else {
                s = "Asdfg";
            }
            t1.Set(i);
            T2 t2 = t1.Get();
            t2.F();
            Object obj = Static_Class.A_Static_Method(i, s, t1);
            super(obj, i, "some argument", s, t1, t2); // compiler rejects because this is not the first line
        }
    }
    

    Reworked into:

    // This compiles fine:
    
    public class Child extends Parent {
        public Child(int i, String s, T1 t1) {
            super(Arg1(i, s, t1), Arg2(i), "some argument", Arg4(i, s), t1, Arg6(i, t1));
        }
    
        private static Object Arg1(int i, String s, T1 t1) {
            i = Arg2(i);
            s = Arg4(s);
            return Static_Class.A_Static_Method(i, s, t1);
        }
    
        private static int Arg2(int i) {
            i = i * 10 - 123;
            return i;
        }
    
        private static String Arg4(int i, String s) {
            i = Arg2(i);
            if (s.length() > i) {
                s = "This is sub s: " + s.substring(0, 5);
            } else {
                s = "Asdfg";
            }
            return s;
        }
    
        private static T2 Arg6(int i, T1 t1) {
            i = Arg2(i);
            t1.Set(i);
            T2 t2 = t1.Get();
            t2.F();
            return t2;
        }
    }
    

    In fact, compilers could have automated this process for us. They'd just chosen not to.

    0 讨论(0)
  • 2020-11-21 23:33

    The parent class' constructor needs to be called before the subclass' constructor. This will ensure that if you call any methods on the parent class in your constructor, the parent class has already been set up correctly.

    What you are trying to do, pass args to the super constructor is perfectly legal, you just need to construct those args inline as you are doing, or pass them in to your constructor and then pass them to super:

    public MySubClassB extends MyClass {
            public MySubClassB(Object[] myArray) {
                    super(myArray);
            }
    }
    

    If the compiler did not enforce this you could do this:

    public MySubClassB extends MyClass {
            public MySubClassB(Object[] myArray) {
                    someMethodOnSuper(); //ERROR super not yet constructed
                    super(myArray);
            }
    }
    

    In cases where a parent class has a default constructor the call to super is inserted for you automatically by the compiler. Since every class in Java inherits from Object, objects constructor must be called somehow and it must be executed first. The automatic insertion of super() by the compiler allows this. Enforcing super to appear first, enforces that constructor bodies are executed in the correct order which would be: Object -> Parent -> Child -> ChildOfChild -> SoOnSoForth

    0 讨论(0)
  • 2020-11-21 23:33

    My guess is they did this to make life easier for people writing tools that process Java code, and to some lesser degree also people who are reading Java code.

    If you allow the super() or this() call to move around, there are more variations to check for. For example if you move the super() or this() call into a conditional if() it might have to be smart enough to insert an implicit super() into the else. It might need to know how to report an error if you call super() twice, or use super() and this() together. It might need to disallow method calls on the receiver until super() or this() is called and figuring out when that is becomes complicated.

    Making everyone do this extra work probably seemed like a greater cost than benefit.

    0 讨论(0)
提交回复
热议问题