I\'m seeking some clarification to the definition of Value-based Classes. I can\'t imagine, how is the last bullet point (6) supposed to work together with the first one
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);
}
It's clear that a.equals(b)
is true. Furthermore, since Optional
is final (cannot be subclassed), immutable, and both a
and b
refer to the same list, a.equals(b)
will always be true. (Well, almost always, subject to race conditions where another thread is modifying the list while this one is comparing them.) Thus, this seems like it would be a case where it would be possible for the JVM to substitute b
for a
or vice-versa.
As things stand 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 an ordinary reference type, and the way things are currently implemented, Optional.of(x)
will always return a new instance, and two new instances are never ==
to each other.
However, the paragraph at the bottom of the value-based classes definition says:
A program may produce unpredictable results if it attempts to distinguish two references to equal values of a value-based class, whether directly via reference equality or indirectly via an appeal to synchronization, identity hashing, serialization, or any other identity-sensitive mechanism. Use of such identity-sensitive operations on instances of value-based classes may have unpredictable effects and should be avoided.
In other words, "don't do that," or at least, don't rely on the result. The reason is that tomorrow the semantics of the ==
operation might change. In a hypothetical future value-typed world, ==
might be redefined for value types to be the same as equals
, and Optional
might change from being a value-based class to being a value type. If this happens, then a == b
will be true instead of false.
One of the main ideas about value types is that they have no notion of identity (or perhaps their identity isn't detectable to Java programs). In such a world, how could we tell whether a
and b
"really" are the same or different?
Suppose we were to instrument the bar
method via some means (say, a debugger) such that we can inspect the attributes of the parameter values in a way that can't be done through the programming language, such as by looking at machine addresses. Even if a == b
is true (remember, in a value-typed world, ==
is the same as equals
) we might be able to ascertain that a
and b
reside at different addresses in memory.
Now suppose the JIT compiler compiles foo
and inlines the calls to Optional.of
. Seeing that there are now two hunks of code that return two results that are always equals
, the compiler eliminates one of the hunks and then uses the same result wherever a
or b
is used. Now, in our instrumented version of bar
, we might observe that the two parameter values are the same. The JIT compiler is allowed to do this because of the sixth bullet item, which allows substitution of values that are equals
.
Note that we're only able to observe this difference because we're using an extra-linguistic mechanism such as a debugger. Within the Java programming language, we can't tell the difference at all, and thus this substitution can't affect the result of any Java program. This lets the JVM choose any implementation strategy it sees fit. The JVM is free to allocate a
and b
on the heap, on the stack, one on each, as distinct instances, or as the same instances, as long as Java programs can't tell the difference. When the JVM is granted freedom of implementation choices, it can make programs go a lot faster.
That's the point of the sixth bullet item.
You can derive the invalidity of your actions from the specification you’re referring to:
A program may produce unpredictable results if it attempts to distinguish two references to equal values of a value-based class, whether directly via reference equality or indirectly via an appeal to synchronization, identity hashing, serialization, or any other identity-sensitive mechanism. Use of such identity-sensitive operations on instances of value-based classes may have unpredictable effects and should be avoided.
(emphasis mine)
Modifying an object is an identity-sensitive operation, as it only affects the object with the specific identity represented by the reference you are using for the modification.
When you are calling x.get().add("");
you are performing an operation that allows to recognize whether x
and y
represent the same instance, in other words, you are performing an identity sensitive operation.
Still, I expect that if a future JVM truly tries to substitute value based instances, it has to exclude instances referring to mutable objects, to ensure compatibility. If you perform an operation that produces an Optional
followed by extracting the Optional
, e.g. … stream. findAny().get()
, it would be disastrous/unacceptable if the intermediate operation allowed to substitute the element with another object that happened to be equal at the point of the intermediate Optional
use (if the element is not itself a value type)…
Point 6 says if a & b are equal then they can be used interchangeably i.e say if a method expects two instances of Class A and you have created a&b instances then if a & b passes point 6 you may send (a,a) or (b,b) or (a,b)
all three will give the same output.
they are freely substitutable when equal, meaning that interchanging any two instances x and y that are equal according to equals() in any computation or method invocation should produce no visible change in behavior
Once b.get().add("a");
is executed, a
is no longer equals
to b
, so you have no reason to expect assertTrue(a.get().isEmpty());
and assertTrue(b.get().isEmpty());
would produce the same result.
The fact that a value based class is immutable doesn't mean you can't mutate the values stored in instances of such classes (as stated in though may contain references to mutable objects
). It only means that once you create an Optional
instance with Optional a = Optional.of(new ArrayList<String>())
, you can't mutate a
to hold a reference to a different ArrayList
.
When you execute the lines:
Optional a = Optional.of(new ArrayList<String>());
Optional b = Optional.of(new ArrayList<String>());
assertEquals(a, b); // passes as `equals` delegated to the lists
In the assertEquals(a, b), according to the API :
a
and b
are both Optional So, when you change one of the ArrayList the Optional instance is pointing to, the assert will fail in the third point.