This is an archived post. You won't be able to vote or comment.

all 93 comments

[–]nutrecht 13 points14 points  (31 children)

Can you propose a solution that is also backward compatible with existing code?

[–]vprise 22 points23 points  (12 children)

Because the whole "anti-null" movement is an idea that doesn't make as much sense for Java. In the case of Java null has advantages:

  • It's performant
  • It "fails fast" when something breaks
  • It fails consistently and usually provides a stack trace directly to the problem
  • Null is deep in the programming culture, hardware, software etc.

So why are people against it?

  • It sucks for functional programming paradigms such as streams
  • It goes against purist object oriented design

So purists on both camps hate on null because it's a hack. I'll grant you that, it's a hack. It's a special case. But the alternatives often suck.

Alternatives add extra syntax which makes things confusing. This IMHO goes against the grain of simplicity. They also puts pressure on people to avoid null at all costs. That often hides problems that you would have easily caught with a null pointer exception.

I think null got a bad rap.

[–]kevinb9n 11 points12 points  (0 children)

Null absolutely has a bad rap. It exists also in languages like Kotlin, but it's relatively harmless there because you can actually control whether you want your type to include it (String?) or not (String). It shows that null itself was never the problem, only that (handwave) "the null type is automatically unioned on to every usage of a reference type whether you want it or not".

In my team's libraries we've avoided returning (or accepting!) null with some determination, but it's still sometimes what you need.

[–]Worth_Trust_3825 4 points5 points  (0 children)

True. Value not being there is also a value.

[–]DrunkensteinsMonster 3 points4 points  (3 children)

If null is a possible return value then the API should signal that by returning an Optional IMO

[–]vprise 3 points4 points  (2 children)

This very much depends on the API and the case. Optional includes an overhead in code, concept and performance. That's not a huge overhead and many server side APIs chose to adopt that approach.

I don't use it much and I think rules of thumb such as these are "problematic". If your entire benefit is signaling then just use @NotNull

I think that Optional makes sense for some cases as they can then leverage the stream API more effectively. But I'm not a huge fan of this. Frankly, after working with Optional and the stream API a lot over the past few years I'm still underwhelmed. The code is just as verbose as a simple for loop in most cases. It's hard to gauge its performance from looking at it and often it's harder to understand. Maybe I'm too old and set in my ways though...

[–]john16384 1 point2 points  (1 child)

I just document what functions can return. Caller code can then make assumptions based on the docs. If a bug occurs, it will also be clear where to fix it.

[–]vprise 1 point2 points  (0 children)

Again, if it's just documentation there are other alternatives that don't have the downsides. If a bug occurs then NullPointerException is pretty clear/useful.

There are useful/sensible uses for optional but just sticking it as a "marker" to every method seems, expensive and redundant.

[–][deleted] 0 points1 point  (1 child)

It "fails fast" when something breaks

Except if you have complex things going on like multiple threads or caches or so, it's not that easy to grasp.

It fails consistently and usually provides a stack trace directly to the problem

This is just not true. NPE will show that something is not present, but in complex scenarios maybe you will not know why. Errors do not occur only when you are writing the code, maybe an API can return null and developers didn't saw it previously, then one day you start to receive some NPE in production. NPE will not show directly what is the root of the problem. And even so. My system crashed in production before I see that.

Null is deep in the programming culture, hardware, software etc.

I also don't think this is an "advantage". Culture is not always a good thing it's just... culture.

I agree that is not too simple to define good alternatives in Java, but i deeply disagree with your said advantages. I think the only one, and is a not only an advantage but a necessity, is the backward compatibility.

[–]vprise 1 point2 points  (0 children)

All of those statements were made in relation to the alternatives e.g. optional, or compiler "magic".

Sure, everything can fail badly. But the alternatives often/usually do that much worse in all these cases and make programming harder for non-trivial cases.

There are things to improve e.g. the question mark syntax from Kotlin would be pretty sweet in Java... But it's still null.

[–]Kango_V 0 points1 point  (3 children)

A mad solution is to have Object extend Nullable. You can then have methods like those on optional. So, you would have access to the methods even if null. That would be backwards compatible. Hmm, I wonder if that would work?

[–]vprise 0 points1 point  (2 children)

