Java Enhancement Proposal: Field Accessors
Julian Waters
tanksherman27 at gmail.com
Fri Mar 25 03:30:30 UTC 2022
Hi Brian, thanks for the review,
The current JEP was written with 395's records in mind and aimed to have
naming matching as closely as possible to the synthetic methods generated
for records. Since records simply reused the name of the field, I
assumed that adding a "set" in front for write methods was acceptable. I
also notice that a lot of the issues faced seem to be related to the syntax
and boilerplate/tuning, as you mentioned. In that instance we could ditch
the syntactically heavy declarations that turn into synthetic methods for a
more concise way to write accessor methods for fields without having to
repeat the typical method boilerplate over and over (and in turn avoiding
the naming conundrum brought up previously), indeed the ultimate goal of
this JEP is to reduce repeated boilerplate for accessors and does not mind
the manner it achieves it in. I don't quite get why "The concept of
accessors as ordinary methods is inherently lossy" though, could you
elaborate slightly more on that? The current proposal does not really mean
to introduce first class properties like those seen in other language like
Kotlin and C# (although it is similar in a sense) since those have a whole
set of limitations that come alongside such an approach, which I personally
hope we can rectify in Java with the power of conventional method overloads
Thanks for your reply, appreciate it and I apologize if this took up a lot
of your time
P.S. This was also written in relation to Amber's with expressions draft,
which mentions accessors as a possible future language feature, which is
why I decided to send this JEP here. On a side note, I personally think we
can do away with the with (no pun intended) and just have braces directly
after for that particular draft.
best regards,
Julian
On Thu, Mar 24, 2022 at 9:46 PM Brian Goetz <brian.goetz at oracle.com> wrote:
> The topic of "properties in Java" has a long and fraught history. Many
> attempts have been made in the past to lick this third rail. Such
> discussions usually have a predicable trajectory; initially there appears
> to be broad desire for such a feature, but then when the discussions get
> into the details, it turns out there isn't much support for *this* variant
> of the feature, or there are design wars over which design is better,
> because everyone knows that at most one will be selected. The most bitter
> and nasty e-mails in "Project Coin" were those between proponents of
> competing properties proposals.
>
> This proposal remixes a number of existing ideas (e.g., borrows from C#'s
> property syntax), plus seizes on the precedent of records (where
> declarations in one place turn into synthetic methods and other members.)
>
> Such proposals historically have had to deal with a number of challenges:
>
> - It is extremely difficult to do this in a way that doesn't just feel
> like a "macro generator" nailed on the side of the language;
>
> - It has a high syntax-to-semantics ratio, meaning that such proposals
> usually die in a bikeshed fire;
>
> - Invariably there are an infinite stream of enhancement requests to
> "tune" the "macro expansion" -- accessibility modifiers, tuning the name
> generation, implicit adaptation (e.g., String field, Optional<String>
> accessors), validation (most frequently null checks), related boilerplate
> features (builders, generation of Object methods), constructors and
> factories, derived properties (accessors not mapped to a specific field),
> etc;
>
> - Such proposals tend to have a lot of detail but relatively little
> "depth", offering a poor return on complexity;
>
> - It is "fighting the last war", in that the model of "every class
> should have getters and setters for their fields" is finally starting to
> fade;
>
> - The concept of accessors as ordinary methods is inherently lossy.
> While the author understands that `getX` and `setX` are related somehow,
> the language, and reflection libraries, do not. (It's what we have; the
> choice to separate field and method namespaces slammed the door pretty much
> forever on first-class properties.)
>
>
> Additionally, we confronted a lot of these issues when we did records. We
> looked at all the popular "macro generator" and properties libraries (e.g.,
> Lombok, Joda Beans, Immutables, etc), and asked ourselves which of the
> features from these libraries belonged in records. We struggled with the
> concept of accessors, and whether the record treatment of accessors (as
> well as other features) could scale to arbitrary classes.
>
> We made a number of decisions during the design of records that may be
> informative here, including:
>
> - The language does not get in the business of naming conventions.
> Records have public constructors and no factories largely because factories
> are not a language feature, they are merely a convention. It would have
> have been acceptable for the language to bless an arbitrary name like "of"
> for factories. If factories had been a language feature, records might
> have leaned on them; in fact, during the design of records, we explored a
> number of interpretations of "factories as a language feature", but in the
> end, decided to prune that branch of inquiry. (Some might want to argue
> that even picking `x()` as the accessor name for field `x` was getting into
> the naming convention business, but this usually turns out to be arguing
> for its own sake.) We also considered how, if we extended the read
> accessors from records to arbitrary classes, what the story might be for
> setters. Your choice of `setX` falls afoul of this principle (as did many
> of the alternatives we explored.)
>
> - The stable number of "tuning knobs" is zero. For every
> not-always-perfect decision we made with records, someone always wants to
> propose "just one" option for tuning it (e.g., "the factory name is
> `newFoo`", "exclude this component from the equals computation", etc). The
> only stable number of such knobs is zero; once there is one, there will be
> many, and you will have turned the language into Lombok. A language
> feature that requires many knobs (whether keywords, annotations,
> "convention over configuration", etc) is probably the wrong design; leave
> that for code generators.
>
>
>
>
>
>
>
> On 3/23/2022 10:29 PM, Julian Waters wrote:
>
> Hi all,
>
>
> It would be appreciated if I could get feedback and refinements
>
> on the current Amber JEP draft.
>
>
> Thanks and have a great day! :)
>
>
> best regards,
>
> Julian
>
>
> Summary
> -------
>
> Enhance Java with language level field accessors.
>
> Accessors provide an easy way to define logic for setting
>
> and retrieving field values in what would otherwise be a
>
> tedious process of writing multiple methods (with potential
>
> overloads)
>
> Goals
> -----
>
> It is intended for this new feature to work with full
>
> compatibility with existing manually defined methods,
>
> as well as with inheritance and subclass overriding,
>
> and to provide a clean way to define accessors with
>
> different signatures.
>
>
> Motivation
> ----------
>
> From JEP-395: "It is a common complaint that
>
> "Java is too verbose" or has "too much ceremony"."
>
>
> Currently, if one wishes to have private fields with
>
> public methods to modify them, it is required to write
>
> at least one getter and setter. In the worst case, it
>
> is required to write multiple copies of the exact same method
>
> if there need to be multiple overloads. This eventually
>
> becomes overly tedious or error prone, and a common desire
>
> is that Java had cleaner syntax for such field access. It
>
> may be tempting to ask why direct field modification is not
>
> then used instead, but accessing a field through methods
>
> allows custom logic to be executed, which is particularly
>
> useful for state checking and possibly returning or setting
>
> a different value if the situation call for it. Other
>
> languages, such as Kotlin and C#, have dedicated syntax
>
> for such "getters and setters", but their approach only
>
> allows for one such set to be specified, and no extra
>
> parameters other than the built in ones are allowed. We
>
> should not abandon the flexibility overloads in Java
>
> provide for the sake of such a feature, so we'll need an
>
> approach that combines the compactness of other such
>
> languages with the more Java-esque method style for full
>
> flexibility and ease of use.
>
>
> Description
> -----------
>
>
> The current proposal proposes the syntax for field
>
> accessors as follows:
>
>
> // Getters have the same name as the field
> // Setters have the name of set<PascalCase name of the field>
>
> private long field = 0 {
> // Getter, leave semicolons after return to define a getter
> without running any custom logic, or a block if custom logic is
> required
> return;
>
> // Getter that executes custom logic
> // If no return is specified in the getter, it will implicitly
> return the field bound to it
> // after executing the body
> return { /*Code here*/ }
>
>
> // Getter that explicitly returns a value using a return statement
> in the body
> // Note that "this" refers to the field and not the containing class
> // To explicitly access the containing class, use KlassName.this
> return { return this; };
>
> // Getter that returns another field instead of this one. Note
> that KlassName.this is
> // not required to access other fields in the class; It serves the
> same purpose as
> // regular usages of "this": To differentiate fields defined in
> the class itself and
> // local variables
>
> // Note that the other field MUST have the same type
>
> // as the one the accessor is bound to
> return { return KlassName.this.otherField }
>
> // Getter overloaded with custom signature
> // Note: No getter or setter can share the same signature
> return(Klass klass) {}
>
>
> // Return random literal
>
> return { return 42L; }
>
>
> // Accessors can call their own overloads too, if
>
> // required
>
> // If an accessor calls its own overload, the implicit
>
> // setting or getting is disabled.
>
> return { field(); }
>
> // Setter, leave as is to implicitly set field to value without
> any custom logic
> // Note that the type and name do not need to be set, the type is
> already inferred
> // and the name is not needed unless it has to be referred to in a
> body with custom
> // logic
> new;
>
> // Setter that executes body before assigning a value to the field
> // Like Getters, Setters execute the body first before setting the
> field, unless the order
> // is customized by setting "this" to another value explicitly
> new { /*Code*/ }
>
>
> // Setter that accepts parameters. Method signature when being
> called will be (valueToSet, ...)
> new(KlassName otherType) {}
>
>
> // Accessors can call their own overloads too, if
>
> // desired
>
> // If an accessor calls its own overload, the implicit
>
> // setting or getting is disabled.
>
> new { setField(5); }
>
> // Setter with access to the value being sent to set the field // Type is
> automatically inferred, but can be set if desired for whatever reason
> new(value) -> {} // Setter with both access to the new value and more
> parameters new(value) -> (KlassName otherType) {} // Setter that explicitly
> sets the field // Normally the field is set implicitly if nothing is
> assigned to "this" in the body new(value) -> { this = value; } // Setter
> with a return type. By default, setters are void // Both setters and
> getters can be marked as final in the same manner with the "final" //
> keyword char new { return 'c'; } } // To override the getter/setter in
> subclasses, if possible
>
> // Eg not marked as final
>
> // Type is immediately inferred, but can be explicitly specified
> before the identifier if
> // there are other fields with the same name but different type
> // This is only possible if those fields have getters or setters
> super field {
> // Syntax is the same as above
> }
>
> // Note that overriding getters or setters defined by
>
> // this new syntax can be done in the typical
> // way, if desired, and vice versa
> @Override
> public long field() { /*Logic*/ }
>
>
> @Override
> public void setField(long value) { /*Logic*/ }
>
>
> The above are all ultimately implemented as normal methods,
>
> making supporting them relatively easy and requiring
>
> little change to javac as a whole, and also means that
>
> any accessor can be overridden by subclasses with regular
>
> methods with the correct name and signature, as shown above
>
> (Unless marked final)
>
>
> Currently, there is no proposal for static fields. This can
>
> hopefully be worked on as discussion around this JEP progresses
>
>
> Alternatives
> ------------
>
> The Lombok project has annotations that can be added
>
> to fields to automatically generate getters and setters,
>
> however this would require developers to install a third
>
> party library, when native language support would be much
>
> more efficient to work with. Additionally, Lombok's field
>
> annotations can only generate one set of methods and do not
>
> allow for overloads, making it much less flexible than the
>
> currently proposed syntax. Alternatively, we could continue
>
> to stick to Java's traditional way of directly writing
>
> methods, but developers often find such an approach rather
>
> tedious, and the amount of boilerplate required with this
>
> approach can make it ultimately more error prone.
>
>
> Risks and Assumptions
> ---------------------
>
> This JEP assumes that developers have prior knowledge of
>
> accessors, and will not have any issues making their code
>
> work with the new language feature.
>
>
>
More information about the amber-dev
mailing list