Could Primitive Wrapper Classes (Integer, Double etc) and String be approximated as value based classes?

Viewed 186

Java introduced value based classes with Java 8 and has only marked few Java.Util (Optional) and Java.time classes with it.

The criteria for value-based classes is defined here.

Most of these criteria (except the strict guidance against using them in identity-sensitive operations and the fact that they should not have accessible constructors) seem to fit for Primitive wrapper classes like Integer, Double (and similar immutable classes like String). I understand technically developers might have used these classes with identity-based operations (although they shouldn't have) and therefore marking them value based now could cause backward-compatibility issues. But apart from this, would there be any other reason that why in concept, these wrapper classes not be value-based?

The reason I incline to think of them as value based.

When you define Integer myInt = 5, you are really just interested in the value 5 and not in the reference holding this value (at least for most of the use cases that I can think of). Likewise when you say String myStr = "hello world", you are really interested in value literal "hello world" and not in it's reference.

2 Answers

Basically, you named the most important reason, why retrofitting these classes as value based is not feasible: backward compatibility.

You might have noticed that the constructors of the primitive wrapper types have been deprecated in Java 9, which would be a step into that direction. Still, using identity sensitive operations is only discouraged, not forbidden, so a change that breaks compatibility can not be made on that basis. But potentially breaking identity sensitive operations, would be the only thing that may enable subsequent practical advantages from being a value based class.

For classes like String, BigInteger, and BigDecimal, the JDK developers did not even dare to make the step of deprecating the constructors, most likely because that would be too disrupting. For some constructors, there’s not even an equivalent factory method.

But there’s more than just the public constructors.

See, the documentation of the valueOf methods, the one of Integer exemplary:

This method will always cache values in the range -128 to 127, inclusive, …

So when the factory method is used, you still get a specified identity behavior for some cases.

Which brings us to JLS §5.1.7:

If the value p being boxed is the result of evaluating a constant expression (§15.29) of type boolean, byte, char, short, int, or long, and the result is true, false, a character in the range '\u0000' to '\u007f' inclusive, or an integer in the range -128 to 127 inclusive, then let a and b be the results of any two boxing conversions of p. It is always the case that a == b.

So even the language specifies the behavior of certain identity sensitive operations.

Note that the specification tries not to name the valueOf method that the compiled code will use in practice, but to make up their own rules (which formally only apply to compile-time constants), which did not really pay off. As this answer documents, that part of the specification underwent several rewrites, so when anyone took the wording literally, the guarantees changed over time…

Other guarantees have burned into the developers’ minds much deeper:

JLS §15.29, Constant Expressions:

Constant expressions of type String are always "interned" so as to share unique instances, using the method String.intern.

This is a guaranty about object identity that is impossible to turn down.

Interestingly, JLS §15.18.1 states:

The String object is newly created (§12.5) unless the expression is a constant expression (§15.29).

It’s not clear whether this strict wording is intentional, but as written, it states that for non-constant string concatenation, it must produce a new object with a distinct identity. Yet another specified behavior that developers should not rely on.

So, if someone was to design a new language without legacies, there is nothing wrong with designing these types as value types in the first place. The designer just has to avoid to put all those guarantees into specification that were thought to be a good idea in the past.

The goal of value-based classes is to lay the foundation for true value types, i.e. types similar to C's struct.

Value types are not objects on the heap, so they are not created using new, and they are not derived from Object, so they don't have object monitors (locks).

That is why the following 2 rules are part of the definition for value-based:

  • make no use of identity-sensitive operations such as reference equality (==) between instances, identity hash code of instances, or synchronization on an instances's intrinsic lock;

  • do not have accessible constructors, but are instead instantiated through factory methods which make no commitment as to the identity of returned instances;

As commented by Brian Goetz on Jan 6, 2015:

Optional is new, and the disclaimers arrived on day 1. Integer, on the other hand, is probably hopelessly polluted, and I am sure that it would break gobs of important code if Integer ceased to be lockable (despite what we may think of such a practice.)


Reference: Value-Based Classes // nipafx

Related