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™ 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™
+ * 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™ 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™ 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
- javadoc: http://cr.openjdk.java.net/~vromero/records.review/CSRs/core-libs/javadoc.05/java.base/module-summary.html
- specdiff: http://cr.openjdk.java.net/~vromero/records.review/CSRs/core-libs/specdiff.05/overview-summary.html
- serialization spec: https://cr.openjdk.java.net/~chegar/records/spec/records-serialization.07.html
- csr of
-
JDK-8225053 Preview APIs support for records
-
- Resolved
-
- relates to
-
JDK-8234782 Discuss evolution of records in serialization spec
-
- Closed
-
-
JDK-8234781 Update description of InvalidClassException to include more conditions
-
- Closed
-
-
JDK-8234783 Improve wording of spec of Record.equals
-
- Closed
-
-
JDK-8234381 API docs should mention special handling of enums in serialization
-
- Resolved
-
-
JDK-8233595 JVM reflection support for records
-
- Closed
-