diff --git a/src/java.base/share/classes/java/lang/Class.java b/src/java.base/share/classes/java/lang/Class.java
index 02ddc71f04b..ae60cbe4047 100644
--- a/src/java.base/share/classes/java/lang/Class.java
+++ b/src/java.base/share/classes/java/lang/Class.java
@@ -607,36 +607,34 @@ public static Class> forName(Module module, String name) {
}
/**
- * {@return {@code true} if this {@code Class} object represents an identity
- * class or interface; otherwise {@code false}}
+ * {@return {@code true} if this {@code Class} object represents an identity class;
+ * otherwise {@code false}}
*
- * If this {@code Class} object represents an array type, then this method
- * returns {@code true}.
- * If this {@code Class} object represents a primitive type, or {@code void},
+ * If this {@code Class} object represents an array type then this method returns {@code true}.
+ * If this {@code Class} object represents an interface, a primitive type, or {@code void}
* then this method returns {@code false}.
*
+ * @see AccessFlag#IDENTITY
* @since Valhalla
*/
@PreviewFeature(feature = PreviewFeature.Feature.VALUE_OBJECTS, reflective=true)
public native boolean isIdentity();
/**
- * {@return {@code true} if this {@code Class} object represents a value
- * class; otherwise {@code false}}
+ * {@return {@code true} if this {@code Class} object represents a value class;
+ * otherwise {@code false}}
+ * All classes that are not {@linkplain #isIdentity identity classes} are value classes.
*
- * If this {@code Class} object represents an array type, an interface,
- * a primitive type, or {@code void}, then this method returns {@code false}.
+ * If this {@code Class} object represents an array type then this method returns {@code false}.
+ * If this {@code Class} object represents an interface, a primitive type, or {@code void},
+ * then this method returns {@code true}.
*
+ * @see AccessFlag#IDENTITY
* @since Valhalla
*/
@PreviewFeature(feature = PreviewFeature.Feature.VALUE_OBJECTS, reflective=true)
public boolean isValue() {
- if (!PreviewFeatures.isEnabled()) {
- return false;
- }
- if (isPrimitive() || isArray() || isInterface())
- return false;
- return ((getModifiers() & Modifier.IDENTITY) == 0);
+ return PreviewFeatures.isEnabled() ? !isIdentity() : false;
}
/**
diff --git a/src/java.base/share/classes/java/lang/IdentityException.java b/src/java.base/share/classes/java/lang/IdentityException.java
index c6a5ca0563d..848b964ed2a 100644
--- a/src/java.base/share/classes/java/lang/IdentityException.java
+++ b/src/java.base/share/classes/java/lang/IdentityException.java
@@ -30,7 +30,8 @@
*
* Identity objects are required for synchronization and locking.
* Value-based
- * objects do not have identity and cannot be used for synchronization or locking.
+ * objects do not have identity and cannot be used for synchronization, locking,
+ * or for any type of {@link java.lang.ref.Reference}.
*
* @since Valhalla
*/
diff --git a/src/java.base/share/classes/java/lang/System.java b/src/java.base/share/classes/java/lang/System.java
index 41ff4623609..b8b746ee028 100644
--- a/src/java.base/share/classes/java/lang/System.java
+++ b/src/java.base/share/classes/java/lang/System.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1994, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1994, 2025, 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
@@ -471,6 +471,21 @@ public static native void arraycopy(Object src, int srcPos,
* hashCode().
* The hash code for the null reference is zero.
*
+ *
+ *
+ * The "identity hash code" of a {@linkplain Class#isValue() value object}
+ * is computed by combining the hash codes of the value object's fields recursively.
+ *
+ *
+ * @apiNote
+ *
+ *
+ * Note that, like ==, this hash code exposes information about a value object's
+ * private fields that might otherwise be hidden by an identity object.
+ * Developers should be cautious about storing sensitive secrets in value object fields.
+ *
+ *
+ *
* @param x object for which the hashCode is to be calculated
* @return the hashCode
* @since 1.1
diff --git a/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java b/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java
index a7d6217988e..282aa257aaf 100644
--- a/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java
+++ b/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2025, 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
@@ -596,7 +596,7 @@ LoadableDescriptorsAttributeBuilder add(MethodType... mtypes) {
}
boolean requiresLoadableDescriptors(Class> cls) {
- return cls.isValue() && cls.accessFlags().contains(AccessFlag.FINAL);
+ return cls.isValue() && cls.accessFlags().contains(AccessFlag.FINAL) && !cls.isPrimitive();
}
boolean isEmpty() {
diff --git a/src/java.base/share/classes/java/util/IdentityHashMap.java b/src/java.base/share/classes/java/util/IdentityHashMap.java
index 48a6d7b28df..5a11bada32a 100644
--- a/src/java.base/share/classes/java/util/IdentityHashMap.java
+++ b/src/java.base/share/classes/java/util/IdentityHashMap.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2025, 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
@@ -35,8 +35,8 @@
/**
* This class implements the {@code Map} interface with a hash table, using
- * reference-equality in place of object-equality when comparing keys (and
- * values). In other words, in an {@code IdentityHashMap}, two keys
+ * `==` in place of object-equality when comparing keys (and values).
+ * In other words, in an {@code IdentityHashMap}, two keys
* {@code k1} and {@code k2} are considered equal if and only if
* {@code (k1==k2)}. (In normal {@code Map} implementations (like
* {@code HashMap}) two keys {@code k1} and {@code k2} are considered equal
diff --git a/src/java.base/share/classes/java/util/Objects.java b/src/java.base/share/classes/java/util/Objects.java
index 7085323ce1e..f732401819a 100644
--- a/src/java.base/share/classes/java/util/Objects.java
+++ b/src/java.base/share/classes/java/util/Objects.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2009, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009, 2025, 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
@@ -28,7 +28,6 @@
import jdk.internal.javac.PreviewFeature;
import jdk.internal.util.Preconditions;
import jdk.internal.vm.annotation.ForceInline;
-import jdk.internal.misc.Unsafe;
import java.util.function.Supplier;
@@ -180,19 +179,38 @@ public static String toIdentityString(Object o) {
}
/**
- * {@return {@code true} if the specified object reference is an identity object,
- * otherwise {@code false}}
+ * {@return {@code true} if the object is a non-null reference
+ * to an {@linkplain Class#isIdentity() identity object}, otherwise {@code false}}
*
- * @param obj an object
- * @throws NullPointerException if {@code obj} is {@code null}
+ * @apiNote
+ * If the parameter is {@code null}, there is no object
+ * and hence no class to check for identity; the return is {@code false}.
+ * To test for a {@linkplain Class#isValue() value object} use:
+ * {@snippet type="java" :
+ * if (obj != null && !Objects.hasIdentity(obj)) {
+ * // obj is a non-null value object
+ * }
+ * }
+ * @param obj an object or {@code null}
* @since Valhalla
*/
@PreviewFeature(feature = PreviewFeature.Feature.VALUE_OBJECTS)
// @IntrinsicCandidate
public static boolean hasIdentity(Object obj) {
- requireNonNull(obj);
- return obj.getClass().isIdentity() || // Before Valhalla all classes are identity classes
- obj.getClass() == Object.class;
+ return (obj == null) ? false : obj.getClass().isIdentity();
+ }
+
+ /**
+ * {@return {@code true} if the object is a non-null reference
+ * to an {@linkplain Class#isValue() value object}, otherwise {@code false}}
+ *
+ * @param obj an object or {@code null}
+ * @since Valhalla
+ */
+ @PreviewFeature(feature = PreviewFeature.Feature.VALUE_OBJECTS)
+// @IntrinsicCandidate
+ public static boolean isValueObject(Object obj) {
+ return (obj == null) ? false : obj.getClass().isValue();
}
/**
diff --git a/src/java.base/share/classes/java/util/WeakHashMap.java b/src/java.base/share/classes/java/util/WeakHashMap.java
index 276e8731d84..e379a3a8f2e 100644
--- a/src/java.base/share/classes/java/util/WeakHashMap.java
+++ b/src/java.base/share/classes/java/util/WeakHashMap.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1998, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2025, 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
@@ -25,8 +25,8 @@
package java.util;
-import java.lang.ref.WeakReference;
import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
@@ -122,6 +122,22 @@
*
* Java Collections Framework.
*
+ * @apiNote
+ *
+ *
+ * Objects that are {@linkplain Class#isValue() value objects} do not have identity
+ * and can not be used as keys in a {@code WeakHashMap}. {@linkplain java.lang.ref.Reference References}
+ * such as {@linkplain WeakReference WeakReference} used by {@code WeakhashMap}
+ * to hold the key; cannot refer to a value object.
+ * Methods such as {@linkplain #get get} or {@linkplain #containsKey containsKey}
+ * will always return {@code null} or {@code false} respectively.
+ * The methods such as {@linkplain #put put}, {@linkplain #putAll putAll},
+ * {@linkplain #compute(Object, BiFunction) compute}, and
+ * {@linkplain #computeIfAbsent(Object, Function) computeIfAbsent} or any method putting
+ * a value object, as a key, throw {@link IdentityException}.
+ *
+ *
+ *
* @param the type of keys maintained by this map
* @param the type of mapped values
*
@@ -288,6 +304,8 @@ static Object unmaskNull(Object key) {
/**
* Checks for equality of non-null reference x and possibly-null y. By
* default uses Object.equals.
+ * The key may be a value object, but it will never be equal to the referent
+ * so does not need a separate Objects.hasIdentity check.
*/
private boolean matchesKey(Entry e, Object key) {
// check if the given entry refers to the given key without
@@ -456,9 +474,11 @@ Entry getEntry(Object key) {
* {@code null} if there was no mapping for {@code key}.
* (A {@code null} return can also indicate that the map
* previously associated {@code null} with {@code key}.)
+ * @throws IdentityException if {@code key} is a value object
*/
public V put(K key, V value) {
Object k = maskNull(key);
+ Objects.requireIdentity(k);
int h = hash(k);
Entry[] tab = getTable();
int i = indexFor(h, tab.length);
@@ -548,6 +568,7 @@ private void transfer(Entry[] src, Entry[] dest) {
*
* @param m mappings to be stored in this map.
* @throws NullPointerException if the specified map is null.
+ * @throws IdentityException if any of the {@code keys} is a value object
*/
public void putAll(Map extends K, ? extends V> m) {
int numKeysToBeAdded = m.size();
diff --git a/test/jdk/java/lang/Class/GenericStringTest.java b/test/jdk/java/lang/Class/GenericStringTest.java
index 73a3ffed5ad..9b0f744944d 100644
--- a/test/jdk/java/lang/Class/GenericStringTest.java
+++ b/test/jdk/java/lang/Class/GenericStringTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2025, 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
@@ -57,7 +57,7 @@ public static void main(String... args) throws ReflectiveOperationException {
new PlatformTestCase(java.lang.Enum.class,
"public abstract class java.lang.Enum>"),
new PlatformTestCase(java.util.Map.class,
- "public abstract interface java.util.Map"),
+ "public abstract value interface java.util.Map"),
new PlatformTestCase(java.util.EnumMap.class,
"public class java.util.EnumMap,V>"),
new PlatformTestCase(java.util.EventListenerProxy.class,
@@ -141,10 +141,10 @@ private static int checkToGenericString(Class> clazz, String expected) {
String value();
}
-@ExpectedGenericString("abstract interface AnInterface")
+@ExpectedGenericString("abstract value interface AnInterface")
strictfp interface AnInterface {}
-@ExpectedGenericString("abstract interface LocalMap")
+@ExpectedGenericString("abstract value interface LocalMap")
interface LocalMap {}
@ExpectedGenericString("final enum AnEnum")
@@ -202,7 +202,7 @@ non-sealed class GreatGrandChildACCB extends GrandChildACC {}
}
// Test cases for sealed/non-sealed _interface_ hierarchy.
-@ExpectedGenericString("abstract sealed interface SealedRootIntf")
+@ExpectedGenericString("abstract sealed value interface SealedRootIntf")
sealed interface SealedRootIntf
permits
SealedRootIntf.ChildA,
@@ -241,13 +241,13 @@ non-sealed class GreatGrandChildACCB extends GrandChildACC {}
}
}
- @ExpectedGenericString("public abstract static sealed interface SealedRootIntf$IntfA")
+ @ExpectedGenericString("public abstract static sealed value interface SealedRootIntf$IntfA")
sealed interface IntfA extends SealedRootIntf {
@ExpectedGenericString("public static non-sealed class SealedRootIntf$IntfA$IntfAImpl")
non-sealed class IntfAImpl implements IntfA {}
}
- @ExpectedGenericString("public abstract static non-sealed interface SealedRootIntf$IntfB")
+ @ExpectedGenericString("public abstract static non-sealed value interface SealedRootIntf$IntfB")
non-sealed interface IntfB extends SealedRootIntf {
// Check that non-sealing can be allowed with a second superinterface being sealed.
@ExpectedGenericString("public static non-sealed class SealedRootIntf$IntfB$IntfAImpl")
diff --git a/test/jdk/java/util/Collection/MOAT.java b/test/jdk/java/util/Collection/MOAT.java
index d281e5db125..cee1d373aa6 100644
--- a/test/jdk/java/util/Collection/MOAT.java
+++ b/test/jdk/java/util/Collection/MOAT.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2025, 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
@@ -26,11 +26,12 @@
* @bug 6207984 6272521 6192552 6269713 6197726 6260652 5073546 4137464
* 4155650 4216399 4294891 6282555 6318622 6355327 6383475 6420753
* 6431845 4802633 6570566 6570575 6570631 6570924 6691185 6691215
- * 4802647 7123424 8024709 8193128
+ * 4802647 7123424 8024709 8193128 8346307
* @summary Run many tests on many Collection and Map implementations
* @author Martin Buchholz
* @modules java.base/java.util:open
* @run main MOAT
+ * @run main/othervm --enable-preview MOAT
* @key randomness
*/
diff --git a/test/jdk/java/util/WeakHashMapValues.java b/test/jdk/java/util/WeakHashMapValues.java
new file mode 100644
index 00000000000..774f3ade96b
--- /dev/null
+++ b/test/jdk/java/util/WeakHashMapValues.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2024, 2025, 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.
+ */
+
+import java.util.HashMap;
+import java.util.WeakHashMap;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/*
+ * @test
+ * @summary Check WeakHashMap throws IdentityException when Value Objects are put
+ * @enablePreview
+ * @run junit WeakHashMapValues
+ */
+public class WeakHashMapValues {
+
+ /*
+ * Check that any kind of put with a value class as a key throws IdentityException
+ */
+ @Test
+ void checkThrowsIdentityException() {
+ WeakHashMap