Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8233436

Preview APIs support for records

    Details

    • Type: CSR
    • Status: Closed
    • Priority: P3
    • Resolution: Approved
    • Fix Version/s: 14
    • Component/s: core-libs
    • Labels:
      None
    • Subcomponent:
    • Compatibility Kind:
      source, behavioral
    • Compatibility Risk:
      low
    • Compatibility Risk Description:
      Hide
      The addition of the feature does not by itself bring a compatibility risk.

      Records are a preview feature in Java SE 14. It is possible that incompatible changes will be made to records in a later Java SE release, before they become final and permanent, but chances are low as records are ultimately Java classes so they shouldn't be introducing any new disruptive feature that could pose a compatibility risk. It is also possible that records will be removed in a later Java SE release, without ever having become final and permanent.
      Show
      The addition of the feature does not by itself bring a compatibility risk. Records are a preview feature in Java SE 14. It is possible that incompatible changes will be made to records in a later Java SE release, before they become final and permanent, but chances are low as records are ultimately Java classes so they shouldn't be introducing any new disruptive feature that could pose a compatibility risk. It is also possible that records will be removed in a later Java SE release, without ever having become final and permanent.
    • Interface Kind:
      Java API, File or wire format
    • Scope:
      SE

      Description

      Summary

      Add support for record classes, see (JEP 359), in core-libs.

      Problem

      Records classes, see (JEP 359), will be previewed in Java SE 14 and some support will be needed for them in core-libs. Records will need reflection support, for records and its record components. Also records components will be a new annotation target, this target needs to be added to the corresponding API. Also a common supertype that defines the API for records will be needed.

      In addition given that the implementation of methods: toString, equals and hashCode follows a very similar pattern for all records, it is sound to provide a common implementation for those methods.

      Solution

      Records allow defining shallowly immutable, transparent carriers for a fixed set of values, the record components. Record components are first class entities in the design of records as they determine the API of a record. For this reason apart from enhancing the API of java.lang.Class to support records, a new class, java.lang.reflect.RecordComponent, has been defined to allow reflecting record components. In addition record components are a new annotation target, this has to be represented in class java.lang.annotation.ElementType

      A common supertype for all records has been defined in class java.lang.Record. This class will defined the common API for records. In addition, a new class has been defined,java.lang.runtime.ObjectMethods, which will provide a common bootstrap method for the implementation of methods: toString, equals and hashCode. These feature will constitute a preview feature (JEP 12) in Java SE 14.

      Specification

      In addition to the API changes below, the serialization specification is also updated; see attached file for those changes.

      diff -r 5573a7098439 src/java.base/share/classes/java/lang/Class.java
      --- a/src/java.base/share/classes/java/lang/Class.java  Sat Nov 02 10:02:18 2019 +0000
      +++ b/src/java.base/share/classes/java/lang/Class.java  Mon Nov 25 16:16:40 2019 -0500
      @@ -2267,6 +2268,68 @@
               return copyFields(privateGetDeclaredFields(false));
           }
      
      +    /**
      +     * {@preview Associated with records, a preview feature of the Java language.
      +     *
      +     *           This method is associated with <i>records</i>, a preview
      +     *           feature of the Java language. Preview features
      +     *           may be removed in a future release, or upgraded to permanent
      +     *           features of the Java language.}
      +     *
      +     * Returns an array containing {@code RecordComponent} objects reflecting all the
      +     * declared record components of the record represented by this {@code Class} object.
      +     * The components are returned in the same order that they are declared in the
      +     * record header.
      +     *
      +     * @return  The array of {@code RecordComponent} objects representing all the
      +     *          record components of this record. The array is empty if this class
      +     *          is not a record, or if this class is a record with no components.
      +     * @throws  SecurityException
      +     *          If a security manager, <i>s</i>, is present and any of the
      +     *          following conditions is met:
      +     *
      +     *          <ul>
      +     *
      +     *          <li> the caller's class loader is not the same as the
      +     *          class loader of this class and invocation of
      +     *          {@link SecurityManager#checkPermission
      +     *          s.checkPermission} method with
      +     *          {@code RuntimePermission("accessDeclaredMembers")}
      +     *          denies access to the declared methods within this class
      +     *
      +     *          <li> the caller's class loader is not the same as or an
      +     *          ancestor of the class loader for the current class and
      +     *          invocation of {@link SecurityManager#checkPackageAccess
      +     *          s.checkPackageAccess()} denies access to the package
      +     *          of this class
      +     *
      +     *          </ul>
      +     *
      +     * @jls 8.10 Record Types
      +     * @since 14
      +     */
      +    @jdk.internal.PreviewFeature(feature=jdk.internal.PreviewFeature.Feature.RECORDS,
      +                                 essentialAPI=false)
      +    @SuppressWarnings("preview")
      +    @CallerSensitive
      +    public RecordComponent[] getRecordComponents() {
      +    }
      
           /**
            * Returns an array containing {@code Method} objects reflecting all the
      @@ -3531,6 +3596,29 @@
               this.getSuperclass() == java.lang.Enum.class;
           }
      
      +    /**
      +     * {@preview Associated with records, a preview feature of the Java language.
      +     *
      +     *           This method is associated with <i>records</i>, a preview
      +     *           feature of the Java language. Preview features
      +     *           may be removed in a future release, or upgraded to permanent
      +     *           features of the Java language.}
      +     *
      +     * Returns {@code true} if and only if this class is a record class.
      +     * It returns {@code false} otherwise. Note that class {@link Record} is not a
      +     * record type and thus invoking this method on class {@link java.lang.Record}
      +     * returns {@code false}.
      +     *
      +     * @return true if and only if this class is a record class
      +     * @jls 8.10 Record Types
      +     * @since 14
      +     */
      +    @jdk.internal.PreviewFeature(feature=jdk.internal.PreviewFeature.Feature.RECORDS,
      +                                 essentialAPI=false)
      +    public boolean isRecord() {
      +    }
      +
           // Fetches the factory for reflective objects
           private static ReflectionFactory getReflectionFactory() {
               if (reflectionFactory == null) {
      diff -r 5573a7098439 src/java.base/share/classes/java/lang/Record.java
      --- /dev/null   Thu Jan 01 00:00:00 1970 +0000
      +++ b/src/java.base/share/classes/java/lang/Record.java Mon Nov 25 16:16:40 2019 -0500
      @@ -0,0 +1,154 @@
      +package java.lang;
      +
      +/**
      + * {@preview Associated with records, a preview feature of the Java language.
      + *
      + *           This class is associated with <i>records</i>, a preview
      + *           feature of the Java language. Programs can only use this
      + *           class when preview features are enabled. Preview features
      + *           may be removed in a future release, or upgraded to permanent
      + *           features of the Java language.}
      + *
      + * This is the common base class of all Java language record classes.
      + *
      + * <p>More information about records, including descriptions of the
      + * implicitly declared methods synthesized by the compiler, can be
      + * found in section 8.10 of
      + * <cite>The Java&trade; Language Specification</cite>.
      + *
      + * <p>A <em>record class</em> is a shallowly immutable, transparent carrier for
      + * a fixed set of values, called the <em>record components</em>.  The Java&trade;
      + * 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 final 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,
      + * 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.
      + *
      + * <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.)
      + *
      + * <p>For all record classes, the following invariant must hold: if a record R's
      + * 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)}.
      + *
      + * @apiNote
      + * A record class that {@code implements} {@link java.io.Serializable} is said
      + * to be a <i>serializable record</i>. Serializable records are serialized and
      + * deserialized differently than ordinary serializable objects. During
      + * deserialization the record's canonical constructor is invoked to construct
      + * the record object. Certain serialization-related methods, such as readObject
      + * and writeObject, are ignored for serializable records. More information about
      + * serializable records can be found in
      + * <a href="{@docRoot}/java.base/java/io/ObjectInputStream.html#record-serialization">record serialization</a>.
      + *
      + * @jls 8.10 Record Types
      + * @since 14
      + */
      +@jdk.internal.PreviewFeature(feature=jdk.internal.PreviewFeature.Feature.RECORDS,
      +                             essentialAPI=true)
      +public abstract class Record {
      +    /**
      +     * Indicates whether some other object is "equal to" this one.  In addition
      +     * 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, 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)}.
      +     *
      +     * @implSpec
      +     * The implicitly provided implementation returns {@code true} if and
      +     * 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 java.util.Objects#equals(Object,Object)}
      +     * for components whose types are reference types, and according to the semantics
      +     * of the {@code equals} method on the corresponding primitive wrapper type.
      +     *
      +     * @see java.util.Objects#equals(Object,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.
      +     */
      +    @Override
      +    public abstract boolean equals(Object obj);
      +
      +    /**
      +     * Obeys the general contract of {@link Object#hashCode Object.hashCode}.
      +     *
      +     * @implSpec
      +     * 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.
      +     *
      +     * @see     Object#hashCode()
      +     *
      +     * @return  a hash code value for this object.
      +     */
      +    @Override
      +    public abstract int hashCode();
      +
      +    /**
      +     * Obeys the general contract of {@link Object#toString Object.toString}.
      +     *
      +     * @implSpec
      +     * 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();
      +}
      diff -r 5573a7098439 src/java.base/share/classes/java/lang/annotation/ElementType.java
      --- a/src/java.base/share/classes/java/lang/annotation/ElementType.java Sat Nov 02 10:02:18 2019 +0000
      +++ b/src/java.base/share/classes/java/lang/annotation/ElementType.java Mon Nov 25 16:16:40 2019 -0500
      @@ -114,5 +114,25 @@
            *
            * @since 9
            */
      -    MODULE
      +    MODULE,
      +
      +    /**
      +     * {@preview Associated with records, a preview feature of the Java language.
      +     *
      +     *           This constant is associated with <i>records</i>, a preview
      +     *           feature of the Java language. Programs can only use this
      +     *           constant when preview features are enabled. Preview features
      +     *           may be removed in a future release, or upgraded to permanent
      +     *           features of the Java language.}
      +     *
      +     * Record component
      +     *
      +     * @jls 8.10.3 Record Members
      +     * @jls 9.7.4 Where Annotations May Appear
      +     *
      +     * @since 14
      +     */
      +    @jdk.internal.PreviewFeature(feature=jdk.internal.PreviewFeature.Feature.RECORDS,
      +                                 essentialAPI=true)
      +    RECORD_COMPONENT;
       }
      diff -r 5573a7098439 src/java.base/share/classes/java/lang/reflect/RecordComponent.java
      --- /dev/null   Thu Jan 01 00:00:00 1970 +0000
      +++ b/src/java.base/share/classes/java/lang/reflect/RecordComponent.java    Mon Nov 25 16:16:40 2019 -0500
      @@ -0,0 +1,254 @@
      +package java.lang.reflect;
      +
      +import jdk.internal.access.SharedSecrets;
      +import sun.reflect.annotation.AnnotationParser;
      +import sun.reflect.annotation.TypeAnnotation;
      +import sun.reflect.annotation.TypeAnnotationParser;
      +import sun.reflect.generics.factory.CoreReflectionFactory;
      +import sun.reflect.generics.factory.GenericsFactory;
      +import sun.reflect.generics.repository.FieldRepository;
      +import sun.reflect.generics.scope.ClassScope;
      +import java.lang.annotation.Annotation;
      +import java.util.Map;
      +import java.util.Objects;
      +
      +/**
      + * {@preview Associated with records, a preview feature of the Java language.
      + *
      + *           This class is associated with <i>records</i>, a preview
      + *           feature of the Java language. Preview features
      + *           may be removed in a future release, or upgraded to permanent
      + *           features of the Java language.}
      + *
      + * A {@code RecordComponent} provides information about, and dynamic access to, a
      + * component of a record class.
      + *
      + * @see Class#getRecordComponents()
      + * @see java.lang.Record
      + * @jls 8.10 Record Types
      + * @since 14
      + */
      +@jdk.internal.PreviewFeature(feature=jdk.internal.PreviewFeature.Feature.RECORDS,
      +                             essentialAPI=false)
      +public final class RecordComponent implements AnnotatedElement {
      +    /**
      +     * Returns the name of this record component.
      +     *
      +     * @return the name of this record component
      +     */
      +    public String getName() {
      +    }
      +
      +    /**
      +     * Returns a {@code Class} that identifies the declared type for this
      +     * record component.
      +     *
      +     * @return a {@code Class} identifying the declared type of the component
      +     * represented by this record component
      +     */
      +    public Class<?> getType() {
      +    }
      +
      +    /**
      +     * Returns a {@code String} that describes the  generic type signature for
      +     * this record component.
      +     *
      +     * @return a {@code String} that describes the generic type signature for
      +     * this record component
      +     *
      +     * @jvms 4.7.9.1 Signatures
      +     */
      +    public String getGenericSignature() {
      +    }
      +
      +    /**
      +     * Returns a {@code Type} object that represents the declared type for
      +     * this record component.
      +     *
      +     * <p>If the declared type of the record component is a parameterized type,
      +     * the {@code Type} object returned reflects the actual type arguments used
      +     * in the source code.
      +     *
      +     * <p>If the type of the underlying record component is a type variable or a
      +     * parameterized type, it is created. Otherwise, it is resolved.
      +     *
      +     * @return a {@code Type} object that represents the declared type for
      +     *         this record component
      +     * @throws GenericSignatureFormatError if the generic record component
      +     *         signature does not conform to the format specified in
      +     *         <cite>The Java&trade; Virtual Machine Specification</cite>
      +     * @throws TypeNotPresentException if the generic type
      +     *         signature of the underlying record component refers to a non-existent
      +     *         type declaration
      +     * @throws MalformedParameterizedTypeException if the generic
      +     *         signature of the underlying record component refers to a parameterized
      +     *         type that cannot be instantiated for any reason
      +     */
      +    public Type getGenericType() {
      +    }
      +
      +    /**
      +     * Returns an {@code AnnotatedType} object that represents the use of a type to specify
      +     * the declared type of this record component.
      +     *
      +     * @return an object representing the declared type of this record component
      +     */
      +    public AnnotatedType getAnnotatedType() {
      +    }
      +
      +    /**
      +     * Returns a {@code Method} that represents the accessor for this record
      +     * component.
      +     *
      +     * @return a {@code Method} that represents the accessor for this record
      +     * component
      +     */
      +    public Method getAccessor() {
      +    }
      +
      +    /**
      +     * @throws NullPointerException {@inheritDoc}
      +     */
      +    @Override
      +    public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
      +    }
      +
      +    /**
      +     * {@inheritDoc}
      +     */
      +    @Override
      +    public Annotation[] getAnnotations() 
      {
      +    }
      +
      + /**
      + * {@inheritDoc} + */
      + @Override
      + public Annotation[] getDeclaredAnnotations() {} + + /** + * Returns a string describing this record component. The format is + * the record component type, followed by a space, followed by the name + * of the record component. + * For example: + * <pre> + * String name + * int age + * </pre> + * + * @return a string describing this record component + */ + public String toString() { + } + + /** + * Return the record class which declares this record component. + * + * @return The record class declaring this record component. + */ + public Class<?> getDeclaringRecord() { + } +} diff -r 5573a7098439 src/java.base/share/classes/java/lang/runtime/ObjectMethods.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java.base/share/classes/java/lang/runtime/ObjectMethods.java Mon Nov 25 16:16:40 2019 -0500 @@ -0,0 +1,375 @@ +package java.lang.runtime; + +import java.lang.invoke.ConstantCallSite; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.invoke.TypeDescriptor; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Objects; + +/** + * {@preview Associated with records, a preview feature of the Java language. + * + * This class is associated with <i>records</i>, a preview + * feature of the Java language. Preview features + * may be removed in a future release, or upgraded to permanent + * features of the Java language.} + * + * Bootstrap methods for state-driven implementations of core methods, + * including {@link Object#equals(Object)}, {@link Object#hashCode()}, and + * {@link Object#toString()}. These methods may be used, for example, by + * Java&trade; compiler implementations to implement the bodies of {@link Object} + * methods for record classes. + * + * @since 14 + */ +@jdk.internal.PreviewFeature(feature=jdk.internal.PreviewFeature.Feature.RECORDS, + essentialAPI=false) +public class ObjectMethods { + /** + * Bootstrap method to generate the {@link Object#equals(Object)}, + * {@link Object#hashCode()}, and {@link Object#toString()} methods, based + * on a description of the component names and accessor methods, for either + * {@code invokedynamic} call sites or dynamic constant pool entries. + * + * For more detail on the semantics of the generated methods see the specification + * of {@link java.lang.Record#equals(Object)}, {@link java.lang.Record#hashCode()} and + * {@link java.lang.Record#toString()}. + * + * + * @param lookup Every bootstrap method is expected to have a {@code lookup} + * which usually represents a lookup context with the + * accessibility privileges of the caller. This is because + * {@code invokedynamic} call sites always provide a {@code lookup} + * to the corresponding bootstrap method, but this method just + * ignores the {@code lookup} parameter + * @param methodName the name of the method to generate, which must be one of + * {@code "equals"}, {@code "hashCode"}, or {@code "toString"} + * @param type a {@link MethodType} corresponding the descriptor type + * for the method, which must correspond to the descriptor + * for the corresponding {@link Object} method, if linking + * an {@code invokedynamic} call site, or the + * constant {@code MethodHandle.class}, if linking a + * dynamic constant + * @param recordClass the record class hosting the record components + * @param names the list of component names, joined into a string + * separated by ";", or the empty string if there are no + * components. Maybe be null, if the {@code methodName} + * is {@code "equals"} or {@code "hashCode"}. + * @param getters method handles for the accessor methods for the components + * @return a call site if invoked by indy, or a method handle + * if invoked by a condy + * @throws IllegalArgumentException if the bootstrap arguments are invalid + * or inconsistent + * @throws Throwable if any exception is thrown during call site construction + */ + public static Object bootstrap(MethodHandles.Lookup lookup, String methodName, TypeDescriptor type, + Class<?> recordClass, + String names, + MethodHandle... getters) throws Throwable { + } +} diff -r 5573a7098439 src/java.base/share/classes/java/lang/runtime/package-info.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java.base/share/classes/java/lang/runtime/package-info.java Mon Nov 25 16:16:40 2019 -0500 @@ -0,0 +1,33 @@ +/** + * The {@code java.lang.runtime} package provides low-level runtime support + * for the Java language. + * + * @since 14 + */ + +package java.lang.runtime; diff -r 5573a7098439 src/java.base/share/classes/java/io/ObjectInputStream.java --- a/src/java.base/share/classes/java/io/ObjectInputStream.java Sat Nov 02 10:02:18 2019 +0000 +++ b/src/java.base/share/classes/java/io/ObjectInputStream.java Mon Nov 25 16:16:41 2019 -0500 @@ -218,6 +220,39 @@ * Similarly, any serialPersistentFields or serialVersionUID field declarations * are also ignored--all enum types have a fixed serialVersionUID of 0L. * + * @implSpec + * <a id="record-serialization"></a> + * Records are serialized differently than ordinary serializable or externalizable + * objects. The serialized form of a record object is a sequence of values derived + * from the record components. The stream format of a record object is the same as + * that of an ordinary object in the stream. During deserialization, if the local + * class equivalent of the specified stream class descriptor is a record class, + * then first the stream fields are read and reconstructed to serve as the record's + * component values; and second, a record object is created by invoking the + * record's <i>canonical</i> constructor with the component values as arguments (or the + * default value for component's type if a component value is absent from the + * stream). + * Like other serializable or externalizable objects, record objects can function + * as the target of back references appearing subsequently in the serialization + * stream. However, a cycle in the graph where the record object is referred to, + * either directly or transitively, by one of its components, is not preserved. + * The record components are deserialized prior to the invocation of the record + * constructor, hence this limitation (see + * <a href="{@docRoot}/../specs/serialization/serial-arch.html#cyclic-references"> + * [Section 1.14, "Circular References"</a> for additional information). + * The process by which record objects are serialized or externalized cannot be + * customized; any class-specific writeObject, readObject, readObjectNoData, + * writeExternal, and readExternal methods defined by record classes are + * ignored during serialization and deserialization. However, a substitute object + * to be serialized or a designate replacement may be specified, by the + * writeReplace and readResolve methods, respectively. Any + * serialPersistentFields field declaration is ignored. Documenting serializable + * fields and data for record classes is unnecessary, since there is no variation + * in the serial form, other than whether a substitute or replacement object is + * used. The serialVersionUID of a record class is 0L unless explicitly + * declared. The requirement for matching serialVersionUID values is waived for + * record classes. + * * @author Mike Warres * @author Roger Riggs * @see java.io.DataInput diff -r 5573a7098439 src/java.base/share/classes/java/io/ObjectOutputStream.java --- a/src/java.base/share/classes/java/io/ObjectOutputStream.java Sat Nov 02 10:02:18 2019 +0000 +++ b/src/java.base/share/classes/java/io/ObjectOutputStream.java Mon Nov 25 16:16:41 2019 -0500 @@ -150,6 +150,10 @@ * defaultWriteObject and writeFields initially terminate any existing * block-data record. * + * @implSpec + * Records are serialized differently than ordinary serializable or externalizable + * objects, see <a href="ObjectInputStream.html#record-serialization">record serialization</a>. + * * @author Mike Warres * @author Roger Riggs * @see java.io.DataOutput

      Additional links

        Attachments

          Issue Links

            Activity

              People

              • Assignee:
                vromero Vicente Arturo Romero Zaldivar
                Reporter:
                vromero Vicente Arturo Romero Zaldivar
                Reviewed By:
                Chris Hegarty, Joe Darcy
              • Votes:
                0 Vote for this issue
                Watchers:
                8 Start watching this issue

                Dates

                • Created:
                  Updated:
                  Resolved: