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