[HN Gopher] JEP draft: No longer require super() and this() to a...
       ___________________________________________________________________
        
       JEP draft: No longer require super() and this() to appear first in
       a constructor
        
       Author : mfiguiere
       Score  : 74 points
       Date   : 2023-01-22 20:54 UTC (2 hours ago)
        
 (HTM) web link (openjdk.org)
 (TXT) w3m dump (openjdk.org)
        
       | londons_explore wrote:
       | Undocumented downside:
       | 
       | Java becomes a slightly harder language to learn, because now
       | there is one more thing to explain to a beginner: "Where exactly
       | should super() be put in the constructor? At the start, in the
       | middle, or at the end? What are the benefits and downsides of
       | each, and when does it matter? Is there a convention?"
        
         | mcculley wrote:
         | As someone who has been using Java since 1.0, I welcome these
         | little improvements but agree that the language is now a mess
         | for a beginner. All of the little rules and corners make sense
         | to someone who learned them gradually. To a beginner, they must
         | seem nonsensical and intimidating.
        
           | londons_explore wrote:
           | This is the evolution of nearly any language... It begins
           | simple and easy, and over time more and more features and
           | syntax are added, and it becomes very hard to learn from
           | scratch, which in turn means beginners end up using other
           | simpler languages.
           | 
           | I think python is a classic example of this - python 2 was
           | pretty simple. Python 3 just gets more and more complex.
        
             | cratermoon wrote:
             | The language formerly known as Perl 6 has entered the chat.
        
         | [deleted]
        
         | rcme wrote:
         | Why does super need to go in a specific position? Isn't "super
         | calls the parent classes constructor" simpler than " super
         | calls the parent classes constructor and it must be the first
         | statement in the child constructor."
        
           | tsimionescu wrote:
           | The problem begins when you want to do more complex things.
           | For example, you can't call super() inside a try{} block as
           | far as I understand, but that doesn't immediately follow from
           | "super calls the parent class' constructor" - and it did
           | follow from "either super() or this() must be the first
           | statement in your constructor".
        
           | revetkn wrote:
           | That's how I view it as well, from an end user perspective
           | this is removing an arbitrary rule that you're required to
           | "just memorize" (or be surprised when your IDE complains).
        
           | avereveard wrote:
           | it's simpler for the subclass.
           | 
           | the parent class however now has to deal with an arbitrary
           | set of operation that the sub class can perform between the
           | initializer block and the constructor method, so it's
           | contract is overall much harder to define, because you can no
           | longer be sure of your internal state in the constructor.
        
             | rcme wrote:
             | This seems like a solvable problem with static analysis:
             | don't allow reads from instance variables / methods of the
             | parent class until super is called.
        
       | wruza wrote:
       | For non-java guys like me: this() is used to call one constructor
       | from the other of the same class.
        
       | layer8 wrote:
       | Relatedly, I don't know if the following has already been fixed
       | in more recent Java versions, but it should:                 Foo
       | foo;       try       {           foo = bar(baz);       }
       | catch (SomeException ex)       {           foo = SOME_DEFAULT;
       | }
       | 
       | For some reason, _foo_ doesn't count as being initialized after
       | the _try_ statement. As a workaround, one can factor it out into
       | a separate method and have "return" there instead of "foo =".
        
       | garblegarble wrote:
       | I really wish useful quality of life things like "?." would get
       | added instead of this kind of stuff that really doesn't improve
       | life all that much for most development
        
         | simplotek wrote:
         | > (...) instead of this kind of stuff that really doesn't
         | improve life all that much for most development
         | 
         | There are comments in this very discussion praising this change
         | due to how this stuff really improves their life as a
         | developer.
         | 
         | Anyway, can you pass the link to your JEP with a proposal for
         | "?." ?
        
         | [deleted]
        
       | TedDoesntTalk wrote:
       | Why? Just because the JVM can?
        
         | zmmmmm wrote:
         | it's really annoying when you want to subclass something but do
         | a transformation, logic or validation on the arguments that are
         | to be passed through to the super class, because you can't put
         | any code prior to the this() or super() call to do that. In
         | practice it means people stuff all kinds of awkward expressions
         | inside the this() or super() which get very ugly, or are forced
         | to move these out into ancilliary methods which has other
         | drawbacks.
        
         | RedShift1 wrote:
         | Why not?
        
         | mabbo wrote:
         | The linked JEP provides a very well written set of reasons.
        
       | Vanit wrote:
       | 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.
        
         | matsemann wrote:
         | Yaeh, you end up having to write crazy one-liners to initialize
         | some other object to pass to your super class.
        
           | jayd16 wrote:
           | It'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.
        
       | RedShift1 wrote:
       | This would be awesome. I tend to do a lot of work in the
       | constructor, this would definitely help clean up different kinds
       | of constructors.
        
       | [deleted]
        
       | charcircuit wrote:
       | It's always bothered me how                 public A(String s) {
       | this(s.isEmpty());       }
       | 
       | is allowed but                 public A(String s) {
       | boolean helpfulName = s.isEmpty();         this(helpfulName);
       | }
       | 
       | isn't
        
         | dehrmann wrote:
         | There's something to be said for making sure the parent
         | constructor is the first call made. It means any method you
         | call in the current constructor will work correctly or
         | obviously be your fault, not the parent class's.
         | 
         | Yes, in your particular case, the compiler could figure out
         | that you're not using "this," but you can only make a compiler
         | so clever.
        
         | layer8 wrote:
         | Yes, though in that case you can use                 public
         | A(String s) {         this(helpfulName(s));       }
         | 
         | with a static method.
        
       | mabbo wrote:
       | I enjoy that the JEP process allows for nice little small things
       | like this. This isn't "we should have lambdas" or "Let's rebuild
       | the type system to allow List<int>". But it's still a very common
       | annoyance, and it can be fixed.
        
         | revetkn wrote:
         | Totally agree, and I also love that so much thought and
         | consideration goes into how the platform gets improved over
         | time. Doing it "right" means taking so many things into account
         | -- many of which are subtle -- and picking the right tradeoffs.
         | Hats off to the people quietly doing the deep work which makes
         | everyone's lives better.
        
       | pron wrote:
       | This JEP touches on an interesting tension in language design
       | (and perhaps many other kinds of design) between a desire for
       | rules that aren't unnecessarily restrictive and a desire for
       | rules that are easy to communicate to the user. I.e. there's
       | value in accepting more correct programs but there's also value
       | in easily communicating why a program is rejected, and sometimes
       | there can be tension between the two.
        
         | paddy_m wrote:
         | Can you expand on this. does this change make it easy to
         | communicate why a program was rejected?
        
           | lmm wrote:
           | This change means that some programs won't be rejected when
           | previously they would be.
        
           | rustyminnow wrote:
           | I think this change would make it harder to explain why a
           | program was rejected, even though it would allow a programmer
           | more flexibility.
           | 
           | It's easier to just say "this() must ALWAYS be called first
           | in constructor" and everybody understands... than to try and
           | say "you can call this() after other statements, but not in a
           | try block and not if those statements reference the instance
           | under construction" which will certainly allow you to do more
           | interesting things, but also be more confusing.
        
       | grishka wrote:
       | One more way to bypass this requirement is to add a private
       | static method that returns something you want to pass to
       | super()/this().
       | 
       | And on the subject of Java restrictions that get in the way: if
       | you use a local variable in an anonymous inner class or lambda,
       | it needs to be final. This presents a problem for when you call a
       | method that runs your lambda at some point before it returns and
       | you want to modify some variables from there. The ugly workaround
       | is to declare a final single-element array. It satisfies "needs
       | to be final", but its single element can still be assigned to
       | from anywhere since "final" only applies to the object reference
       | to the array itself.
        
         | [deleted]
        
         | layer8 wrote:
         | > One more way to bypass this requirement is to add a private
         | static method
         | 
         | This doesn't work if it requires multiple arguments to be
         | generated with common code, unless you're okay with effectively
         | running that common code twice, which in turn is problematic
         | when it has side-effects (like logging). See
         | https://news.ycombinator.com/item?id=34482908 for an
         | alternative way of solving this.
        
       | rcme wrote:
       | Given that the entire constructor call stack is known at compile
       | time, it should be possible to use static analysis to detect if
       | fields are used before initialization. Swift does this, I
       | believe.
        
         | layer8 wrote:
         | You can call methods from constructors, including methods
         | overridden in unknown subclasses, or methods in superclasses
         | whose implementation may change later, which complicates
         | things. You can also pass _this_ to foreign code from a
         | constructor, which in turn may call arbitrary methods and /or
         | access fields on the instance.
         | 
         | Presumably the rule will be that any explicit or implicit use
         | of _this_ within the constructor body before a call to _this_
         | () or _super_ () will be forbidden.
        
           | rcme wrote:
           | Sure, the super class can call unknown methods in subclasses
           | in its constructor, but the subclass has knowledge of the
           | super class, so the subclass should fail to compile if it
           | overrides a super class method that access instance variables
           | before they're initialized.
        
             | layer8 wrote:
             | Superclass implementations can change later (you can
             | recompile the superclass without having to recompile the
             | subclass), you can't assume their current implementation
             | will stay the same.
        
               | rcme wrote:
               | You could also remove a method from the superclass
               | entirely in such a situation. This would cause an
               | invocation of the removed method to fail if the
               | superclass depended on it. In general you need to
               | recompile the subclass of the superclass changes.
        
           | georgelyon wrote:
           | The way Swift handles this is that you can't do anything that
           | involves the self pointer before calling super.
        
       | tubs wrote:
       | Best part about this is finally being able to avoid the dance of
       | 'oh no the super called an overridden method and kinda-sorta-
       | broke but not fatally, whoopsies'.
        
       | oweiler wrote:
       | One of the many reasons to prefer static factories to
       | constructors.
        
         | mcculley wrote:
         | How does that help the implementor of a subclass?
        
         | vips7L wrote:
         | I personally don't like static factories that much. They have
         | their place, but half the JDK is now `.of` or `.from` or
         | `.newInstance` or `.get`.
         | 
         | I'd really like to be able to define a constructor on sealed
         | interfaces:                   sealed interface Path permits
         | WindowsPath, UnixPath {             public Path(String p) {
         | if (isWindows()) {                     return new
         | WindowsPath(p);                 }                      return
         | new UnixPath(p);             }         }
         | 
         | That way user code to construct an object is always the same:
         | var p = new Path(s);
         | 
         | Instead of:                   var p = Paths.get(s);
        
         | cratermoon wrote:
         | One of the many reasons to prefer composition over inheritance.
        
         | layer8 wrote:
         | Alternatively (in particular for subclassing), it can always be
         | worked around with an intermediate object. For example:
         | Constructor(A a, B b) { ... }            Constructor(C c, D d)
         | {           // A and B can be derived from C and D, and
         | // we want to forward to the (A, B) constructor,           //
         | so we use an intermediate object to do the           //
         | conversion and provide A and B:                      this(new
         | Intermediate(c, d));       }            private
         | Constructor(Intermediate x)       {           this(x.a, x.b);
         | }
         | 
         | Of course, with the JEP this will become much simpler.
        
       ___________________________________________________________________
       (page generated 2023-01-22 23:00 UTC)