How to avoid null checking in Java?

后端 未结 30 3276
失恋的感觉
失恋的感觉 2020-11-21 04:43

I use object != null a lot to avoid NullPointerException.

Is there a good alternative to this?

For example I often use:



        
相关标签:
30条回答
  • 2020-11-21 05:27

    This to me sounds like a reasonably common problem that junior to intermediate developers tend to face at some point: they either don't know or don't trust the contracts they are participating in and defensively overcheck for nulls. Additionally, when writing their own code, they tend to rely on returning nulls to indicate something thus requiring the caller to check for nulls.

    To put this another way, there are two instances where null checking comes up:

    1. Where null is a valid response in terms of the contract; and

    2. Where it isn't a valid response.

    (2) is easy. Either use assert statements (assertions) or allow failure (for example, NullPointerException). Assertions are a highly-underused Java feature that was added in 1.4. The syntax is:

    assert <condition>
    

    or

    assert <condition> : <object>
    

    where <condition> is a boolean expression and <object> is an object whose toString() method's output will be included in the error.

    An assert statement throws an Error (AssertionError) if the condition is not true. By default, Java ignores assertions. You can enable assertions by passing the option -ea to the JVM. You can enable and disable assertions for individual classes and packages. This means that you can validate code with the assertions while developing and testing, and disable them in a production environment, although my testing has shown next to no performance impact from assertions.

    Not using assertions in this case is OK because the code will just fail, which is what will happen if you use assertions. The only difference is that with assertions it might happen sooner, in a more-meaningful way and possibly with extra information, which may help you to figure out why it happened if you weren't expecting it.

    (1) is a little harder. If you have no control over the code you're calling then you're stuck. If null is a valid response, you have to check for it.

    If it's code that you do control, however (and this is often the case), then it's a different story. Avoid using nulls as a response. With methods that return collections, it's easy: return empty collections (or arrays) instead of nulls pretty much all the time.

    With non-collections it might be harder. Consider this as an example: if you have these interfaces:

    public interface Action {
      void doSomething();
    }
    
    public interface Parser {
      Action findAction(String userInput);
    }
    

    where Parser takes raw user input and finds something to do, perhaps if you're implementing a command line interface for something. Now you might make the contract that it returns null if there's no appropriate action. That leads the null checking you're talking about.

    An alternative solution is to never return null and instead use the Null Object pattern:

    public class MyParser implements Parser {
      private static Action DO_NOTHING = new Action() {
        public void doSomething() { /* do nothing */ }
      };
    
      public Action findAction(String userInput) {
        // ...
        if ( /* we can't find any actions */ ) {
          return DO_NOTHING;
        }
      }
    }
    

    Compare:

    Parser parser = ParserFactory.getParser();
    if (parser == null) {
      // now what?
      // this would be an example of where null isn't (or shouldn't be) a valid response
    }
    Action action = parser.findAction(someInput);
    if (action == null) {
      // do nothing
    } else {
      action.doSomething();
    }
    

    to

    ParserFactory.getParser().findAction(someInput).doSomething();
    

    which is a much better design because it leads to more concise code.

    That said, perhaps it is entirely appropriate for the findAction() method to throw an Exception with a meaningful error message -- especially in this case where you are relying on user input. It would be much better for the findAction method to throw an Exception than for the calling method to blow up with a simple NullPointerException with no explanation.

    try {
        ParserFactory.getParser().findAction(someInput).doSomething();
    } catch(ActionNotFoundException anfe) {
        userConsole.err(anfe.getMessage());
    }
    

    Or if you think the try/catch mechanism is too ugly, rather than Do Nothing your default action should provide feedback to the user.

    public Action findAction(final String userInput) {
        /* Code to return requested Action if found */
        return new Action() {
            public void doSomething() {
                userConsole.err("Action not found: " + userInput);
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-21 05:27

    Java 7 has a new java.util.Objects utility class on which there is a requireNonNull() method. All this does is throw a NullPointerException if its argument is null, but it cleans up the code a bit. Example:

    Objects.requireNonNull(someObject);
    someObject.doCalc();
    

    The method is most useful for checking just before an assignment in a constructor, where each use of it can save three lines of code:

    Parent(Child child) {
       if (child == null) {
          throw new NullPointerException("child");
       }
       this.child = child;
    }
    

    becomes

    Parent(Child child) {
       this.child = Objects.requireNonNull(child, "child");
    }
    
    0 讨论(0)
  • 2020-11-21 05:28

    In addition to using assert you can use the following:

    if (someobject == null) {
        // Handle null here then move on.
    }
    

    This is slightly better than:

    if (someobject != null) {
        .....
        .....
    
    
    
        .....
    }
    
    0 讨论(0)
  • 2020-11-21 05:29

    I'm a fan of "fail fast" code. Ask yourself - are you doing something useful in the case where the parameter is null? If you don't have a clear answer for what your code should do in that case... I.e. it should never be null in the first place, then ignore it and allow a NullPointerException to be thrown. The calling code will make just as much sense of an NPE as it would an IllegalArgumentException, but it'll be easier for the developer to debug and understand what went wrong if an NPE is thrown rather than your code attempting to execute some other unexpected contingency logic - which ultimately results in the application failing anyway.

    0 讨论(0)
  • 2020-11-21 05:29

    Null is not a 'problem'. It is an integral part of a complete modeling tool set. Software aims to model the complexity of the world and null bears its burden. Null indicates 'No data' or 'Unknown' in Java and the like. So it is appropriate to use nulls for these purposes. I don't prefer the 'Null object' pattern; I think it rise the 'who will guard the guardians' problem.
    If you ask me what is the name of my girlfriend I'll tell you that I have no girlfriend. In the Java language I'll return null. An alternative would be to throw meaningful exception to indicate some problem that can't be (or don't want to be) solved right there and delegate it somewhere higher in the stack to retry or report data access error to the user.

    1. For an 'unknown question' give 'unknown answer'. (Be null-safe where this is correct from business point of view) Checking arguments for null once inside a method before usage relieves multiple callers from checking them before a call.

      public Photo getPhotoOfThePerson(Person person) {
          if (person == null)
              return null;
          // Grabbing some resources or intensive calculation
          // using person object anyhow.
      }
      

      Previous leads to normal logic flow to get no photo of a non-existent girlfriend from my photo library.

      getPhotoOfThePerson(me.getGirlfriend())
      

      And it fits with new coming Java API (looking forward)

      getPhotoByName(me.getGirlfriend()?.getName())
      

      While it is rather 'normal business flow' not to find photo stored into the DB for some person, I used to use pairs like below for some other cases

      public static MyEnum parseMyEnum(String value); // throws IllegalArgumentException
      public static MyEnum parseMyEnumOrNull(String value);
      

      And don't loathe to type <alt> + <shift> + <j> (generate javadoc in Eclipse) and write three additional words for you public API. This will be more than enough for all but those who don't read documentation.

      /**
       * @return photo or null
       */
      

      or

      /**
       * @return photo, never null
       */
      
    2. This is rather theoretical case and in most cases you should prefer java null safe API (in case it will be released in another 10 years), but NullPointerException is subclass of an Exception. Thus it is a form of Throwable that indicates conditions that a reasonable application might want to catch (javadoc)! To use the first most advantage of exceptions and separate error-handling code from 'regular' code (according to creators of Java) it is appropriate, as for me, to catch NullPointerException.

      public Photo getGirlfriendPhoto() {
          try {
              return appContext.getPhotoDataSource().getPhotoByName(me.getGirlfriend().getName());
          } catch (NullPointerException e) {
              return null;
          }
      }
      

      Questions could arise:

      Q. What if getPhotoDataSource() returns null?
      A. It is up to business logic. If I fail to find a photo album I'll show you no photos. What if appContext is not initialized? This method's business logic puts up with this. If the same logic should be more strict then throwing an exception it is part of the business logic and explicit check for null should be used (case 3). The new Java Null-safe API fits better here to specify selectively what implies and what does not imply to be initialized to be fail-fast in case of programmer errors.

      Q. Redundant code could be executed and unnecessary resources could be grabbed.
      A. It could take place if getPhotoByName() would try to open a database connection, create PreparedStatement and use the person name as an SQL parameter at last. The approach for an unknown question gives an unknown answer (case 1) works here. Before grabbing resources the method should check parameters and return 'unknown' result if needed.

      Q. This approach has a performance penalty due to the try closure opening.
      A. Software should be easy to understand and modify firstly. Only after this, one could think about performance, and only if needed! and where needed! (source), and many others).

      PS. This approach will be as reasonable to use as the separate error-handling code from "regular" code principle is reasonable to use in some place. Consider the next example:

      public SomeValue calculateSomeValueUsingSophisticatedLogic(Predicate predicate) {
          try {
              Result1 result1 = performSomeCalculation(predicate);
              Result2 result2 = performSomeOtherCalculation(result1.getSomeProperty());
              Result3 result3 = performThirdCalculation(result2.getSomeProperty());
              Result4 result4 = performLastCalculation(result3.getSomeProperty());
              return result4.getSomeProperty();
          } catch (NullPointerException e) {
              return null;
          }
      }
      
      public SomeValue calculateSomeValueUsingSophisticatedLogic(Predicate predicate) {
          SomeValue result = null;
          if (predicate != null) {
              Result1 result1 = performSomeCalculation(predicate);
              if (result1 != null && result1.getSomeProperty() != null) {
                  Result2 result2 = performSomeOtherCalculation(result1.getSomeProperty());
                  if (result2 != null && result2.getSomeProperty() != null) {
                      Result3 result3 = performThirdCalculation(result2.getSomeProperty());
                      if (result3 != null && result3.getSomeProperty() != null) {
                          Result4 result4 = performLastCalculation(result3.getSomeProperty());
                          if (result4 != null) {
                              result = result4.getSomeProperty();
                          }
                      }
                  }
              }
          }
          return result;
      }
      

      PPS. For those fast to downvote (and not so fast to read documentation) I would like to say that I've never caught a null-pointer exception (NPE) in my life. But this possibility was intentionally designed by the Java creators because NPE is a subclass of Exception. We have a precedent in Java history when ThreadDeath is an Error not because it is actually an application error, but solely because it was not intended to be caught! How much NPE fits to be an Error than ThreadDeath! But it is not.

    3. Check for 'No data' only if business logic implies it.

      public void updatePersonPhoneNumber(Long personId, String phoneNumber) {
          if (personId == null)
              return;
          DataSource dataSource = appContext.getStuffDataSource();
          Person person = dataSource.getPersonById(personId);
          if (person != null) {
              person.setPhoneNumber(phoneNumber);
              dataSource.updatePerson(person);
          } else {
              Person = new Person(personId);
              person.setPhoneNumber(phoneNumber);
              dataSource.insertPerson(person);
          }
      }
      

      and

      public void updatePersonPhoneNumber(Long personId, String phoneNumber) {
          if (personId == null)
              return;
          DataSource dataSource = appContext.getStuffDataSource();
          Person person = dataSource.getPersonById(personId);
          if (person == null)
              throw new SomeReasonableUserException("What are you thinking about ???");
          person.setPhoneNumber(phoneNumber);
          dataSource.updatePerson(person);
      }
      

      If appContext or dataSource is not initialized unhandled runtime NullPointerException will kill current thread and will be processed by Thread.defaultUncaughtExceptionHandler (for you to define and use your favorite logger or other notification mechanizm). If not set, ThreadGroup#uncaughtException will print stacktrace to system err. One should monitor application error log and open Jira issue for each unhandled exception which in fact is application error. Programmer should fix bug somewhere in initialization stuff.

    0 讨论(0)
  • 2020-11-21 05:30

    If undefined values are not permitted:

    You might configure your IDE to warn you about potential null dereferencing. E.g. in Eclipse, see Preferences > Java > Compiler > Errors/Warnings/Null analysis.

    If undefined values are permitted:

    If you want to define a new API where undefined values make sense, use the Option Pattern (may be familiar from functional languages). It has the following advantages:

    • It is stated explicitly in the API whether an input or output exists or not.
    • The compiler forces you to handle the "undefined" case.
    • Option is a monad, so there is no need for verbose null checking, just use map/foreach/getOrElse or a similar combinator to safely use the value (example).

    Java 8 has a built-in Optional class (recommended); for earlier versions, there are library alternatives, for example Guava's Optional or FunctionalJava's Option. But like many functional-style patterns, using Option in Java (even 8) results in quite some boilerplate, which you can reduce using a less verbose JVM language, e.g. Scala or Xtend.

    If you have to deal with an API which might return nulls, you can't do much in Java. Xtend and Groovy have the Elvis operator ?: and the null-safe dereference operator ?., but note that this returns null in case of a null reference, so it just "defers" the proper handling of null.

    0 讨论(0)
提交回复
热议问题