Skip to content

Commit 3626f88

Browse files
author
Roger Riggs
committed
8361307: [lworld] Clarify identity vs value in Class, Objects, and document limitations of value objects
1 parent 1fdd381 commit 3626f88

File tree

10 files changed

+277
-50
lines changed

10 files changed

+277
-50
lines changed

src/java.base/share/classes/java/lang/Class.java

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -613,26 +613,41 @@ public static Class<?> forName(Module module, String name) {
613613
}
614614

615615
/**
616-
* {@return {@code true} if this {@code Class} object represents an identity
617-
* class or interface; otherwise {@code false}}
618-
*
619-
* If this {@code Class} object represents an array type, then this method
620-
* returns {@code true}.
621-
* If this {@code Class} object represents a primitive type, or {@code void},
622-
* then this method returns {@code false}.
616+
* {@return {@code true} if this {@code Class} object represents an identity class,
617+
* otherwise {@code false}}
623618
*
619+
* <ul>
620+
* <li>
621+
* If this {@code Class} object represents an array type this method returns {@code true}.
622+
* <li>
623+
* If this {@code Class} object represents an interface, a primitive type,
624+
* or {@code void} this method returns {@code false}.
625+
* <li>
626+
* For all other {@code Class} objects, this method returns {@code true} if either
627+
* preview features are disabled or {@linkplain Modifier#IDENTITY} is set in the
628+
* {@linkplain #getModifiers() class modifiers}.
629+
* </ul>
630+
* @see AccessFlag#IDENTITY
624631
* @since Valhalla
625632
*/
626633
@PreviewFeature(feature = PreviewFeature.Feature.VALUE_OBJECTS, reflective=true)
627634
public native boolean isIdentity();
628635

629636
/**
630-
* {@return {@code true} if this {@code Class} object represents a value
631-
* class; otherwise {@code false}}
632-
*
633-
* If this {@code Class} object represents an array type, an interface,
634-
* a primitive type, or {@code void}, then this method returns {@code false}.
635-
*
637+
* {@return {@code true} if this {@code Class} object represents a value class,
638+
* otherwise {@code false}}
639+
* <ul>
640+
* <li>
641+
* If this {@code Class} object represents an array type this method returns {@code false}.
642+
* <li>
643+
* If this {@code Class} object represents an interface, a primitive type,
644+
* or {@code void} this method returns {@code true} only if preview features are enabled.
645+
* <li>
646+
* For all other {@code Class} objects, this method returns {@code true} only if
647+
* preview features are enabled and {@linkplain Modifier#IDENTITY} is not set in the
648+
* {@linkplain #getModifiers() class modifiers}.
649+
* </ul>
650+
* @see AccessFlag#IDENTITY
636651
* @since Valhalla
637652
*/
638653
@PreviewFeature(feature = PreviewFeature.Feature.VALUE_OBJECTS, reflective=true)

src/java.base/share/classes/java/lang/IdentityException.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
* <p>
3131
* Identity objects are required for synchronization and locking.
3232
* <a href="{@docRoot}/java.base/java/lang/doc-files/ValueBased.html">Value-based</a>
33-
* objects do not have identity and cannot be used for synchronization or locking.
33+
* objects do not have identity and cannot be used for synchronization, locking,
34+
* or any type of {@link java.lang.ref.Reference}.
3435
*
3536
* @since Valhalla
3637
*/

