Mixing classes based on values

I am seeking clarification for the definition of numbering classes based on values . I cannot imagine how it is assumed that the last point (6) should work together with the first.

  • (1) they are final and immutable (although they may contain references to mutable objects)
  • (6) they are freely replaced at equal values, which means that the exchange of any two instances x and y equal in accordance with equals () for any call to a calculation or method should not cause visible changes 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 does he need to clarify?

Update

Eran's answer 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 

Define a fun method m that performs some mutation and cancels 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; } 

This is a weird method, I know. But, I think it qualifies as "any calculation or method call", right?

+5
source share
5 answers

You can obtain the invalidity of your actions from the specification you are referring to:

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.

(my emphasis)

Modifying an object is an identification sensitive operation, because it only affects the object with a specific identifier represented by the link that you use to modify it.

When you call x.get().add(""); , you perform an operation that allows you to determine whether the tags x and y represent the same instance, in other words, you perform an operation that is sensitive to the identifier.

However, I expect that if the future JVM is really trying to replace value-based instances, it should exclude instances that reference mutable objects to ensure compatibility. If you perform an operation that creates Optional , followed by retrieving Optional , for example. … stream. findAny().get() … stream. findAny().get() would be catastrophic / unacceptable if the intermediate operation would allow replacing the element with another object that turned out to be equal at the intermediate point of Optional (if the element is not the value type itself) ...

+5
source

they are freely substituted if equal , which means that the exchange of any two instances x and y, equal in accordance with equals () for any call to a calculation or method, should not cause visible changes in behavior

As soon as b.get().add("a"); is executed, a no longer equals - b , so you have no reason to expect assertTrue(a.get().isEmpty()); and assertTrue(b.get().isEmpty()); will give the same result.

The fact that a value-based class is immutable does not mean that you cannot mutate the values ​​stored in instances of such classes (as indicated in though may contain references to mutable objects ). This means that after creating an Optional instance with Optional a = Optional.of(new ArrayList<String>()) you cannot mutate a to maintain a reference to another ArrayList .

+9
source

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.

+3
source

Clause 6 says that if a and b are equal, then they can be used interchangeably, i.e. they say that if the method expects two instances of class A, and you created instances of & b, then if a and b pass point 6, you can send (a,a) or (b,b) or (a,b) all three will be give the same result.

+1
source

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 assertEquals (a, b), according to the API :

  • checks if parameters a and b optional
  • Elements that do not matter, or
  • The values ​​given are equal to each other through equals () (in your example, this is the same as that of ArrayList).

So, when you change one of the ArrayList that the optional instance points to, the statement will fail at the third point.

+1
source

Source: https://habr.com/ru/post/1273710/


All Articles