diff --git a/src/java.base/share/classes/java/lang/Object.java b/src/java.base/share/classes/java/lang/Object.java index 705c524eb75..74e8e89de68 100644 --- a/src/java.base/share/classes/java/lang/Object.java +++ b/src/java.base/share/classes/java/lang/Object.java @@ -35,12 +35,24 @@ * *
*
- * When preview features are enabled, subclasses of {@code java.lang.Object} can be either - * identity classes or {@linkplain Class#isValue value classes}. - * See The Java Language Specification {@jls value-objects-8.1.1.5 Value Classes}. - * Use of value class instances for synchronization or with - * {@linkplain java.lang.ref.Reference object references} results in - * {@link IdentityException}. + * When preview features are enabled, subclasses of {@code java.lang.Object} + * are either {@linkplain Class#isValue value classes} or identity classes. + * A value object is an instance of a non-abstract value class. All + * other objects, including arrays, are identity objects. See + * The Java Language Specification {@jls value-objects-8.1.1.5 Value Classes}. + *

+ * All classes are identity classes and all objects are identity objects when + * preview features are disabled. + *

+ * It is not possible to synchronize on a value object. An attempt to {@code + * synchronize} on a value object causes {@link IdentityException} to be thrown. + *

+ * The {@link #finalize()} method of a value class will never be invoked by + * the garbage collector. + *

+ * A {@linkplain java.lang.ref.Reference Reference Object} can only refer to an + * object with identity. Creating a reference object with a value object as + * the referent throws {@code IdentityException}. *

*
* @@ -186,7 +198,7 @@ public boolean equals(Object obj) { /** * Creates and returns a copy of this object. The precise meaning * of "copy" may depend on the class of the object. The general - * intent is that, for any object {@code x}, the expression: + * intent is that, for any identity object {@code x}, the expression: *
*
      * x.clone() != x
@@ -194,17 +206,17 @@ public boolean equals(Object obj) { *
*
      * x.clone().getClass() == x.getClass()
- * will be {@code true}, but these are not absolute requirements. + * will be {@code true} for any object, but these are not absolute requirements. * While it is typically the case that: *
*
      * x.clone().equals(x)
* will be {@code true}, this is not an absolute requirement. *

- * By convention, the returned object should be obtained by calling - * {@code super.clone}. If a class and all of its superclasses (except - * {@code Object}) obey this convention, it will be the case that - * {@code x.clone().getClass() == x.getClass()}. + * By convention, the {@code clone} method of an identity class should return an + * object obtained by calling {@code super.clone}. If a class and all of its + * superclasses (except {@code Object}) obey this convention, it will be the + * case that {@code x.clone().getClass() == x.getClass()}. *

* By convention, the object returned by this method should be independent * of this object (which is being cloned). To achieve this independence, @@ -216,6 +228,18 @@ public boolean equals(Object obj) { * primitive fields or references to immutable objects, then it is usually * the case that no fields in the object returned by {@code super.clone} * need to be modified. + *

+ * For a value object {@code x}, the expectation that {@code x.clone() != x} is not + * meaningful. Value classes that contain references to identity objects may override + * {@code clone} to perform a "deep copy" of the identity objects, returning an object + * with references to the copied identity objects. + * + * @apiNote + * It should be rare for new classes to implement the {@link Cloneable} interface. + * Copy constructors and static factory methods provide a more explicit and flexible + * means of creating copies, allowing classes to define and document their copying + * semantics without the constraints imposed by {@code Cloneable} interface and + * the {@code clone} method. * * @implSpec * The method {@code clone} for class {@code Object} performs a @@ -225,12 +249,16 @@ public boolean equals(Object obj) { * are considered to implement the interface {@code Cloneable} and that * the return type of the {@code clone} method of an array type {@code T[]} * is {@code T[]} where T is any reference or primitive type. - * Otherwise, this method creates a new instance of the class of this - * object and initializes all its fields with exactly the contents of + *

+ * For an identity object, this method creates a new instance of the class of + * this object and initializes all its fields with exactly the contents of * the corresponding fields of this object, as if by assignment; the * contents of the fields are not themselves cloned. Thus, this method * performs a "shallow copy" of this object, not a "deep copy" operation. *

+ * For a value object, this method returns an object that is indistinguishable + * from the original. + *

* The class {@code Object} does not itself implement the interface * {@code Cloneable}, so calling the {@code clone} method on an object * whose class is {@code Object} will result in throwing an @@ -310,14 +338,15 @@ public String toString() { * Only one thread at a time can own an object's monitor. *

*
- * If this object is a {@linkplain java.util.Objects#hasIdentity value object}, - * it does not have a monitor, an {@code IllegalMonitorStateException} is thrown. + * The {@code notify} method requires that the current thread be the owner + * of the object's monitor. Since it is not possible to synchronize on a + * value object, an attempt to call this method on a value object will + * always fail with {@code IllegalMonitorStateException}. *
*
* * @throws IllegalMonitorStateException if the current thread is not - * the owner of this object's monitor or - * if this object is a {@linkplain java.util.Objects#hasIdentity value object}. + * the owner of this object's monitor. * @see java.lang.Object#notifyAll() * @see java.lang.Object#wait() */ @@ -343,14 +372,15 @@ public String toString() { * *
*
- * If this object is a {@linkplain java.util.Objects#hasIdentity value object}, - * it does not have a monitor, an {@code IllegalMonitorStateException} is thrown. + * The {@code notifyAll} method requires that the current thread be the owner + * of the object's monitor. Since it is not possible to synchronize on a + * value object, an attempt to call this method on a value object will + * always fail with {@code IllegalMonitorStateException}. *
*
* * @throws IllegalMonitorStateException if the current thread is not - * the owner of this object's monitor or - * if this object is a {@linkplain java.util.Objects#hasIdentity value object}. + * the owner of this object's monitor. * @see java.lang.Object#notify() * @see java.lang.Object#wait() */ @@ -367,14 +397,15 @@ public String toString() { * *
*
- * If this object is a {@linkplain java.util.Objects#hasIdentity value object}, - * it does not have a monitor, an {@code IllegalMonitorStateException} is thrown. + * The {@code wait} method requires that the current thread be the owner + * of the object's monitor. Since it is not possible to synchronize on a + * value object, an attempt to call this method on a value object will + * always fail with {@code IllegalMonitorStateException}. *
*
* * @throws IllegalMonitorStateException if the current thread is not - * the owner of the object's monitor or - * if this object is a {@linkplain java.util.Objects#hasIdentity value object}. + * the owner of the object's monitor * @throws InterruptedException if any thread interrupted the current thread before or * while the current thread was waiting. The interrupted status of the * current thread is cleared when this exception is thrown. @@ -398,16 +429,17 @@ public final void wait() throws InterruptedException { * *
*
- * If this object is a {@linkplain java.util.Objects#hasIdentity value object}, - * it does not have a monitor, an {@code IllegalMonitorStateException} is thrown. + * The {@code wait} method requires that the current thread be the owner + * of the object's monitor. Since it is not possible to synchronize on a + * value object, an attempt to call this method on a value object will + * always fail with {@code IllegalMonitorStateException}. *
*
* * @param timeoutMillis the maximum time to wait, in milliseconds * @throws IllegalArgumentException if {@code timeoutMillis} is negative * @throws IllegalMonitorStateException if the current thread is not - * the owner of the object's monitor or - * if this object is a {@linkplain java.util.Objects#hasIdentity value object}. + * the owner of the object's monitor * @throws InterruptedException if any thread interrupted the current thread before or * while the current thread was waiting. The interrupted status of the * current thread is cleared when this exception is thrown. @@ -502,8 +534,10 @@ public final void wait(long timeoutMillis) throws InterruptedException { * *
*
- * If this object is a {@linkplain java.util.Objects#hasIdentity value object}, - * it does not have a monitor, an {@code IllegalMonitorStateException} is thrown. + * The {@code wait} method requires that the current thread be the owner + * of the object's monitor. Since it is not possible to synchronize on a + * value object, an attempt to call this method on a value object will + * always fail with {@code IllegalMonitorStateException}. *
*
* @@ -529,8 +563,7 @@ public final void wait(long timeoutMillis) throws InterruptedException { * @throws IllegalArgumentException if {@code timeoutMillis} is negative, * or if the value of {@code nanos} is out of range * @throws IllegalMonitorStateException if the current thread is not - * the owner of the object's monitor or - * if this object is a {@linkplain java.util.Objects#hasIdentity value object}. + * the owner of the object's monitor * @throws InterruptedException if any thread interrupted the current thread before or * while the current thread was waiting. The interrupted status of the * current thread is cleared when this exception is thrown. @@ -557,14 +590,24 @@ public final void wait(long timeoutMillis, int nanos) throws InterruptedExceptio } /** - * Called by the garbage collector on an object when garbage collection + * Called by the garbage collector on an identity object when garbage collection * determines that there are no more references to the object. - * A subclass overrides the {@code finalize} method to dispose of + * An identity class may override the {@code finalize} method to dispose of * system resources or to perform other cleanup. + *
+ *
+ * The {@code finalize} method of a value class is never directly + * invoked by the garbage collector. This includes the case where an + * abstract value class declares a {@code finalize} method and the + * class is extended by an identity class; the garbage collector never + * directly invokes the {@code finalize} method declared by the + * abstract value class. + *
+ *
*

* When running in a Java virtual machine in which finalization has been - * disabled or removed, the garbage collector will never call - * {@code finalize()}. In a Java virtual machine in which finalization is + * disabled or removed, the garbage collector will never call {@code finalize()} + * for any object. In a Java virtual machine in which finalization is * enabled, the garbage collector might call {@code finalize} only after an * indefinite delay. *

@@ -607,13 +650,6 @@ public final void wait(long timeoutMillis, int nanos) throws InterruptedExceptio * the finalization of this object to be halted, but is otherwise * ignored. * - *

- *
- * If this object is a {@linkplain java.util.Objects#hasIdentity value object}, - * this method will never be invoked by the garbage collector. - *
- *
- * * @apiNote * Classes that embed non-heap resources have many options * for cleanup of those resources. The class must ensure that the diff --git a/test/jdk/java/lang/Object/ValueObjects.java b/test/jdk/java/lang/Object/ValueObjects.java new file mode 100644 index 00000000000..f84cee73a99 --- /dev/null +++ b/test/jdk/java/lang/Object/ValueObjects.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2026, 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. + * + * 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. + */ + +/* + * @test + * @summary Basic test of Object methods on value objects + * @enablePreview + * @library /test/lib + * @run junit ${test.main.class} + */ + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.provider.MethodSource; +import static org.junit.jupiter.api.Assertions.*; + +import jdk.test.lib.Utils; + +class ValueObjects { + + /** + * Test the Object.clone method on a value object. It should return an object that + * is indistinguishable from the original. + */ + @Test + void testClone() throws Exception { + value class V implements Cloneable { + @Override + protected V clone() throws CloneNotSupportedException { + return (V) super.clone(); + } + } + + var obj = new V(); + assertSame(obj, obj.clone()); + } + + /** + * Test the Object.clone method on a value object when the value class does + * not implement Cloneable. + */ + @Test + void testCloneNotSupportedException() throws Exception { + value class V { + @Override + protected V clone() throws CloneNotSupportedException { + return (V) super.clone(); + } + } + + var obj = new V(); + assertThrows(CloneNotSupportedException.class, obj::clone); + } + + /** + * Test that the finalize method on a value class is not invoked by the GC. + */ + @Test + void testValueClassFinalize() throws Exception { + value class V { + CountDownLatch latch; + V(CountDownLatch latch) { + this.latch = latch; + } + @Override + protected void finalize() { + latch.countDown(); + } + } + + var latch = new TimeoutAdjustingLatch(); + var obj = new V(latch); + obj = null; + for (int i = 0; i < 3; i++) { + System.gc(); + // latch should not count down + assertFalse(latch.await(100, TimeUnit.MILLISECONDS)); + } + } + + /** + * Test that the finalize method on an abstract value value is not invoked by the GC. + */ + @Test + void testAbstractValueClassFinalize() throws Exception { + abstract value class AV { + CountDownLatch latch; + AV(CountDownLatch latch) { + this.latch = latch; + } + @Override + protected void finalize() { + latch.countDown(); + } + } + /*identity*/ class C extends AV { + C(CountDownLatch latch) { + super(latch); + } + } + + var latch = new TimeoutAdjustingLatch(); + var obj = new C(latch); + obj = null; + for (int i = 0; i < 3; i++) { + System.gc(); + // latch should not count down + assertFalse(latch.await(100, TimeUnit.MILLISECONDS)); + } + } + + /** + * Test that the wait/notify methods on a value object throw IMSE. + */ + @Test + void testWaitNotify() { + value class V {} + Object obj = new V(); + assertThrows(IllegalMonitorStateException.class, () -> obj.wait()); + assertThrows(IllegalMonitorStateException.class, () -> obj.wait(1000)); + assertThrows(IllegalMonitorStateException.class, () -> obj.wait(1000, 10)); + assertThrows(IllegalMonitorStateException.class, () -> obj.notify()); + assertThrows(IllegalMonitorStateException.class, () -> obj.notifyAll()); + } + + /** + * Test default toString method. + */ + @Test + void testToString() { + value class V { } + var obj = new V(); + String expected = V.class.getName() + "@" + Integer.toHexString(obj.hashCode()); + assertEquals(expected, obj.toString()); + } + + /** + * A CountDownLatch that is created with a count of 1 and with a timed-await method + * that adjusts the timeout based on the jtreg timeout factory configuration. + */ + private static class TimeoutAdjustingLatch extends CountDownLatch { + TimeoutAdjustingLatch() { + super(1); + } + @Override + public boolean await(long timeout, TimeUnit unit) throws InterruptedException { + return super.await(Utils.adjustTimeout(timeout), unit); + } + } +}