src/java.base/share/classes/java/lang/Object.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,17 @@
3131
* Class {@code Object} is the root of the class hierarchy.
3232
* Every class has {@code Object} as a superclass. All objects,
3333
* including arrays, implement the methods of this class.
34-
* <p>
35-
* Subclasses of {@code java.lang.Object} can be either an {@linkplain Class#isIdentity identity class}
36-
* or a {@linkplain Class#isValue value class}.
37-
* See {@jls The Java Language Specification 8.1.1.5 Value Classes}.
34+
*
35+
* <div class="preview-block">
36+
* <div class="preview-comment">
37+
* When preview features are enabled, subclasses of {@code java.lang.Object} can be either
38+
* an {@linkplain Class#isIdentity identity class} or a {@linkplain Class#isValue value class}.
39+
* See {@jls The Java Language Specification 8.1.1.5 Value Classes}.
40+
* Use of value class instances for synchronization, mutexes, or with
41+
* {@linkplain java.lang.ref.Reference object references} result in
42+
* {@link IdentityException}.
43+
* </div>
44+
* </div>
3845
*
3946
* @see java.lang.Class
4047
* @since 1.0

src/java.base/share/classes/java/lang/System.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,21 @@ public static native void arraycopy(Object src, int srcPos,
471471
* hashCode().
472472
* The hash code for the null reference is zero.
473473
*
474+
* <div class="preview-block">
475+
* <div class="preview-comment">
476+
* The "identity hash code" of a {@linkplain Class#isValue() value object}
477+
* is computed by combining the identity hash codes of the value object's fields recursively.
478+
* </div>
479+
* </div>
480+
* @apiNote
481+
* <div class="preview-block">
482+
* <div class="preview-comment">
483+
* Note that, like ==, this hash code exposes information about a value object's
484+
* private fields that might otherwise be hidden by an identity object.
485+
* Developers should be cautious about storing sensitive secrets in value object fields.
486+
* </div>
487+
* </div>
488+
*
474489
* @param x object for which the hashCode is to be calculated
475490
* @return the hashCode
476491
* @since 1.1

src/java.base/share/classes/java/util/IdentityHashMap.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -35,8 +35,8 @@
3535

3636
/**
3737
* This class implements the {@code Map} interface with a hash table, using
38-
* reference-equality in place of object-equality when comparing keys (and
39-
* values). In other words, in an {@code IdentityHashMap}, two keys
38+
* `==` in place of object-equality when comparing keys (and values).
39+
* In other words, in an {@code IdentityHashMap}, two keys
4040
* {@code k1} and {@code k2} are considered equal if and only if
4141
* {@code (k1==k2)}. (In normal {@code Map} implementations (like
4242
* {@code HashMap}) two keys {@code k1} and {@code k2} are considered equal

src/java.base/share/classes/java/util/Objects.java

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2009, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -28,7 +28,6 @@
2828
import jdk.internal.javac.PreviewFeature;
2929
import jdk.internal.util.Preconditions;
3030
import jdk.internal.vm.annotation.ForceInline;
31-
import jdk.internal.misc.Unsafe;
3231

3332
import java.util.function.Supplier;
3433

@@ -180,19 +179,38 @@ public static String toIdentityString(Object o) {
180179
}
181180

182181
/**
183-
* {@return {@code true} if the specified object reference is an identity object,
184-
* otherwise {@code false}}
182+
* {@return {@code true} if the object is a non-null reference
183+
* to an {@linkplain Class#isIdentity() identity object}, otherwise {@code false}}
185184
*
186-
* @param obj an object
187-
* @throws NullPointerException if {@code obj} is {@code null}
185+
* @apiNote
186+
* If the parameter is {@code null}, there is no object
187+
* and hence no class to check for identity; the return is {@code false}.
188+
* To test for a {@linkplain Class#isValue() value object} use:
189+
* {@snippet type="java" :
190+
* if (obj != null && !Objects.hasIdentity(obj)) {
191+
* // obj is a non-null value object
192+
* }
193+
* }
194+
* @param obj an object or {@code null}
188195
* @since Valhalla
189196
*/
190197
@PreviewFeature(feature = PreviewFeature.Feature.VALUE_OBJECTS)
191198
// @IntrinsicCandidate
192199
public static boolean hasIdentity(Object obj) {
193-
requireNonNull(obj);
194-
return obj.getClass().isIdentity() || // Before Valhalla all classes are identity classes
195-
obj.getClass() == Object.class;
200+
return (obj == null) ? false : obj.getClass().isIdentity();
201+
}
202+
203+
/**
204+
* {@return {@code true} if the object is a non-null reference
205+
* to a {@linkplain Class#isValue() value object}, otherwise {@code false}}
206+
*
207+
* @param obj an object or {@code null}
208+
* @since Valhalla
209+
*/
210+
@PreviewFeature(feature = PreviewFeature.Feature.VALUE_OBJECTS)
211+
// @IntrinsicCandidate
212+
public static boolean isValueObject(Object obj) {
213+
return (obj == null) ? false : obj.getClass().isValue();
196214
}
197215

198216
/**

src/java.base/share/classes/java/util/WeakHashMap.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1998, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -25,8 +25,8 @@
2525

2626
package java.util;
2727

28-
import java.lang.ref.WeakReference;
2928
import java.lang.ref.ReferenceQueue;
29+
import java.lang.ref.WeakReference;
3030
import java.util.function.BiConsumer;
3131
import java.util.function.BiFunction;
3232
import java.util.function.Consumer;
@@ -122,6 +122,22 @@
122122
* <a href="{@docRoot}/java.base/java/util/package-summary.html#CollectionsFramework">
123123
* Java Collections Framework</a>.
124124
*
125+
* @apiNote
126+
* <div class="preview-block">
127+
* <div class="preview-comment">
128+
* Objects that are {@linkplain Class#isValue() value objects} do not have identity
129+
* and can not be used as keys in a {@code WeakHashMap}. {@linkplain java.lang.ref.Reference References}
130+
* such as {@linkplain WeakReference WeakReference} used by {@code WeakhashMap}
131+
* to hold the key cannot refer to a value object.
132+
* Methods such as {@linkplain #get get} or {@linkplain #containsKey containsKey}
133+
* will always return {@code null} or {@code false} respectively.
134+
* The methods such as {@linkplain #put put}, {@linkplain #putAll putAll},
135+
* {@linkplain #compute(Object, BiFunction) compute}, and
136+
* {@linkplain #computeIfAbsent(Object, Function) computeIfAbsent} or any method putting
137+
* a value object, as a key, throw {@link IdentityException}.
138+
* </div>
139+
* </div>
140+
*
125141
* @param <K> the type of keys maintained by this map
126142
* @param <V> the type of mapped values
127143
*
@@ -288,6 +304,8 @@ static Object unmaskNull(Object key) {
288304
/**
289305
* Checks for equality of non-null reference x and possibly-null y. By
290306
* default uses Object.equals.
307+
* The key may be a value object, but it will never be equal to the referent
308+
* so does not need a separate Objects.hasIdentity check.
291309
*/
292310
private boolean matchesKey(Entry<K,V> e, Object key) {
293311
// check if the given entry refers to the given key without
@@ -456,9 +474,11 @@ Entry<K,V> getEntry(Object key) {
456474
* {@code null} if there was no mapping for {@code key}.
457475
* (A {@code null} return can also indicate that the map
458476
* previously associated {@code null} with {@code key}.)
477+
* @throws IdentityException if {@code key} is a value object
459478
*/
460479
public V put(K key, V value) {
461480
Object k = maskNull(key);
481+
Objects.requireIdentity(k);
462482
int h = hash(k);
463483
Entry<K,V>[] tab = getTable();
464484
int i = indexFor(h, tab.length);
@@ -546,8 +566,13 @@ private void transfer(Entry<K,V>[] src, Entry<K,V>[] dest) {
546566
* These mappings will replace any mappings that this map had for any
547567
* of the keys currently in the specified map.
548568
*
569+
* @apiNote If the specified map contains keys that are {@linkplain Objects#isValueObject value objects}
570+
* an {@linkplain IdentityException } is thrown when the first value object key is encountered.
571+
* Zero or more mappings may have already been copied to this map.
572+
*
549573
* @param m mappings to be stored in this map.
550574
* @throws NullPointerException if the specified map is null.
575+
* @throws IdentityException if any of the {@code keys} is a value object
551576
*/
552577
public void putAll(Map<? extends K, ? extends V> m) {
553578
int numKeysToBeAdded = m.size();

test/jdk/java/util/Collection/MOAT.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -26,11 +26,12 @@
2626
* @bug 6207984 6272521 6192552 6269713 6197726 6260652 5073546 4137464
2727
* 4155650 4216399 4294891 6282555 6318622 6355327 6383475 6420753
2828
* 6431845 4802633 6570566 6570575 6570631 6570924 6691185 6691215
29-
* 4802647 7123424 8024709 8193128 8327858
29+
* 4802647 7123424 8024709 8193128 8327858 8346307
3030
* @summary Run many tests on many Collection and Map implementations
3131
* @author Martin Buchholz
3232
* @modules java.base/java.util:open
3333
* @run main MOAT
34+
* @run main MOAT --enable-preview
3435
* @key randomness
3536
*/
3637

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
import java.util.HashMap;
25+
import java.util.LinkedHashMap;
26+
import java.util.WeakHashMap;
27+
28+
import org.junit.jupiter.api.Test;
29+
30+
import static org.junit.jupiter.api.Assertions.assertFalse;
31+
import static org.junit.jupiter.api.Assertions.assertThrows;
32+
import static org.junit.jupiter.api.Assertions.assertTrue;
33+
import static org.junit.jupiter.api.Assertions.assertEquals;
34+
35+
/*
36+
* @test
37+
* @summary Check WeakHashMap throws IdentityException when Value Objects are put
38+
* @enablePreview
39+
* @run junit WeakHashMapValues
40+
*/
41+
public class WeakHashMapValues {
42+
43+
/*
44+
* Check that any kind of put with a value class as a key throws IdentityException
45+
*/
46+
@Test
47+
void checkThrowsIdentityException() {
48+
WeakHashMap<Object, Object> whm = new WeakHashMap<>();
49+
Object key = new Foo(1);
50+
assertThrows(IdentityException.class, () -> whm.put(key, "1"));
51+
assertThrows(IdentityException.class, () -> whm.putIfAbsent(key, "2"));
52+
assertThrows(IdentityException.class, () -> whm.compute(key, (_, _) -> "3"));
53+
assertThrows(IdentityException.class, () -> whm.computeIfAbsent(key, (_) -> "4"));
54+
55+
HashMap<Object, String> hmap = new HashMap<>();
56+
hmap.put(key, "6");
57+
assertThrows(IdentityException.class, () -> whm.putAll(hmap));
58+
}
59+
60+
/*
61+
* Check that any kind of put with Integer as a value class as a key throws IdentityException
62+
*/
63+
@Test
64+
void checkIntegerThrowsIdentityException() {
65+
WeakHashMap<Object, Object> whm = new WeakHashMap<>();
66+
Object key = 1;
67+
assertThrows(IdentityException.class, () -> whm.put(key, "1"));
68+
assertThrows(IdentityException.class, () -> whm.putIfAbsent(key, "2"));
69+
assertThrows(IdentityException.class, () -> whm.compute(key, (_, _) -> "3"));
70+
assertThrows(IdentityException.class, () -> whm.computeIfAbsent(key, (_) -> "4"));
71+
72+
HashMap<Object, String> hmap = new HashMap<>();
73+
hmap.put(key, "6");
74+
assertThrows(IdentityException.class, () -> whm.putAll(hmap));
75+
76+
}
77+
78+
/**
79+
* Check that queries with a value object return false or null.
80+
*/
81+
@Test
82+
void checkValueObjectGet() {
83+
WeakHashMap<Object, Object> whm = new WeakHashMap<>();
84+
Object key = "X";
85+
Object v = new Foo(1);
86+
assertEquals(whm.get(v), null, "Get of value object should return null");
87+
assertEquals(whm.containsKey(v), false, "containsKey should return false");
88+
}
89+
90+
/**
91+
* Check WeakHashMap.putAll from a source map containing a value object as a key throws.
92+
*/
93+
@Test
94+
void checkValueObjectPutAll() {
95+
// src is mix of identity and value objects (Integer is value class with --enable-preview)
96+
HashMap<Object, Object> srcMap = new LinkedHashMap<>();
97+
srcMap.put("abc", "Vabc");
98+
srcMap.put(1, "V1");
99+
srcMap.put("xyz", "Vxyz");
100+
WeakHashMap<Object, Object> whm = new WeakHashMap<>();
101+
assertThrows(IdentityException.class, () -> whm.putAll(srcMap));
102+
assertTrue(whm.containsKey("abc"), "Identity key should have been copied");
103+
assertFalse(whm.containsKey(1), "Value object key should not have been copied");
104+
assertEquals(1, whm.size(), "Result map size");
105+
}
106+
}
107+
108+
value class Foo {
109+
int x;
110+
Foo(int x) {
111+
this.x = x;
112+
}
113+
}

0 commit comments

Comments
 (0)