Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8346307: [lworld] Clarify identity vs value in Class, Objects, and document limitations of value objects #1327

Open
wants to merge 4 commits into
base: lworld
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 14 additions & 16 deletions src/java.base/share/classes/java/lang/Class.java
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -683,36 +683,34 @@ private static Class<?> forName(Module module, String name, Class<?> caller) {
}

/**
* {@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;
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/java.base/share/classes/java/lang/IdentityException.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
* <p>
* Identity objects are required for synchronization and locking.
* <a href="{@docRoot}/java.base/java/lang/doc-files/ValueBased.html">Value-based</a>
* 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
*/
Expand Down
17 changes: 16 additions & 1 deletion src/java.base/share/classes/java/lang/System.java
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -673,6 +673,21 @@ public static native void arraycopy(Object src, int srcPos,
* hashCode().
* The hash code for the null reference is zero.
*
* <div class="preview-block">
* <div class="preview-comment">
* 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.
* </div>
* </div>
* @apiNote
* <div class="preview-block">
* <div class="preview-comment">
* 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.
* </div>
* </div>
*
* @param x object for which the hashCode is to be calculated
* @return the hashCode
* @since 1.1
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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() {
Expand Down
6 changes: 3 additions & 3 deletions src/java.base/share/classes/java/util/IdentityHashMap.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2000, 2022, 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
Expand Down Expand Up @@ -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
Expand Down
36 changes: 27 additions & 9 deletions src/java.base/share/classes/java/util/Objects.java
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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();
}

/**
Expand Down
25 changes: 23 additions & 2 deletions src/java.base/share/classes/java/util/WeakHashMap.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1998, 2022, 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
Expand All @@ -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;
Expand Down Expand Up @@ -122,6 +122,22 @@
* <a href="{@docRoot}/java.base/java/util/package-summary.html#CollectionsFramework">
* Java Collections Framework</a>.
*
* @apiNote
* <div class="preview-block">
* <div class="preview-comment">
* Objects that are {@linkplain Class#isValue() value objects} do not have identity
* and can not be used as keys in a {@code WeakHashMap}. {@linkplain 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}.
* </div>
* </div>
*
* @param <K> the type of keys maintained by this map
* @param <V> the type of mapped values
*
Expand Down Expand Up @@ -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<K,V> e, Object key) {
// check if the given entry refers to the given key without
Expand Down Expand Up @@ -456,9 +474,11 @@ Entry<K,V> 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<K,V>[] tab = getTable();
int i = indexFor(h, tab.length);
Expand Down Expand Up @@ -548,6 +568,7 @@ private void transfer(Entry<K,V>[] src, Entry<K,V>[] 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();
Expand Down
14 changes: 7 additions & 7 deletions test/jdk/java/lang/Class/GenericStringTest.java
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -57,7 +57,7 @@ public static void main(String... args) throws ReflectiveOperationException {
new PlatformTestCase(java.lang.Enum.class,
"public abstract class java.lang.Enum<E extends java.lang.Enum<E>>"),
new PlatformTestCase(java.util.Map.class,
"public abstract interface java.util.Map<K,V>"),
"public abstract value interface java.util.Map<K,V>"),
new PlatformTestCase(java.util.EnumMap.class,
"public class java.util.EnumMap<K extends java.lang.Enum<K>,V>"),
new PlatformTestCase(java.util.EventListenerProxy.class,
Expand Down Expand Up @@ -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<K,V>")
@ExpectedGenericString("abstract value interface LocalMap<K,V>")
interface LocalMap<K,V> {}

@ExpectedGenericString("final enum AnEnum")
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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")
Expand Down
5 changes: 3 additions & 2 deletions test/jdk/java/util/Collection/MOAT.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2023, 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
Expand All @@ -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
*/

Expand Down
Loading