Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Value-based Classes confusion

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

  • (1) they are final and immutable (though may contain references to mutable objects)
  • (6) 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.

Optional is such a class.

Optional a = Optional.of(new ArrayList<String>());
Optional b = Optional.of(new ArrayList<String>());
assertEquals(a, b); // passes as `equals` delegated to the lists

b.get().add("a");

// now bite the last bullet
assertTrue(a.get().isEmpty()); // passes
assertTrue(b.get().isEmpty()); // throws

Am I reading it incorrectly, or would it need to get more precise?

Update

The answer by Eran makes sense (they are no more equal), but let me move the target:

...
assertEquals(a, b); // now, they are still equal
assertEquals(m(a, b), m(a, a)); // this will throw
assertEquals(a, b); // now, they are equal, too

Let's define a funny method m, which does some mutation and undoes it again:

int m(Optional<ArrayList<String>> x, Optional<ArrayList<String>> y) {
    x.get().add("");
    int result = x.get().size() + y.get().size();
    x.get().remove(x.get().size() - 1);
    return result;
}

It's strange method, I know. But I guess, it qualifies as "any computation or method invocation", doesn't it?

like image 803
maaartinus Avatar asked Nov 28 '17 09:11

maaartinus


2 Answers

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.

like image 183
Eran Avatar answered Sep 29 '22 20:09

Eran


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)…

like image 36
Holger Avatar answered Sep 29 '22 20:09

Holger