I think a more interesting example is as follows:
void foo() { List<String> list = new ArrayList<>(); Optional<List<String>> a = Optional.of(list); Optional<List<String>> b = Optional.of(list); bar(a, b); }
Clearly a.equals(b) true. Also, since Optional is final (cannot be a subclass), immutable, and both a and b belong to the same list, a.equals(b) will always be true. (Well, almost always, depending on race conditions, when another thread changes the list while this thread compares them.) So it looks like this will be the case when the JVM can replace b with a or vice versa.
Like today (Java 8 and 9 and 10), we can write a == b , and the result will be false. The reason is that we know that Optional is an instance of a regular reference type and the way it is currently implemented, Optional.of(x) will always return a new instance, and two new instances will never == each other.
However, in the paragraph below , value-based classes say:
A program can produce unpredictable results if it tries to distinguish two references to equal values of a value-based class, either directly through reference equality or indirectly through access to synchronization, hashing identities, serialization, or any other identification-sensitive mechanism. Using such case-sensitive operations for value-based class instances can have unpredictable effects and should be avoided.
In other words, do not do this, or at least do not rely on the result. The reason is that tomorrow the semantics of the == operation may change. In a hypothetical future world with typed values, == can be redefined for value types as the same as equals , and Optional can be changed from a value-based class to a value type. If this happens, then a == b will be true, not false.
One of the main ideas about value types is that they have no concept of identity (or perhaps their identification is not recognized for Java programs). In such a world, how can we determine if tags a and b “really” the same or different?
Suppose we had to measure the bar method in some way (for example, with a debugger) so that we could check the attributes of parameter values in a way that could not be done using a programming language, for example by looking at the addresses of machines. Even if a == b true (remember that in the world the type of values == matches equals ), we can make sure that a and b are at different addresses in memory.
Now suppose the JIT compiler compiles foo and builds calls on Optional.of . Seeing that there are currently two code commands that return two results, which are always equals , the compiler excludes one of the blocks, and then uses the same result wherever a or b . Now, in our instrumental version of bar , we can notice that both parameter values are the same. The JIT compiler is allowed for this because of the sixth element of the marker, which allows you to replace equals .
Note that we can only observe this difference because we use an extralinguistic mechanism such as a debugger. In the Java programming language, we cannot tell the difference at all, and thus this substitution cannot affect the result of any Java program. This allows the JVM to choose any implementation strategy that it sees fit. The JVM can allocate a and b on the heap, on the stack, one on each, as separate instances or as identical instances if Java programs cannot distinguish this information. When the JVM is given the freedom of choice, it can make programs much faster.
This is the point of the sixth paragraph.