Java Enhancement Proposal: Field Accessors
Brian Goetz
brian.goetz at oracle.com
Thu Mar 24 13:46:34 UTC 2022
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