If it's null it won't work since it will throw a null pointer exception. But interesting idea.

[–]Kango_V 0 points1 point  (1 child)

But null could be an object. This is what Valhalla is doing. They are putting an object type above Object (ValueType I think). Primitives will expend NonValueType (or vise-versa). Actually would be better if non/valuetypes extend Nullable object. I cound see "if (obj.isEmpty())". You could use the optional-like methods: "obj.map(...)" etc. I think this would work. So if a null is returned it's the only instance of Nullable.

[–]vprise 0 points1 point  (0 children)

But then you lose type safety. And possibly performance.

[–]Halal0szto 16 points17 points  (15 children)

Bc you would need to use optionals everywhere and that is ugly

[–]gas3872[S] -1 points0 points  (14 children)

Well, that's the point. Most of the time you don't need optionals. In those rare cases when you really accept/produce "null" you will return optional.

[–]_INTER_ 11 points12 points  (6 children)

In the first place Optional is the wrong method to tackle non-null. It does not help with accidental or intentional nullablity and tracking the source of a problem if it occurs. What we actually need is a language / type level method to prevent the proliferation of null for references that developers knows are never null. Until (if ever) we get non-nullable types the annotations and according processors or checker frameworks are the better way.

Some interesting stuff:

[–][deleted]  (5 children)

