5 ms·
I haven't written java in a long time, but I remember this requirement being a thorn in the side of many of my subclasses.
by Vanit 4y ago
I haven't written java in a long time, but I remember this requirement being a thorn in the side of many of my subclasses.
- pulse7 4y agoWhy are so many people writing something like "I haven't written java in a long time, but..." on Java related posts on HN? Is this trendy or what?
- matsemann 4y agoYaeh, you end up having to write crazy one-liners to initialize some other object to pass to your super class.
- jayd16 4y agoIt's easier if you just use a factory pattern. That way your constructors stay as simple allocation and the logic to build instances can break out into full methods. Exceptions can stay out of your constructors and it just makes things easier to work with over all.
- rvcdbn 4y agoI think this Java oddity is the main practical motivation for a lot of factory methods. I wonder if we will see fewer after this change.
- jayd16 4y agoIt's really not. Use before full construction is a pitfall in a lot of languages.
- throwaway2037 4y ago"this Java oddity": Doesn't C++ have the same restriction?
- Karliss 4y agoC++ have similar restriction, but I feel like it has much more reasons for having it. For Java there is Java language and JVM, but the restriction applied only the language, the JVM had to support this anyway. So this JEP tries to loosen the restriction so that Language matches with what the JVM already allows. From syntax perspective, the parent and member constructors (in C++) are in a special list outside function body, it doesn't tease you with it looking like a regular statement inside the constructor body. C++ not only has constructors, but default constructors and destructors. If an exception gets thrown, the compiler needs to ensure that destructors will get called only for the parent classes and members that were initialized. It does so by having very specific order in which the parent and member constructors and destructors get executed. The use RAII mechanism also means that C++ developers are more likely to make classes where construction/destruction has sideffects which need to happen in specific order. Unlike Java where due to all the classes are reference types semantics uninitialized members can be left pointing to null, in c++ any nontrivial member will have the constructor called no matter what. If you don't do it explicitly the default constructor will be called. Implicitly or explicitly in both cases the member constructors will be called in the definition order !BEFORE! the constructor body, but after the parent constructor call. As if things weren't complicated enough, C++ not only supports multiple inheritance it supports the diamond case in both ways: with the shared base class duplicated, and in case of virtual inheritance with shared base class not being duplicated. With all that complexity compiler still needs to ensure that right subset of destructors get executed in case of exception during construction. Some of these problems Java solves with the help of garbage collector. If you get some members laying around after failed construction in Java, sooner or later it will be cleaned up by GC, and since Java doesn't have destructors it isn't critical when exactly it happens.
- saghm 4y agoHonest question: if constructors have these pitfalls, and using a different design pattern fixes the issue, why have constructors at all? Couldn't the whole issue just be avoided by just having a basic, implicit constructor and then just letting people use static methods on top of this?
- joshlemer 4y agoInteresting question. Though, there's not really a factory method equivalent to super(). If you want your subclass factories to also call superclass factories to initialize parent state, you'd need to come up with an additional mechanism in user-space.
- usrusr 4y agoHistorical reasons. Back then it was all about hiding state which was perceived as some hidden walled paradise where there are no rules and yet everybody lived happy and without strife. Some certainly saw the need for dependable rules and so on (after all you can have final fields and be forced to set them in the constructor) but the lure of easy attitude from true believers was so strong that The Bean became the de-facto standard for about a decade. The Bean, with its sole default constructor giving a heartfelt middle finger to everything resembling some kind of predictability. It was a painful learning process from there to where we are now, where we consider any internal state that isn't passed to the constructor in an already immutable shape a considerable smell that you'd better have very good reasons for and we prefer a clear distinction of preparation and operation phases separated by that one constructor which sets (and in most post-OOP languages also declares) the values of the object's constituents. (PS why isn't "post-OOP" a far more established term? It feels considerably more useful to me than the usual mind gymnastics about "functional but not quite" that provoke all those (im)purity squabbles that help noone)
- imtringued 4y ago"The Bean" is mostly a framework compatibility thing. For example, Hibernate needs to be able to proxy your type and cannot intercept member variables. So now you need getters and setters.
- zaphirplane 4y agoWhat’s the difference between 3 overloaded constructors and 3 overloaded factories + a constructor ? The factories will call the constructor, they kind of look similar
- kleiba 4y agoThe idea is that for cases where the initialization of the arguments to super() or this() become more complex, you provide a factory method (often only a single one) where these more complex computations take place and their results are then passed via "new" to one of the constructors, with the resulting object being the return value of the factory method. This makes it easy to create an object where such more complex values are needed for initialization, and it releases your code from jumping through hoops to circumvent the overly restrictive super/init-must-come-first policy, with constructions such as a number of private static methods that are never called from anywhere else besides the constructor. The constructors could still be public, so if you don't have the need to do anything fancy, you might still just call one of the simple constructors directly. However, you often see that API designers restrict this approach in favor to a single point of entry into creating objects of that class. That is, you then have to create objects via a factory, even if you could as easily pass already existing values to a simple constructor. But that's a different story.
- karmakaze 4y agoYup, that was dumb: this(f1(), f2()) is valid, but not with intermediate vars for f1, f2. I never got a good explanation why--because there isn't one.
- tadfisher 4y agoThe reason is a heavy-handed way to avoid leaking "this" before it's constructed. Part of the contract for objects is that you can't interact with an instance before its constructor has run, so that all of its internal state and public fields have been initialized. Likewise for "super", because Java allows subclasses to modify parent fields, which need to be initialized to fulfill the same contract. Swift, which has the same contract, splits the "initialization" phase from the "construction" phase, so you can indeed modify internal state prior to calling "this()" or "super()".
- pkolaczk 4y ago> Part of the contract for objects is that you can't interact with an instance before its constructor has run, This contract doesn't hold anyway, because you can call object's methods from the constructor before the object initialization finishes, and those methods can even call the overriding code in the derived classes. So even though `super` is guaranteed to be initialized fully, `this` is not.
- kevincox 4y agoInterestingly C++ avoids this problem by making virtual method calls use the vtable of the currently constructing type. So for example if you have Super and a subclass Sub. When Super's constructor is running it won't call a method of Sub. Once the parent constructor completes the new vtable is then used. So you can think that the object starts life as a Super then becomes a Sub after the Super constructor finishes. It is complicated and I'm sure it has caused some bugs, but it has also solved some bugs.
- int_19h 4y ago