javanullvalue-typeproject-valhalla

Can null be assigned to or be compared to a value type variable?


In Java, there have always existed primitive types and references, but not value types.

One of the goals of Project Valhalla is to explore and incubate feature candidates such as value types.

I would like to know if null can be assigned to a value type variable, or if a value type variable can be compared to null.

If not, I would also like to know why.


Solution

  • Yes!

    This changed during the long development of Project Valhalla.

    As of 2024, the value types proposal, JEP 401, which is probably now very close to the final form, has value types as nullable:

    Value class types are reference types. In Java, any code that operates on an object is really operating on a reference to that object; member accesses must resolve the reference to locate the object (throwing an exception in the case of a null reference). Value objects are no different in this respect.

    But why?

    It's not ideal that value types are nullable, because it adds an extra possible state to the type representation, which both makes it harder to reason about the state of your data and means that even when a value field is "flattened" into another object at runtime, it still needs an extra bit to store whether or not the "reference" is null. The VM work is truly complicated.

    The reason it ended up like this, in short, is compatibility. There are many existing classes which are morally value types, like Integer, Long, Double, LocalDate, Optional, any number of little immutable data carriers out there. But three decades of Java existing code will already do things like Integer value = null;. Making value types non-nullable would have prevented the migration of these classes. My understanding is that early attempts at non-nullable value types ("primitive classes") also played havoc with generics and required a new complicated set of bytecodes and type descriptors to cope with the new types.

    As it is, the model they've ended up with provides fantastic compatibility. Adding (or removing) the keyword value in front of a class or record is a mostly binary compatible change, meaning that code that uses the class can be compiled separately from the value class itself. You mostly don't need to know whether the class you're using is a value class or not. No new bytecodes, nor type descriptors, nor constant pool forms. Migration becomes easy. The proposed diffs to the language spec and VM spec are reassuringly small.

    So what is a value class?

    As best as I can explain it, a value class is a class whose instances are described purely by the values of their fields. Its fields are final, it has no synchronized lock/monitor (because that would need a hidden state variable), System.identityHashCode is computed based on its field values (because there is no hidden hash code field), and == compares instances of value classes by comparing the bit value of each field.

    (You can still have .equals and == that differ. For example, if you make a value class with String fields, you should provide a .equals method to compare the Strings with .equals, whereas == would merely check if the fields point at the same String instance.)

    The opposite of a value class is an identity class. An identity class is a classic Java class where each new instance lives somewhere distinct in memory, and == compares the memory address.

    The slogan of Project Valhalla is "Codes like a class, works like an int". For example, imagine:

    int a = 3, b = 3;
    

    We don't have to ask "is this 3 the same 3 as the other 3?" The question doesn't make sense. There is only the value 3. There is no individual identity to "each" 3. So of course a == b must compare them as equal. It can't compare identity (the way it would with new Integer) because an int primitive doesn't have identity, it only has value.

    So, given some value class:

    value record Point(int x, int y) {}
    

    it will be the case that:

    new Point(3, 4) == new Point(3, 4) // true!!
    

    Which is obviously absolutely impossible in the pre-Valhalla times. They are separately created instances, but totally indistinguishable. (You could pretend that all possible values of a value class pre-exist somewhere as singletons and new just gives the pointer to the cached instance.)

    But null is, yes, still a possible value of the type:

    Point pointless = null; // 👍
    

    In the language model, value types are still nullable reference types, just not identity types.

    What you get in exchange for giving up object identity is excellent performance. The early access releases warn not to use them for benchmarking, but I did, and I was ecstatic. When the VM knows it doesn't need to keep track of individual objects, it can aggressively shred them into naked fields passed around in CPU registers (plus a boolean for isItNull). It beats the hell out of escape analysis.

    I wholeheartedly encourage everyone to watch the July 2024 talk by Brian Goetz: https://www.youtube.com/watch?v=IF9l8fYfSnI

    Nullity control

    A closely-connected part of Project Valhalla is exploring null-restricted types, in which Java types (value or not) can be decorated with ! or ? to specify whether or not they are nullable, at the point of use. For example, a variable of type String! ("string bang") is a reference to a String and is enforced non-null. Obviously, this will marry well with value types. So types int and Integer! will have exactly the same possible values and become essentially the same thing: non-null pure 32-bit integer. Meanwhile, int? would mean "nullable int".

    The nullity control feature is frontier language development and still evolving.