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

前端 未结 21 1924
北荒
北荒 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:36

    I totally agree, the restrictions are too strong. Using a static helper method (as Tom Hawtin - tackline suggested) or shoving all "pre-super() computations" into a single expression in the parameter is not always possible, e.g.:

    class Sup {
        public Sup(final int x_) { 
            //cheap constructor 
        }
        public Sup(final Sup sup_) { 
            //expensive copy constructor 
        }
    }
    
    class Sub extends Sup {
        private int x;
        public Sub(final Sub aSub) {
            /* for aSub with aSub.x == 0, 
             * the expensive copy constructor is unnecessary:
             */
    
             /* if (aSub.x == 0) { 
              *    super(0);
              * } else {
              *    super(aSub);
              * } 
              * above gives error since if-construct before super() is not allowed.
              */
    
            /* super((aSub.x == 0) ? 0 : aSub); 
             * above gives error since the ?-operator's type is Object
             */
    
            super(aSub); // much slower :(  
    
            // further initialization of aSub
        }
    }
    

    Using an "object not yet constructed" exception, as Carson Myers suggested, would help, but checking this during each object construction would slow down execution. I would favor a Java compiler that makes a better differentiation (instead of inconsequently forbidding an if-statement but allowing the ?-operator within the parameter), even if this complicates the language spec.

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

    The question of why Java does this has already been answered, but since I stumbled upon this question hoping to find a better alternative to the one-liner, I'll hereby share my work-around:

    public class SomethingComplicated extends SomethingComplicatedParent {
    
        private interface Lambda<T> {
            public T run();
        }
    
        public SomethingComplicated(Settings settings) {
            super(((Lambda<Settings>) () -> {
    
                // My modification code,
                settings.setting1 = settings.setting2;
                return settings;
            }).run());
        }
    }
    

    Calling a static function should perform better, but I would use this if I insist on having the code "inside" the constructor, or if I have to alter multiple parameters and find defining many static methods bad for readability.

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

    I found a woraround.

    This won't compile :

    public class MySubClass extends MyClass {
        public MySubClass(int a, int b) {
            int c = a + b;
            super(c);  // COMPILE ERROR
            doSomething(c);
            doSomething2(a);
            doSomething3(b);
        }
    }
    

    This works :

    public class MySubClass extends MyClass {
        public MySubClass(int a, int b) {
            this(a + b);
            doSomething2(a);
            doSomething3(b);
        }
    
        private MySubClass(int c) {
            super(c);
            doSomething(c);
        }
    }
    
    0 讨论(0)
  • 2020-11-21 23:40

    Because the JLS says so. Could the JLS be changed in a compatible manner to allow it? Yup.

    However, it would complicate the language spec, which is already more than complicated enough. It wouldn't be a highly useful thing to do and there are ways around it (call another constructor with the result of a static method or lambda expression this(fn()) - the method is called before the other constructor, and hence also the super constructor). So the power to weight ratio of doing the change is unfavourable.

    Note that this rule alone does not prevent use of fields before the super class has completed construction.

    Consider these illegal examples.

    super(this.x = 5);
    
    super(this.fn());
    
    super(fn());
    
    super(x);
    
    super(this instanceof SubClass);
    // this.getClass() would be /really/ useful sometimes.
    

    This example is legal, but "wrong".

    class MyBase {
        MyBase() {
            fn();
        }
        abstract void fn();
    }
    class MyDerived extends MyBase {
        void fn() {
           // ???
        }
    }
    

    In the above example, if MyDerived.fn required arguments from the MyDerived constructor they would need to be sleazed through with a ThreadLocal. ;(

    Incidentally, since Java 1.4, the synthetic field that contains the outer this is assigned before inner classes super constructor is called. This caused peculiar NullPointerException events in code compiled to target earlier versions.

    Note also, in the presence of unsafe publication, construction can be viewed reordered by other threads, unless precautions are made.

    Edit March 2018: In message Records: construction and validation Oracle is suggesting this restriction be removed (but unlike C#, this will be definitely unassigned (DU) before constructor chaining).

    Historically, this() or super() must be first in a constructor. This restriction was never popular, and perceived as arbitrary. There were a number of subtle reasons, including the verification of invokespecial, that contributed to this restriction. Over the years, we've addressed these at the VM level, to the point where it becomes practical to consider lifting this restriction, not just for records, but for all constructors.

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

    I know I am a little late to the party, but I've used this trick a couple of times (and I know it's a bit unusual):

    I create an generic interface InfoRunnable<T> with one method:

    public T run(Object... args);
    

    And if I need to do something before passing it to the constructor I just do this:

    super(new InfoRunnable<ThingToPass>() {
        public ThingToPass run(Object... args) {
            /* do your things here */
        }
    }.run(/* args here */));
    
    0 讨论(0)
  • 2020-11-21 23:45

    I am fairly sure (those familiar with the Java Specification chime in) that it is to prevent you from (a) being allowed to use a partially-constructed object, and (b), forcing the parent class's constructor to construct on a "fresh" object.

    Some examples of a "bad" thing would be:

    class Thing
    {
        final int x;
        Thing(int x) { this.x = x; }
    }
    
    class Bad1 extends Thing
    {
        final int z;
        Bad1(int x, int y)
        {
            this.z = this.x + this.y; // WHOOPS! x hasn't been set yet
            super(x);
        }        
    }
    
    class Bad2 extends Thing
    {
        final int y;
        Bad2(int x, int y)
        {
            this.x = 33;
            this.y = y; 
            super(x); // WHOOPS! x is supposed to be final
        }        
    }
    
    0 讨论(0)
提交回复
热议问题