My experience with platform design has consistently been that handling version evolution in the presence of distant teams increases complexity by 10x, and it's not just about some mechanical notion of backwards compatibility. It's a particular constraint for Java because it supports separate compilation. This enables extremely fast edit/run cycles because you only have to recompile a minimal set of files, and means that downloading+installing a new library into your project can be done in a few seconds, but means you have to handle the case of a program in which different files were compiled at different times against different versions of each other.
[1] https://github.com/titzer/virgil/blob/master/lib/util/Map.v3
This is partly my style too; I try to avoid using maps for things unless they are really far flung, and the things that end up serving as keys in one place usually end up serving as keys in lots of other places too.
Java doesnt make this very composable
Rust’s approach to the Hash and Eq problem is to make them opt-in but provide a derive attribute that autoimplements them with minimal boilerplate for most types.
Also, Rust’s Hash::hash implementations don’t actually hash anything themselves, they just pass the relevant parts of the object to a Hasher passed as a parameter. This way types aren’t stuck with just a single hash implementation, and normal programmers don’t need to worry about primes and modular arithmetic.
Fully separating implementation of an interface from the data can create quite a lot of additional complexities. See my comment elsewhere about encapsulation and version stability.