[HN Gopher] What if null was an Object in Java?
       ___________________________________________________________________
        
       What if null was an Object in Java?
        
       Author : ingve
       Score  : 34 points
       Date   : 2024-04-27 08:29 UTC (1 days ago)
        
 (HTM) web link (donraab.medium.com)
 (TXT) w3m dump (donraab.medium.com)
        
       | mrkeen wrote:
       | I totally support the idea of null having type Null, but only if
       | null no longer has type Integer, and null no longer has type
       | Bufferedreader, etc.
        
       | randomopining wrote:
       | Doesn't an Optional basically cover this case
        
         | hedora wrote:
         | An Optional is just a tri-valued null (null, None, and Some),
         | so no.
         | 
         | 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.)
        
           | jflwyasdf wrote:
           | Fortunately, Uber made tooling for languages with broken type
           | systems
           | 
           | * https://github.com/uber/NullAway
           | 
           | * https://github.com/uber-go/nilaway
        
             | erik_seaberg wrote:
             | Lombok, Error Prone, and Kotlin also have their takes on
             | the problem.
        
           | durable_queue wrote:
           | null can be avoided with a good linter
        
             | kbolino wrote:
             | Not avoided altogether. Static checkers cannot possibly
             | follow all code paths, and they generally err on the side
             | of false negatives rather than risking too many false
             | positives causing people to disable them.
        
               | dexwiz wrote:
               | I wasn't aware they preferred type II errors. That makes
               | sense, but I don't really expect tools like that to work
               | across modules.
        
               | kbolino wrote:
               | It depends on the specific tool and how it's configured.
               | But that has been my experience with many tools
               | configured with their recommended settings.
        
           | kbolino wrote:
           | Assuming that was the only constructor you defined on class
           | Foo, and you used this.bar instead of foo.bar (latter won't
           | compile), then the caller can't possibly get a Foo with bar
           | set to null (except by reflection, and there are ways to
           | prevent that). Moreover, even if new Bar() did throw an
           | (unchecked) exception, the invariant would still hold, since
           | Foo would rethrow the exception. This has always been the
           | case, as far as I know.
        
             | hedora wrote:
             | Doing it requires two threads.
             | 
             | 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.
        
               | kbolino wrote:
               | I think you're right, if access to shared is not in any
               | way synchronized. But the correct way to handle this, at
               | least in this case, is to mark shared as volatile, which
               | guarantees thread B will only ever read null or a fully
               | constructed Foo from shared. This has been the case since
               | Java 5, released 20 years ago, thanks to JSR-133.
        
               | tsimionescu wrote:
               | By that standard, C and C++ are much worse, since they
               | offer no runtime encapsulation at all, and have much
               | worse and more subtle multithreaded errors (e.g. Java at
               | least guarantees that all native word sized reads/writes
               | are atomic, if I recall correctly). C++ doesn't even
               | guarantee that a reference can't be null, or worse,
               | deallocated before it is dereferenced. They allow you to
               | _specify_ that a field is of some type and _shouldn 't_
               | be null, which is nice, but they don't enforce that in
               | any way, they just call any code path that violates it
               | UB.
               | 
               | 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.
        
               | Maxatar wrote:
               | We can quibble on definitions here, but a reference in
               | C++ can not be null. The undefined behavior happens
               | before any assignment to a reference is executed so that
               | at the moment that the assignment happens, the reference
               | is guaranteed to not be null.
               | 
               | 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.
        
           | alkonaut wrote:
           | Wait, javas Optional is a reference type so it can be null?
           | Doesn't that almost defeat the purpose of it?
        
             | Defletter wrote:
             | Yup. Your IDE will likely highlight it as an issue, but
             | it's totally legal to return a null Optional. There's
             | nothing special about it, it's just a wrapper class.
        
               | alkonaut wrote:
               | Did the project to add value types to Java (I'm sure I
               | heard of it a decade ago) never finish?
        
               | Defletter wrote:
               | Not yet, that's Project Valhalla iirc. It's coming along
               | but hasn't been merged yet. I don't believe it's even a
               | preview feature within the JDK yet.
        
               | steve_rambo wrote:
               | https://openjdk.org/projects/valhalla
        
             | michaelt wrote:
             | Arguably yes, but that doesn't stop people using it.
             | 
             | 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.
        
             | paulddraper wrote:
             | Kinda.
             | 
             | 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.
        
             | pgwhalen wrote:
             | It doesn't defeat the problem in theory, but in my
             | experience it does in practice. I've never come across an
             | NPE on a nullable reference even in development - it would
             | have to be the result of a really fundamental
             | misunderstanding of the concept.
             | 
             | YMMV. Obviously it depends on your teammates.
        
             | mrkeen wrote:
             | It gives you the ability to treat nulls as something-to-
             | fix.
             | 
             | 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.
        
           | simpsond wrote:
           | Yeah, I wish the VM would prevent null assignment of optional
           | and force to empty. There are probably side effects I can't
           | think of here and certainly would cause problems with legacy
           | code misusing optionals.
        
           | sedro wrote:
           | > 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.
           | 
           | Java might be the only language where a simple assignment `x
           | = y` can throw a NullPointerException (due to auto-unboxing)
        
       | philipwhiuk wrote:
       | I don't actually think it solves any problems. You still have to
       | null check.
        
         | jflwyasdf wrote:
         | null would just mean the zero value instead of the absence of a
         | value                 String foo = null;            String bar
         | = "";            foo.equals(bar) --> true
         | 
         | This works well provided the data type has a sensible zero
         | value like collection types
         | 
         | EDIT: I'm blocked from posting so I won't be responding
         | further, thank you for discussion.
        
           | golergka wrote:
           | In the end, you'll have a mixture of NULL and "" in your DB,
           | and a couple of years later a piece of logic written in
           | another language will fail spectacularly.
        
             | jflwyasdf wrote:
             | This is how I would do it.                 Go: *string
             | Java: Option<String> or @Nullable String            Rust:
             | Option<String>            TypeScript: string | undefined
             | (or string | null)
        
               | golergka wrote:
               | The problem is, not all of these languages think that ""
               | and null are equal.
        
           | alkonaut wrote:
           | A null collection and an empty collection are two different
           | things. A nullable collection is one that has the state "no
           | collection" semantically separate from "empty collection".
           | 
           | Similarly an Option<byte> has 257 different values while a
           | byte has 256 different values. That the byte has a good zero
           | value doesn't change that - the whole reason for choosing a
           | maybe-byte is having 257 values, not 256.
        
             | jflwyasdf wrote:
             | Right that depends if you subscribe to the belief that null
             | means the absence of a value `Option<T>` or does it mean
             | the zero value `T`.
        
               | alkonaut wrote:
               | If null and [] should be the same thing then I'd make
               | absolutely sure you can't represent both. You don't want
               | two states representing the same thing. That should be
               | easy to ensure if a language is reasonable. E.g a field
               | that can't be null (best case a non-nullable type,
               | otherwise maybe a constructor guaranteeing it's never
               | null)
               | 
               | As the example of byte vs option<byte> either you want
               | 256 states or you want 257. If you have 256 or 257 states
               | you want to represent will decide which type is correct.
               | The other choice of type is _incorrect_.
               | 
               | In some languages, these things are blurred because the
               | language doesn't _let_ you choose a correct type for the
               | state space, but I'm talking about the case where you can
               | (coincidentally the set of languages I'd use).
        
               | coin wrote:
               | Null is the absence of a value. How do to distinguish 0
               | from no value?
        
               | Maxatar wrote:
               | The point is to eliminate the idea of an absence of a
               | value. A variable is always assigned to a value, but
               | there is a special value called null which behaves as a
               | kind of sentinel value whose methods all return the null
               | value for their respective type.
        
           | tsimionescu wrote:
           | That just gets us back to the problem for which Null is
           | introduced in almost every lamguage: indicating the absence
           | of a value. This is an important feature in every language,
           | and null is the most popular solution to it (the only
           | significant alternative is the Maybe monad).
           | 
           | To put this in more concrete terms, if this change were
           | integrated in Java, how would you indicate the difference
           | between a JSON document which doesn't contain a field VS one
           | where the field is an empty string?
        
         | Larrikin wrote:
         | Null should be valid.
         | 
         | Kotlin solved Java's problem by making it a compiler error if a
         | value that can be null isn't checked and shown to be null or
         | the actual value, eliminating an entire class of exceptions.
        
           | equalsione wrote:
           | I'm not familiar enough with kotlin to comment fully but from
           | your description the checker framework [0] appears to do the
           | same thing in Java.
           | 
           | I confess I'm not fond of checker framework. I find the error
           | messages can be obtuse but it is very effective.
           | 
           | 0 - https://checkerframework.org/
        
         | sebastianconcpt wrote:
         | It enables you to https://www.geeksforgeeks.org/proxy-design-
         | pattern/
        
       | recursive wrote:
       | If Null is not a subtype of MyType, then you wouldn't be able to
       | assign null to a variable decalred as MyType, without breaking
       | the rest of the rules of Java. I don't really see how this could
       | work, even theoretically.
        
         | Kamq wrote:
         | There's no reason that something can't both an object and the
         | bottom of the type hierarchy.
         | 
         | It's, technically, an instance of multiple inheritance. Java
         | doesn't generally allow this, but there's tons of special cases
         | in the compiler for things that you can't do yourself. For
         | example, defining operators is done in the compiler, but you
         | can't define operators for your own classes.
        
           | DaiPlusPlus wrote:
           | It would be a bottom-type of reference-types. This wouldn't
           | work for value-types like int, at least not without boxing,
           | which would be very painful.
        
       | m_fayer wrote:
       | I think the nullable reference type system in c# is a perfectly
       | workable compromise for languages that had nullability from the
       | very beginning. Once a code base uses them fully, most null-
       | related bugs are gone. And it's far from an unwieldy bolt-on.
        
         | aloisdg wrote:
         | The Nullable class is nice and I love to use it but it would be
         | even better with an option type like in F#
        
           | alkonaut wrote:
           | There isn't any big difference between T? And Option<T>
           | semantically. Many code bases use an Option<T> in C# to
           | indicate a 0-or-1 object result, but refactor those to T?
           | instead with nullable. It combines better e.g
           | Option<Option<T>> doesn't need to be handled manually.
        
             | ablob wrote:
             | I'm going to be a bit pedantic here: There is a semantic
             | difference between Option<Option<T>> and Option<T>. If I
             | intend to retrieve a setting from a file, the former allows
             | me to differentiate between a missing file or a missing
             | setting, while the latter destroys that information. i.e.:
             | There are 3 possible cases, while only 2 can be
             | represented.
             | 
             | So T? doesn't compose, while Option<T> does, which I'd
             | consider a big difference.
             | 
             | However, without a builtin option type, duck-typing, or a
             | pleasant way of converting the types, Option<T> may become
             | a hassle (especially when different dependencies ship their
             | own). And as T? is shipped with the language this is
             | probably why it is used when the composability is not
             | required.
             | 
             | P.S.: In C# T? even neatly composes.                 return
             | obj?.a?.b
             | 
             | is equivalent to                 if (obj != null && obj.a
             | != null) {         return obj.a.b       } else return null;
        
               | zigzag312 wrote:
               | > There is a semantic difference between
               | Option<Option<T>> and Option<T>. If I intend to retrieve
               | a setting from a file, the former allows me to
               | differentiate between a missing file or a missing setting
               | 
               | Does nesting Option's really has practical use or does it
               | quickly become confusing?
               | 
               | In your example, Option<Option<T>> return type doesn't
               | tell me by itself that this differentiates between a
               | missing file or a missing setting. I would need to get
               | this information from somewhere else.
        
               | evrimoztamur wrote:
               | That sounds like you should be using Result<T, E> to
               | handle the two E cases you are describing. Success with
               | Ok(T), with Err(Missing File) plus Err(Missing Setting).
        
           | Spivak wrote:
           | I think Option<T> is an overrated construct when it's not a
           | monad. The whole value is being able to write a chain of
           | operations                   myobj.dothing(x).otherthing(y)
           | 
           | Without having to check the intermediate results. When it's
           | just a tagged union you end up having to check for null
           | everywhere anyway. It's better than "untyped" null because it
           | can't pop up anywhere but it's not super ergonomic. I think
           | Nullable where the Option is implicit does it better.
        
         | jeroenhd wrote:
         | With a good IDE configured to break on nullability errors,
         | @Nonnull and @Nullable can also be used to replace the C#
         | system in practice (not the same, but close enough).
         | Unfortunately, this requires coordination from different team
         | members and external dependencies rather than standard language
         | features.
        
       | mbb70 wrote:
       | Ruby has a `NilClass` and the best/worst part of it is the to_s
       | is "", to_i is 0, to_f is 0.0, to_a is [], to_h is {}
       | 
       | It's incredibly clean and convenient until you wake up one
       | morning and have no idea what is happening in your code
        
         | falcor84 wrote:
         | Indeed. One's greatest strength is also their greatest
         | weakness.
        
         | Terr_ wrote:
         | > the to_s is "", to_i is 0, to_f is 0.0, to_a is [], to_h is
         | {}
         | 
         | I somehow can't help reading that as some sort of high school
         | sports-cheer: "Gimme an S to the quote to the I to the oh to
         | the F to the zero! Goooo Rubies!"
        
         | cmiller1 wrote:
         | Of course since it's Ruby you can just monkey patch those to_s
         | methods to do whatever the hell you want, confounding anyone
         | else working on your codebase.
         | 
         | I love using Ruby when I'm the only one who will ever have to
         | look at or touch it.
        
         | gls2ro wrote:
         | I dont think there is something wrong with that once you think
         | about what is a Null Element (or identity) in a group that is
         | represented by a set of elements and a function:
         | 
         | Integer, + => 0
         | 
         | Float, + => 0.0
         | 
         | Array, add => []
         | 
         | Hash, merge => {}
         | 
         | and so on.
         | 
         | I think maybe we can debate the operations/functions, but they
         | make sense. For Integer in some ways you can define almost all
         | other operations that you commonly use based on the addition.
         | 
         | So while nil is an object when trying to find a representation
         | in other group I find it logical or expected.
         | 
         | Also Ruby will not automatically try to coerce nil when not
         | asked to do so
         | 
         | like for example 0 + nil will throw an error.
        
       | xamde wrote:
       | There is also the "Void" type.
       | 
       | > The {@code Void} class is an uninstantiable placeholder class
       | to hold a reference to the {@code Class} object representing the
       | Java keyword void.
       | 
       | When Java introduced Generics they re-used "Void" type. Method
       | calls need to use "null" when "Void" is the type. So in a way,
       | "type of null" is "Void".
        
       | JanisErdmanis wrote:
       | Has anyone tried to make null as a function? In Julia, there is a
       | `get(null::Function, collection, key)` method which can be used
       | in a syntax:
       | 
       | value = get(collection, key) do                   error("$key in
       | the collection not found")
       | 
       | end
       | 
       | This syntax has captivated my attention lately as it seems like a
       | viable alternative to doing an explicit null check before using
       | the value.
        
       | docandrew wrote:
       | I proposed something similar for D a long time ago, I didn't
       | realize though that there was prior art there with Smalltalk!
        
       | krackers wrote:
       | Objective-c allows you to send messages to null objects. On one
       | hand it allows for a form of null-coalescing, but on the other it
       | allows bugs to slip in and get the program into an unexpected
       | state, whereas a more rigorous treatment would result in a more
       | deterministic crash.
        
       | downrightmike wrote:
       | What if it was a smart object and the system could figure out
       | what was supposed to happen and give you that back?
        
       | hgyjnbdet wrote:
       | I quite like the way dart handles nulls.
        
       | nitwit005 wrote:
       | It'd be fairly easy to modify Java so that the primitives,
       | including null, behave more like objects. And certainly, Java has
       | inched that direction.
       | 
       | It doesn't solve anything fundamental though. You could make
       | null.toString() return "null", but in most cases that will just
       | be a bug. You're missing a null check or failed to initialize
       | something.
        
       | specialist wrote:
       | What if every null reference was an instance of a Null Object
       | pattern? https://en.wikipedia.org/wiki/Null_object_pattern
       | 
       | Eliminates null checks, Optionals, and NPEs. Probably moots
       | annotations like @NotNull too, but maybe some use cases need
       | those for client APIs.
       | 
       | Makes iterating data structures like graphs simple. Faster too,
       | because the JIT NOP a Null Object's methods. (Mostly; profile to
       | confirm, then tweak as needed.)
       | 
       | I implement a Null Object for each base class.
       | 
       | Each base class has a static final member NULL referencing its
       | Null Object implementation (flyweight, singleton). Then assign
       | variables to AwesomeThing.NULL instead of null.
       | 
       | A spiffy javac could code generate Null Object implementations.
       | (It's on my todo list.) For scalars, just use the default null
       | value should be. int is 0, float is NaN, etc. Their boxed values
       | will need small shims too.
       | 
       | Customizing javac (with some compiler plugin or something) is
       | deep down on my TODO list. So I'm unlikely to be the (first)
       | person to do this work. Sorry.
        
       | yafetn wrote:
       | If null-safety on the JVM is important to you, just use Kotlin.
        
       | Joker_vD wrote:
       | We already have None in e.g. Python but that merely means that
       | "x.mathod_name()" instead of throwing a NullPointerException
       | raises an AttributeError, because None has no method
       | "method_name". Okay? Not really any meaningfully different.
        
       ___________________________________________________________________
       (page generated 2024-04-28 23:00 UTC)