Good technical overview, and I fully agree with the conclusion's sentiment at the end:
```
Declaring a value class is first and foremost a semantic decision. It tells our fellow programmers that its instances are defined entirely by their state and do not need identity. That clearer model is valuable in itself! The JVM’s additional freedom to optimize how those values are represented is a welcome bonus.
```
Many developers seem to think that "go value go broom", but the truth is much more nuanced and the idea should not be to think about "but performance" but to think about the nature of your underlying data. At the very least this integrates some core DDD lessons directly into language. I'm glad tearing isn't turned on by default exactly for this reason.
Java was always a language that geared itself towards making libraries easy to use, putting much faith in the library author and strong encapsulation. Now, experts can gain significantly more performance from the JVM, while more humble programmers are avoided from creating bugs they will not expect.
Since being created, the Java pitch was "don't worry about low-level stuff like value/reference classes, a Sufficiently Smart Compiler will automatically pick the best option given your code and runtime profiling".
What changed, why suddenly they adopt C++ features they explicitly excluded?
Java is so dynamic that it's impossible to prove a class will only be used in "value friendly" ways. When objects have no identity the meaning of `==` is different, and the compiler would have to do some kind of whole program analysis to find out if there is any way an instance of the class could ever be checked for equality.
That's hard when you have type erasure and polymorphism and runtime class loading. And even if they pulled it off it would blow up compile times and be programmer unfriendly because adding one line could deoptimize an important class defined in another package. So they decided to bite the bullet and add a way to statically opt in for faster but incompatible behavior.
This is really interesting. I've worked and lived alongside Java since, like, becoming extremely proficient in AS3/ECMA5(ish) and understanding the low-level quirks of that VM, but I never dealt in Java.
What's funny to me is that by your description, AS3 started almost as dynamic as the objective mess you're describing, and somewhat correctly headed down a path of compile-time type safety along its trajectory, which kind of gave good guardrails for those of us who had to switch to a wild west nonsense of JavaScript tempered with some hints from typescript.
I wasn't aware that you could `==` two objects in Java and that it would, like, deconstruct them somehow and see if their contents matched rather than just telling you whether they referenced the same object. I'm not even sure if that is what you're saying, because that's wild and insane and it's the whole reason for observable classes in other languages (which are sort of hackish). But after so many years of like, figuring out the quickest ways to diff similar objects, relying on the VM to do it would feel like never the best solution to any given problem, and more of a footgun than a feature...?
`==` is reference identity in java and that is the problem. so you can't treat an arbitrary object as a value type because somewhere in the program `==` might be called on it.
> don't worry about low-level stuff like value/reference classes
Part of the issue is that this was never low-level stuff. When you're selecting between the two you're making a semantic choice, and that's not something a compiler can do for you.
>What changed, why suddenly they adopt C++ features they explicitly excluded?
Project Valhalla, which includes the effort to add value types was announced in 2014. They've been working on it for a while.
As for "what changed" ...
Back in 1990s when Java was conceived, there was an idea that CPUs in desktops had plenty of extra cycles that were being wasted and could therefore be used to reduce mental load on developers. It was the same "cpus are cheaper than developers" idea that is repeated today with "tokens/cpu are cheaper than developers". With that philosophy, James Gosling talked about Java's "everything-is-an-object" as a mental simplification for developers. All the extra indirections of pointer-chasing to box unbox primitives and/or iterate through arrays of objects wasn't seen as a penalty (again, "CPUs are cheap; devs are expensive").
Now having value types that are contiguous in RAM is a big deal for performance. Avoid a bunch of pointer chasing. Even C++ best practices were affected. E.g. the traditional tradeoffs you learned from from classroom textbooks of linked-lists being faster than arrays for middle-of-list insertions was no longer always correct in the new world where CPUs are caching adjacent RAM areas to try to reduce the memory wall issue. So O(n) could be faster than O(log n) depending on the size of the data structure and interactions with RAM pre-fetch, etc.
Even a smart compiler can't break the program semantics, and without a closed world assumption it simply can't assume that an entirely different part of the program doesn't expect to observe object identity for a type.
Java is adding small, orthogonal features that amount to the same feature set as value types in other languages, but can be cherry-picked into existing code for partial advantages without significant changes. These value classes are still nullable, lack a guaranteed layout, and can't be observed torn, unlike in C++/C#/Go.
Yes, this was the idiom in the 90s, but Escape Analysis has not proven to be powerful enough to optimize away identity.
And looking at all the edge-cases and possible data-races via tearing, declaring something as a value must be an explicit design decision that cannot be inferred by a compiler or optimizer alone.
It was not at all “suddenly”. The discussions about explicitly defining value types because escape analysis is insufficient have been going on for at least a decade.
``` Declaring a value class is first and foremost a semantic decision. It tells our fellow programmers that its instances are defined entirely by their state and do not need identity. That clearer model is valuable in itself! The JVM’s additional freedom to optimize how those values are represented is a welcome bonus. ```
Many developers seem to think that "go value go broom", but the truth is much more nuanced and the idea should not be to think about "but performance" but to think about the nature of your underlying data. At the very least this integrates some core DDD lessons directly into language. I'm glad tearing isn't turned on by default exactly for this reason.
Java was always a language that geared itself towards making libraries easy to use, putting much faith in the library author and strong encapsulation. Now, experts can gain significantly more performance from the JVM, while more humble programmers are avoided from creating bugs they will not expect.
What changed, why suddenly they adopt C++ features they explicitly excluded?
https://wiki.c2.com/?SufficientlySmartCompiler
That's hard when you have type erasure and polymorphism and runtime class loading. And even if they pulled it off it would blow up compile times and be programmer unfriendly because adding one line could deoptimize an important class defined in another package. So they decided to bite the bullet and add a way to statically opt in for faster but incompatible behavior.
What's funny to me is that by your description, AS3 started almost as dynamic as the objective mess you're describing, and somewhat correctly headed down a path of compile-time type safety along its trajectory, which kind of gave good guardrails for those of us who had to switch to a wild west nonsense of JavaScript tempered with some hints from typescript.
I wasn't aware that you could `==` two objects in Java and that it would, like, deconstruct them somehow and see if their contents matched rather than just telling you whether they referenced the same object. I'm not even sure if that is what you're saying, because that's wild and insane and it's the whole reason for observable classes in other languages (which are sort of hackish). But after so many years of like, figuring out the quickest ways to diff similar objects, relying on the VM to do it would feel like never the best solution to any given problem, and more of a footgun than a feature...?
Part of the issue is that this was never low-level stuff. When you're selecting between the two you're making a semantic choice, and that's not something a compiler can do for you.
Project Valhalla, which includes the effort to add value types was announced in 2014. They've been working on it for a while.
As for "what changed" ...
Back in 1990s when Java was conceived, there was an idea that CPUs in desktops had plenty of extra cycles that were being wasted and could therefore be used to reduce mental load on developers. It was the same "cpus are cheaper than developers" idea that is repeated today with "tokens/cpu are cheaper than developers". With that philosophy, James Gosling talked about Java's "everything-is-an-object" as a mental simplification for developers. All the extra indirections of pointer-chasing to box unbox primitives and/or iterate through arrays of objects wasn't seen as a penalty (again, "CPUs are cheap; devs are expensive").
However, the later evolution in 2000s of CPU hardware vs RAM hardware changed that performance tradeoff thesis: https://en.wikipedia.org/wiki/Random-access_memory#Memory_wa...
Now having value types that are contiguous in RAM is a big deal for performance. Avoid a bunch of pointer chasing. Even C++ best practices were affected. E.g. the traditional tradeoffs you learned from from classroom textbooks of linked-lists being faster than arrays for middle-of-list insertions was no longer always correct in the new world where CPUs are caching adjacent RAM areas to try to reduce the memory wall issue. So O(n) could be faster than O(log n) depending on the size of the data structure and interactions with RAM pre-fetch, etc.
This has always been the case. The RAM effects only changed at which point the O(n) stops being faster than the O(log n) solution.
Java is adding small, orthogonal features that amount to the same feature set as value types in other languages, but can be cherry-picked into existing code for partial advantages without significant changes. These value classes are still nullable, lack a guaranteed layout, and can't be observed torn, unlike in C++/C#/Go.
And looking at all the edge-cases and possible data-races via tearing, declaring something as a value must be an explicit design decision that cannot be inferred by a compiler or optimizer alone.