10 ms·
What if null was an Object in Java?
- mrkeen 3y agoI 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 3y agoDoesn't an Optional basically cover this case
- hedora 3y agoAn 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 3y agoFortunately, Uber made tooling for languages with broken type systems * https://github.com/uber/NullAway https://github.com/uber/NullAway * https://github.com/uber-go/nilaway https://github.com/uber-go/nilaway
- erik_seaberg 3y agoLombok, Error Prone, and Kotlin also have their takes on the problem.
- durable_queue 3y agonull can be avoided with a good linter
- kbolino 3y agoNot 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 3y agoI 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 3y agoIt 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 3y agoAssuming 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 3y agoDoing 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 3y agoI 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 3y agoBy 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.
- alkonaut 3y agoWait, javas Optional is a reference type so it can be null? Doesn’t that almost defeat the purpose of it?
- Defletter 3y agoYup. 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 3y agoDid the project to add value types to Java (I’m sure I heard of it a decade ago) never finish?
- Defletter 3y agoNot 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 3y agohttps://openjdk.org/projects/valhalla https://openjdk.org/projects/valhalla
- michaelt 3y agoArguably 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 3y agoKinda. 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.
- simpsond 3y agoYeah, 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 3y ago> 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 3y agoI don't actually think it solves any problems. You still have to null check.
- jflwyasdf 3y agonull 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 3y agoIn 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.
- recursive 3y agoIf 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 3y agoThere'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 3y agoIt 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.
- Kamq 3y agoFair, but those are kind of off in their own in the java hierarchy as is.
- chopin 3y agoBut an int can't be null. If a method takes or returns an int it's guaranteed to be not null.
- DaiPlusPlus 3y ago…yes; that’s my point.
- codebje 3y agoIt's the other way around: null needs to be a subtype of MyType, not a supertype: anywhere I can pass a MyType I should be able to pass a null. You could get most of the way to the blog post's outcomes by simply adding a few methods to Object (is(Not)Null, primarily) and having null be a magic (because it's all classes and none) instance that will NPE on all but the few defined Object methods, I think, but none of that answers the burning question of _why_ that I feel the article doesn't really address.
- m_fayer 3y agoI 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 3y agoThe Nullable class is nice and I love to use it but it would be even better with an option type like in F#
- alkonaut 3y agoThere 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 3y agoI'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 3y ago> 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.
- mbb70 3y agoRuby 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 3y agoIndeed. One's greatest strength is also their greatest weakness.
- Terr_ 3y ago> 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 3y agoOf 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 3y agoI 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.
- eru 3y agoIntegers support both addition and multiplication and taking maximum and minimums, and a few other semi-group operations. Do you want to define different Null elements for all of them?
- xamde 3y agoThere 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 3y agoHas 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.
- masklinn 3y agoThat’s just a default callback. Actually using this instead of nulls in any sort of general case would be using CPS for all faillible APIs, and that sounds heinous.
- docandrew 3y agoI proposed something similar for D a long time ago, I didn’t realize though that there was prior art there with Smalltalk!
- krackers 3y agoObjective-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.
- ipv6ipv4 3y agoIt is part of what makes Objective-C phenomenally good for rapid development in the hands of competent developers. However, as team, and codebase sizes increase, it allows for subtle bugs by less competent developers.
- masklinn 3y agoIt’s also very much related to its use in GUI hierarchies: GUI objects generally have a lot of properties and behaviours, most of which you often don’t care for at any given point. Nil being a shorthand for “do nothing” without needing explicit checking is very convenient.
- downrightmike 3y agoWhat if it was a smart object and the system could figure out what was supposed to happen and give you that back?
- hgyjnbdet 3y agoI quite like the way dart handles nulls.
- hi-v-rocknroll 3y agoThe way Crystal and Dart 2 handles it? Null as a separate and unique type unrelated to Object where type unions solve the problem using syntax like `String?` that is equal to `String | Null`.
- hgyjnbdet 3y agoYeah, means I don't need to worry about null unless I explicitly allow null.
- nitwit005 3y agoIt'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 3y agoWhat if every null reference was an instance of a Null Object pattern? https://en.wikipedia.org/wiki/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.
- int_19h 3y agoNot everything has a sensible null object. It also means that forgetting to set a value will often silently give wrong results instead of immediately producing an error.
- specialist 3y agoApologies, I omitted part of my proposal: While most Null Objects would (probably) be code generated, you can provide your own as needed. Said another way: Most of my Null Object implementations have been boilerplate, and therefore could be code generated. But occasionally some are not.
- int_19h 3y agoWhat I mean is that semantically there's often no such thing as a null object for a given domain. So any artificial one that can be made is indistinguishable from null in practice (i.e. all operations on it will throw etc).
- gilgoomesh 3y ago
- yafetn 3y agoIf null-safety on the JVM is important to you, just use Kotlin.
- MrDresden 3y agoJust use Kotlin. I'm honestly not seeing any need nor benefit in writing Java anymore.
- MrBuddyCasino 3y agoI‘d say its the other way around, Java is closing the gap, and I say that as a Kotlin fan. Nullability in the type system is the big remaining advantage.
- usrusr 3y agoScope functions are still huge. Yes, deep let chains can certainly be considered an antipattern (sometimes I like the approach of writing them, then transforming to more imperative for readability, I think readability peaks at a mix of imperative with some shallow .let), but I miss them in any language that is not kotlin. Variables should be the exception, not the norm. I have no patience for names that designate some fluid work in progress instead of a value.
- yafetn 3y ago> Scope functions are still huge. I’d put any one of extension methods, value and data classes, immutable variables, structured concurrency, and top-level functions ahead of scope functions for reasons to switch to Kotlin. But hey, if you’re switching, we’re already friends :) .
- usrusr 3y agoReally, top level functions? I fail to see their impact, outside of getting rid of ceremony around trivial code examples. You missed optional and named arguments in the list, those really change cost/benefit decisions in API design. (and, unfortunately, make argument names part of the public interface, this must be the most controversial part in all of kotlin) I singled out scope functions because all those other things feel very much like the usual set of language differences, whereas scope functions feel completely orthogonal. They could be added to every single language that has excursions and imperative elements, and they would be about the same improvement everywhere.
- Joker_vD 3y agoWe 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.
- eru 3y agoPython isn't exactly statically typed.
- sgammon 3y agoKotlin can do this -- `(null as String?).isNullOrBlank()`. I really like this feature because it prevents nullability cascade. I'd love to see pure Java adopt and I feel like Kotlin's edge language development has really helped the greater Java ecosystem (records + `data class`, exclusive `when`, so many good examples).
- DonHopkins 3y agoWould a prototype based system like Self or JavaScript allow you to attach custom properties and methods to individual instances of Null? That would be so cool! Nihilistic Oriented Programming is where you don't use any classes, just customized instances of null.
- exabrial 3y agoI like the way null is handled in Java, I think it’s pretty well thought out, other than the small awkwardness with basic types. Contrast that with SQL, which had my external scorn for null != null So you can do WHERE table.col = Null…. Ugh
- dehrmann 3y ago> I like the way null is handled in Java, I think it’s pretty well thought out, other than the small awkwardness with basic types. It's all inherited from C. Null works the same, references are just pointers, and primitives have to be boxed since collections store references. Not sure if I'd say it's well thought out, but coming from C, all the weirdness makes sense.
- jraph 3y ago> Contrast that with SQL, which had my external scorn for null != null For anyone not familiar, or who doesn't know the reason null behave like this in SQL: in this word, null essentially means "don't know". So you can't assume a = b when a and b are null. They could be different values, just that we don't have them. This thing being called null, where null has another semantic in the languages from which we send SQL request is confusing, and I suspect many of us would prefer null behave the same way as everywhere else.
- Akronymus 3y agoSQL is one of the very few places where I deem null to be valid (altough, you could normalize all nulls away, most of the time it isn't worth it) The null != null thing is something I quite like, as it simplifies a lot of joins/queries. and as jraph said, null in relational databases has a very different meaning than null in most programming languages. Don't know vs not assigned
- deleted 3y ago[deleted]
- lisper 3y agoAnother option in the language design space which this article doesn't mention is to have different kinds of null indicating what kind of object this null value is intended to be a stand-in for. NULL is a generic non-value, but it's also possible to have null-number, null-string, null-character, etc. which can be handy for detecting certain kinds of type errors. In particular, a constructor for a class C can return a null-C if it fails, which can be very useful for debugging. One of the problems with generic NULLs is that it can be very hard to track down where they came from.
- borland 3y agoThe author is now discovering Ruby and Objective-C :-)
- hn_throwaway_99 3y agoThe problem isn't really that null isn't an object, it's that the type system allows any object reference of a particular class to also be null. After using Typescript for the past couple years, it's such a joy when you define a variable of type Foo and know it won't contain null. Granted, TS also has to deal with undefined vs null weirdness (and even more weirdness in that an object not containing a property is subtly different from that object property being undefined), but in general the support for "optional" typing works very well. Java tried to add things like the Optional class, but without first-class language support it's just a mess and you have developers doing crazy things, like having a method return an Optional that will sometimes also return null, by design (yes I've actually seen this).
- deleted 3y ago[deleted]
- nextaccountic 3y agoSeeing all this discussion reinforces the idea that no new programming language should have this "every type implicitly contains null" thing
- eru 3y agoYes, sum types and generics solve this nicely. It's a shame Java was designed before they had generics, so the core of the language can't take advantage of them.
- ruszki 3y agoIt’s still possible to do something like TypeScript, where everything is like generics in Java. But Java try to be consistent, while TypeScript purposefully not. There are a ton of features in TypeScript which are awesome, but it breaks in some rare cases. Java won’t allow that.
- ruszki 3y agoAlso no new languages should default to mutable variables, fields, and types, when the language supposed to be in the same field as Java, eg. large enterprise services. And for both problems, they should be consistent, because now they are mess (there is an example in the article regarding nulls in collections, and for mutability a great example is collect(Collectors.toList()) vs toList()).
- akira2501 3y agoI'd like a null on the same order as NaN. You can use it, compute new values with it, call methods on it, but all results are a similar NaV (not-a-value?).
- thfuran 3y agoThat sounds horrible. Throwing an npe is almost certainly better than just blithely continuing to process broken state.
- chuckadams 3y agoYou’d love objective C: all sends to a nil ref return nil.
- Cloudef 3y agoNSNull says hi
- gumby 3y agoIt would be fun to subclass null, and make multiple instances of it. I did hack MACLISP to have multiple NILs once. A surprising number of things appeared to run OK for a while. Of course the PDP-10 had a hardware addressing mode just for NIL so if you weren't the official one compiled code didn't believe you were a legit NIL. Making another T was more boring, except that the few bugs that were triggered seemed to appear more quickly than in the NIL case. I guess a lot more people explicitly compared with 'T. Eh, grad students, what do you expect? As to why I did this, well it was decades ago but surely a case of work avoidance.
- p0w3n3d 3y agoJava developers introduced Optional for this very case, but Optional's API enforces stream style constructions. Moreover you must handle nulls sometimes (foreign API calls), and then you might run into type checking problems, NPE when put into a Map.of() etc. For me the given examples from Smalltalk are more appealing. I would trade null for nil, or at least have some nullsafe construction like .? in swift
- szastamasta 3y agoThe only real problem with null, is not having a real optional type in Java. If you have it, 99,999% of null errors go away. Java Optional std lib class, instead of a language (typesystem level) feature was one of biggest mistakes in its recent history.
- MrBuddyCasino 3y agoAt least they plan on introducing null safety for value objects via the „Null-Restricted Value Class Types“ JEP: https://openjdk.org/jeps/8316779 https://openjdk.org/jeps/8316779
- jillesvangurp 3y agoKotlin makes nullability part of the typesystem. It's a good reference for what would happen if Java did this better because if you are using Java you can just switch to Kotlin and experience this in your own code base (you can mix Java and Kotlin code easily). You can actually write extension functions for nullable types and calling them on a null value does not cause a Nullpointer exception. This also works with Java classes. For example, there's a CharSequence?.isNullOrBlank(): Boolean function in the Kotlin standard library that allows you to implement a null or blank check on strings. There's a similar isNullOrEmpty function on Lists (and yes, this does work for nullable generic types too). There are many good reasons to switch from Java to Kotlin. But this is probably one of the bigger ones and genuinely low hanging fruit. Made me feel stupid after years of dealing with Java NPEs routinely and writing a lot of very verbose and needlessly defensive Java code in attempts to avoid them. All that cruft goes away with a proper language. Other languages that do this too are available of course. Pick one. Any one that isn't Java. Dealing with this stuff in 2024 is stupid.
- fyve 3y agoCharSequence?.isNullOrBlank() In java I can make my own static function isNullOrBlank(String input) Which works on nulls too, right? It's annoying to have to reference another class - but otherwise it's not much less ergonomic.
- usrusr 3y agoYou can, you can even call the kotlin version that way if it happens to be on the classpath, but it's truly horrible ergonomics if for every call you have to guess between class-ref-otherargs and ref-otherargs forms. It's one of those little cuts you grow numb to.
- severino 3y agoCharSequence is the base class that things like java.lang.String inherit. So I understood that as an instance method you can call like "foo".isNullOrBlank() (in Kotlin syntax)
- usrusr 3y ago
- he0001 3y agoIs a NullPointerException so much worse than a NoSuchElementException? Is there a difference?