Draft specification for java.lang.Record

Kevin Bourrillion kevinb at google.com
Thu Aug 15 19:54:19 UTC 2019

On Thu, Aug 15, 2019 at 10:07 AM Brian Goetz <brian.goetz at oracle.com> wrote:

> Draft spec, please comment.
> /* * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation.  Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */package java.lang;
> /** * This is the common base class of all Java language record classes.
> Well, and *only* record classes -- it can't be extended manually. It
seems useful to understand both necessity and sufficiency, though I notice
Enum also doesn't say this.

>  * * <p>More information about records, including descriptions of the
> To the extent that the bare term "record" (without "class") has meaning,
that is still something that is always declared by a "record class" anyway,
so might it be clearer to always say "record classes"? Just a thought.

>  * implicitly declared methods synthesized by the compiler, can be * found in section 8.10 of * <cite>The Java™ Language Specification</cite>. * * <p>A <em>record class</em> is a shallowly immutable, transparent carrier for
> Typical readers may wonder what "transparent" means here. (They may also
not understand shallow immutability, but that they can probably look up.)

>  * a fixed set of values, called the <em>record components</em>.  The Java™ * language provides concise syntax for declaring record classes, whereby the * record components are declared in the record header.  The list of record * components declared in the record header form the <em>record descriptor</em>. * * <p>A record class has the following mandated members: a public <em>canonical * constructor</em>, whose descriptor is the same as the record descriptor; * a private static field corresponding to each component, whose name and * type are the same as that of the component; a public accessor method * corresponding to each component, whose name and return type are the same as * that of the component.  If not explicitly declared in the body of the record,
> Even if "record" is used in some contexts this still seems to want to be
"record class"?

>  * implicit implementations for these members are provided. * * <p>The implicit declaration of the canonical constructor initializes the * component fields from the corresponding constructor arguments.  The implicit * declaration of the accessor methods returns the value of the corresponding * component field.  The implicit declaration of the {@link Object#equals(Object)}, * {@link Object#hashCode()}, and {@link Object#toString()} methods are derived * from all of the component fields.
> That may be confusing, but if you add "... as specified below" or
something like that it's all good.

 * <p>The primary reasons to provide an explicit declaration for the *
canonical constructor or accessor methods are to validate constructor
* arguments, perform defensive copies on mutable components, or
normalize groups * of components (such as reducing a rational number
to lowest terms.)
> Is this intentionally excluding normalizing a single component?

>   If any * of these are provided explicitly. * * <p>For all record classes, the following invariant must hold: if a record R's
> "a record class R's"?

More importantly, was the "if any of these are provided explicitly" meant
to be part of this new paragraph? I'd like the part about the invariant to
explain both that it does always hold by default AND that if you choose to
customize anything that's when you become responsible for upholding it.

 * components are {@code c1, c2, ... cn}, then if a record instance is
copied * as follows: * <pre> *     R copy = new R(r.c1(), r.c2(), ...,
r.cn()); * </pre> * then it must be the case that {@code
r.equals(copy)}. * * @jls 8.10 * @since 14 */public abstract class
Record {
>     /**     * Indicates whether some other object is "equal to" this one.  In addition
> We could improve on the superclass wording, like "Indicates whether {@code
obj} is a record with equal corresponding values to this one."

     * to the general contract of {@link Object#equals(Object)},     *
record classes must further participate in the invariant that when
* a record instance is "copied" by passing the result of the record
component     * accessor methods to the canonical constructor, the
resulting copy is     * equal to the original instance.
> Finding this sentence awfully unwieldy. Could some of the ideas from this
formulation help?

"As illustrated in the class documentation, this method must return {@code
true} if {@code obj} is a simple componentwise copy of this record (it may,
of course, return {@code true} in other cases as well)."

Sure, "simple componentwise copy" is not self-defining, but that's why
there's an illustration up top.

     * @implNote     * The implicitly provided implementation returns
{@code true} if and
> Perhaps start by saying "Although this method is abstract, record classes
do not need to implement it explicitly" and *then* explain what the
implicit impl does?

     * only if the argument is an instance of the same record type as
this object,     * and each component of this record is equal to the
corresponding component     * of the argument, according to {@link
Object#equals(Object)} for components     * whose types are reference
types, and {@code ==} for components whose     * types are primitive
> The floating-point issue jumped to my mind as well. But we really don't
want to have to call out such a fine-grained special case, so something
like "(boxing primitive values if necessary)" could do the trick?

The main danger of this behavior is arrays; is it worth giving that a
callout here?

     * @see Object#equals(Object)     *      * @param   obj   the
reference object with which to compare.     * @return  {@code true} if
this object is the same as the obj     *          argument; {@code
false} otherwise.
> It is strange to have clear method docs and then a @return clause that
makes things muddy again. Maybe it should reference "as described more
fully in the method documentation"? (This is why @return clauses shouldn't
be mandatory; they very often subtract value in just this way, but nothing
we can do about that here...)

Is it worth a reminder here that if you provide any alternate behavior for
this method you must do so for hashCode as well? imho, you should even be
encouraged to do so for toString() too (or you will get confusing "expected
<foo> but was <foo> test failures, not that we want to say all that here).

     */    @Override    public abstract boolean equals(Object obj);
>     /**     * {@inheritDoc}     *      * @implNote     * The implicitly provided implementation returns a hash code value derived     * by combining the hash code value for all the components, according to      * {@link Object#hashCode()} for components whose types are reference types,     * or the primitive wrapper hash code for components whose types are primitive      * types.
> It's unclear to me why it's even worth saying any of this. It seems like
it's trying to communicate the fact that it's neither going to randomly
ignore some components, nor try to compute a hash code for a component via
some weird other means. But then, since it's not specifying *how* exactly
it will *combine* the values, it's not really guaranteeing it doesn't do
those weird things anyway.

I tend to think this method doesn't need to say anything at all. Perhaps
just to say that there is almost no reason to provide your own
implementation unless you are doing so for equals() (in which case you

     *     * @see     Object#hashCode()     *      * @return  a hash
code value for this object.     */    @Override    public abstract int
>     /**     * {@inheritDoc}     *     * @implNote     * The implicitly provided implementation returns a string that is derived     * from the name of the record class and the names and string representations     * of all the components, according to {@link Object#toString()} for components     * whose types are reference types, and the primitive wrapper {@code toString}     * method for components whose types are primitive types.     *     * @see     Object#toString() ()     *     * @return  a string representation of the object.     */    @Override    public abstract String toString();
> }
> Perhaps mention that the output does not incorporate the component names.

(off-topic: it would be nice if there was a final toSelfDescribingString()
method I could delegate my toString() to if I wanted that. It's what most
classes by far would want, I think.)

Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20190815/52c12cec/attachment-0001.html>

More information about the amber-spec-experts mailing list