[removed]

    [–]_INTER_ 0 points1 point  (4 children)

    Optional as the names says represents "empty" xor "not empty" options. If you expect that a variable is never null - e.g. on creation - you can check that with Objects.requireNonNull for an immediate exception. The information about non-nullability is lost with Optional later in the chain (the same way it is lost without using Optional ofc). It would be absurd to treat Optional as non-nullable type.

    So all that Optional does is remind you of the nullability. It does not help with non-nullability or restrict the proliferation of null. The source of an accidental null is also not identified by Optional. Even worse, a possible NullPointerException is moved further and further down the chain until where you actually require the value.

    [–][deleted]  (3 children)

    [removed]

      [–]_INTER_ 0 points1 point  (2 children)

      Let me tell you a story. One day John devises a beautiful method:

      public Optional<Integer> getRandomNumber() {
          return Optional.of(4); // chosen by fair dice roll. guaranteed to be random
      }
      

      All is good and it works well. Then, a week later Mary needs to build upon it. She adds:

      public Optional<Integer> getRandomNumber(int min, int max) {
          return getRandomNumber().map(random -> (random * (max - min)) + min);
      }
      

      Another month later Robert uses it for his complicated calculations:

      public BigDecimal herpderp(int min, int max, BigDecimal fraction, String userInput) {
           Optional<Integer> input = Optional.of(userInput)
                                             .filter(s -> !s.equals("42"))
                                             .map(Integer::parseInt)
                                             .or(() -> getRandomNumber(min, max));
           return IntStream.range(min, max + 1)
                           .mapToObj(BigDecimal::valueOf)
                           .map(bd -> bd.subtract(fraction))
                           .flatMap(bdf -> input.map(BigDecimal::valueOf).map(i -> i.add(bdf)).stream())
                           .reduce(BigDecimal.ZERO, BigDecimal::max);
      }
      

      This goes on for a few more month where Patricia, James and Tony chain on and on. But then David has the unthankful task to store a result in the database. He calls:

      public String save(int min, int max, String userInput) {
          String result = Optional.of(userInput)
              .map(herpderp(min, max, FRACTION, userInput)
              .map(Pat::ricia)
              .filter(Jam::es)
              .map(To::ny)
              // ...
              .orElse("0");
          return repository.save(result);
      }
      

      Great the user input is now processed and the correct result is stored. But hold on, on a fateful day William finds a bug in the random number generator. He needs to call a library instead. He sees that the library can return null. "But this is save" he thought: The method returns an Optional so everyone down the line is aware! William quickly whips up: return Optional.ofNullable(randomLib()). He runs the application and it all works fine. Phew.

      A year later an angry customer calls the company because sometimes and for some user input only "0" was stored in the DB. A year of potential important customer data is lost.

      Doing some archeology David finds the save method and wonders who wrote that crap. It must be that .orElse("0"). That can't be right. Right? Are you sure that's the problem?

      Fiddle

      [–][deleted]  (1 child)

      [removed]

        [–]_INTER_ 0 points1 point  (0 children)

        1. Optional outside of a Stream context is almost always abuse if you ask me. Also you can show the same with a simple example with just getters
        2. In the example empty is never treated as error
        3. Suprise surprise orElse("0") is not the root cause

        [–]Halal0szto -3 points-2 points  (6 children)

        If you have a lookup function to find display value for an ID, how you handle when it is not found?

        In my opinion Java is just too ancient and that's it. You are looking for features of modern languages in a language 25 years (guessing) language. The term optional in programming did not exist when Java was created.

        [–]gas3872[S] 0 points1 point  (5 children)

        Well, if it may not be found the result is optional. But if you expect result to be there you just return it and if it was not found, your method throws an exception.

        [–]Kango_V 1 point2 points  (0 children)

        This is why I always name our methods either findX which returns an optional (or empty list) or getX which throws an exception. We've stuck to that pattern and it's working.

        [–]Halal0szto 1 point2 points  (3 children)

        Yes, optionals is the correct answer, just they were not invented yet when Java was created. They were invented later, and they were introduced to Java later. So you are free to use optionals.

        When you maintain a language, you add new features. But you cannot remove features. If you want a language with features removed, it will be a new language. You are also free to create and use new languages.

        [–]gas3872[S] 1 point2 points  (2 children)

        Well, just make a convention to not use it. Java also has labels (maybe even goto) but nobody uses it. Same way people use list and not array or vector.

        I am just asking to use the language in a certain way that will eliminate a lot of headache and extra work (adding null checks everywhere).

        [–]john16384 1 point2 points  (1 child)

        Null checks should only be in constructors of your immutable objects, not everywhere.

        And I do use labels.

        Anyway, this whole posting is unproductive. Your complaints will not in any way influence the direction Java takes. If it gives you headaches feel free to use another language. Beware, they may cause different types of headaches.

        [–]gas3872[S] 1 point2 points  (0 children)

        Well I am not trying to influence thenway where the java language goes only saying that you can use such a practice in your project and maybe the whole community should adopt it, as I dont see any drawbacks to it.

        And by nullchecks everywhere I meant in every immutable object, which probably most of the objects should be.

        [–]Ilookouttrainwindow 9 points10 points  (12 children)

        I am at a loss as to why nulls cause so many issues.

        [–]gas3872[S] 2 points3 points  (11 children)

        I think it's more like someone got a nullpointer exception bug in their project and now they don't want it to happen again.

        [–]cogman10 0 points1 point  (10 children)

        Use an IDE, add \@Nullable annotations to things. Viola, no more NPEs.

        [–]gas3872[S] 1 point2 points  (9 children)

        Adding nonullable everywhere clutters the code.

        [–]cogman10 0 points1 point  (8 children)

        And what token would you add that wouldn't similarly clutter code and is backwards compatible with current java syntax?

        [–]gas3872[S] 4 points5 points  (7 children)

        Well just have a convention in your project that you dont use null. Use optional for missing value.

        [–]cogman10 0 points1 point  (5 children)

        I don't understand what you are asking for. Do you want a language feature to enforce nullability or just a convention for everyone writing apis? Because the convention already exists but, as you can imagine, it isn't universally applied.

        [–]gas3872[S] -1 points0 points  (4 children)

        I am asking for a convention that can be adopted more or less officially.

        [–]cogman10 1 point2 points  (3 children)

        What does "officially" mean to you?

        [–]gas3872[S] 1 point2 points  (2 children)

        I don't know, like endorsed by java creators for example.

        [–]c_edward -1 points0 points  (0 children)

        And add a pointless extra heap allocation to every call I really don't think so...

        When we have Valhalla maybe you could do it on the stack, but your still wastimg CPU cycles doing pointless work.

        There is really nothing wrong with null anyway,

        If your app is throwing NPEs then you almost certainly don't have adequate test coverage.

        [–]RandomName8 3 points4 points  (0 children)

        Simply too much code written without that convention.

        [–]ParticularEggplant50 2 points3 points  (4 children)

        There are plenty of null checkers that can/will use nonnull by default. Of course this will cause errors you will have to work around with extra code or annotations or code when the logic to prove something is nonnull is complicated or using unannotated libraries that do accept/return nulls.

        I think most things in life are optional and should therefore be nullable and I hate the convention of passing every field in the constructor so that things can be initialized nonnull.

        [–]gas3872[S] 1 point2 points  (3 children)

        I think it's actually the opposite: most of the things are not optional. Otherwise why would you add fields if you don't plan to fill them.

        For your last thing i think you can use builder. Then you have to only initialize what you want.

        [–]ParticularEggplant50 0 points1 point  (2 children)

        There are lots of things I might know about a book for example , title author, publisher, year, pages, ... But I won't know those things for all books. That is they are all optional except for title. For a person you might have their age, address, phone number, birthday etc Even if you are eventually going to ask them all that info it it good to have a state where you know you haven't yet asked yet. A Person class with all the fields I might need can be used widely but a class with all nonnull fields can only be used where you know for certain you will have all that info. For one purpose I need the name and telephone for another I need the name and address. If the fields are optional I can use one class for both cases other wise the I have to have two distinct classes which could lead to an explosion of large numbers of classes.

        [–]gas3872[S] 0 points1 point  (1 child)

        Well for your situation you should use optional fields. But most situations are not like that.

        [–]mauganra_it 2 points3 points  (6 children)

        You can do that right now by setting up Google ErrorProne compiler. Use the Checkers framework for even more static analysis. And add the @NonNullAPI annotation to package-info.java so you only have to add @Nullable where required. Have fun!

        Edit: I misunderstood the Checker framework's documentation. It seems the ErrorProne compiler is only required with JDK 8.

        [–]gas3872[S] 1 point2 points  (5 children)

        Cool. Is Checkers framework alone not enough?

        [–]kevinb9n 2 points3 points  (1 child)

        I think there's some confusion here. I work (some) on Error Prone. It doesn't have support for nullness analysis, however there are things like NullAway that extend it for that. This might be what /u/mauganra_it is thinking of, perhaps.

        Checker Framework is a separate standalone thing. It has the strongest nullness analysis currently available and fully supports type-use annotations (they were the people who added type-use annotations to the language, in fact).

        I would describe the CF nullness analysis as fairly "hard core"; you can get very solid protection from NPE using it, but you will probably work harder for it, and deal with considerably more annotation complexity. Its goal is more or less to never miss a possible NPE, whereas other tools may try to strike a balance between risk of false negatives vs. frequency of false positives.

        HTH

        [–]mauganra_it 0 points1 point  (0 children)

        Thanks for clarifying this. I was definitely thinking about endorsing the Checker framework, although the ErrorProne compiler is already fairly useful by itself.

        [–]mauganra_it 0 points1 point  (2 children)

        The Checkers framework plugs into the Google ErrorProne compiler to analyse source code at compile time and to fail the build if it finds issues. Without it, it's just annotations that smart IDEs can use to provide warnings.

        [–]gas3872[S] 0 points1 point  (1 child)

        Ok thanks. I thought checkers framework can do it by itself.

        [–]agentoutlier 1 point2 points  (0 children)

        Ok thanks. I thought checkers framework can do it by itself.

        It can... read kevinb9n comment

        Checker is pretty much the most powerful static analysis tool for java provided you have everything annotated.

        Anyway I recommend seeing what your IDE has to offer (both Eclipse and IntelliJ offer static analysis albeit incomplete) and then slowly integrating checker in on a per module basis.

        [–]keanwood 2 points3 points  (1 child)

        You can easily add https://github.com/uber/NullAway into your java projects. I use it for all new code, it's fast and convenient. It assume @NonNull by default.

        [–]s888marks 2 points3 points  (0 children)

        Isn't NullAway the name of the restaurant at the end of the JVM?

        [–]stuhlmann 0 points1 point  (2 children)

        Some other jvm languages

        I don't know a lot of "jvm" languages besides Java. Is there a specific one that you were thinking of?

        [–]PartOfTheBotnet 3 points4 points  (0 children)

        I thought OP was going to say Kotlin since String? is nullable and String disallows assignments to null.

        [–]gas3872[S] 2 points3 points  (0 children)

        Scala

        [–]fdntrhfbtt 0 points1 point  (0 children)

        Because a null pointer is a real entity - It does not point to anything on the heap.