It'd be nice if Java had a concept of a never-null reference (like a C++ reference vs. a C++ pointer), but the @NotNull annotation wasn't enforced the last time I checked.
Also, there's no way for an object to express that invariant because encapsulation is so weak. Given only this constructor (and no reflection):
Foo() { foo.bar = new Bar(); /* foo.bar is final; Bar() does not throw */ }
callers can still get an instance of Foo with bar set to null.Anyway, null handling in java somehow manages to be worse than C, where you can at least inline a struct into another, statically guaranteeing the instance of the inlined struct exists.
I can't think of another statically typed language that screws this up so badly. It just keeps getting worse with stuff like Optional and @NotNull.
(Disclaimer: I haven't followed java for 4-5 years; it's possible they finally fixed this stuff.)
Thread A sets a shared reference to a newly allocated and null initialized reference to Foo:
shared = new Foo();
While that's running, thread B invokes a method on the reference that assumes bar is non-null: shared.useBar(); // null pointer exception
Later, thread A runs the constructor for Foo.For example, this is code that any C or C++ compiler will happily run and do something:
struct Bar {
int b;
};
struct Foo {
struct Bar bar;
} foo;
strcpy((char*)(&foo), "ABC");
Or in relation to null C++ references: int& foo(int* p) {
return *p;
}
int &r = foo(nullptr); //UB, but in practice will likely result in a null reference at runtime
Similarly, accessing an object from multiple threads without synchronization means its value is not fully defined in Java. Unlike C or C++, it is at least known to be a Java type, not a memory corruption vulnerability.In your example, it's the dereference of the pointer to p that is undefined behavior, so anything that happens after that point is also undefined behavior. Note that means there is never an actual assignment to r if p is null.
As I mentioned earlier, this might seem like quibbling with definitions, but this is the proper mental model to have with respect to C++'s semantics.
Having said that, I don't disagree with the main crux of your point, which is that C++'s semantics are terrible and there is little that the language provides to write correct code, but I do think there are subtleties on this matter that are worth clarifying.
My point is that we can compare two things: valid programs, or programs that compile.
In valid C++ programs, references can't be null and there are no data races. In valid Java programs, all final fields initialized in an object's constructor have that value for the lifetime of the object.
If we compare invalid programs that compile, which is an important point as well, then those guarantees go out the window. But here Java is much more forgiving than C++: if you have improper synchronization, you may see fields which are null instead of having their final value, which is bad and confusing. But in C++ with improper synchronization, you can see literally any outcome at all.
https://docs.oracle.com/javase/specs/jls/se21/html/jls-17.ht...
>An object is considered to be completely initialized when its constructor finishes. A thread that can only see a reference to an object after that object has been completely initialized is guaranteed to see the correctly initialized values for that object's final fields.
If you don't publish a reference to the object from within the constructor, you will not see a null value of the final field, even if the object itself was unsafely published across threads via a non-volatile field.
https://godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(filename...
Basically Java had nulls from the start. A decade or so later some people who didn't like nulls introduced their own Optional type, as a third-party library. Enough people liked it that Optional was added to Java's standard library.
But as it's just an object, it can be null. Some null avoidance enthusiasts also use third-party @Nullable and @NotNull annotations, which some automated code checking tools will attempt to verify during compile/test.
Every non-primitive is nullable in Java. Adding Optional doesn't/can't change that.
You can have a gentlemen's agreement to prefer None to null.
YMMV. Obviously it depends on your teammates.
In a team without Optionals, every time you touch a null that you didn't expect, you have to decide "Is this deliberately null, or was it a mistake?" Without that knowledge, you don't know whether your code should assert against the null, or allow it to pass through as a valid value.
With Optionals, it becomes much simpler to cut through that nonsense. A null is a bug and you fix it (with the exception of json at the boundaries of your system, etc.) If you do find a value where you change your mind about its nullability, changing it to/from Optional will give you compile errors in exactly those other parts of the code that you now have to check/change.
Java might be the only language where a simple assignment `x = y` can throw a NullPointerException (due to auto-unboxing)