[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)