Why did guava/java use possible.isPresent() as opp

2019-07-21 06:33发布

问题:

On https://code.google.com/p/guava-libraries/wiki/UsingAndAvoidingNullExplained it is explained that guava (and later java 8) adds a generic class Optional in order to clear up null checking.

If a function returns an Optional it requires the caller to unwrap the string before using it.

this is normally done in the form

Optional<String> possible = returnAnAbsentOptional();
if(possible.isPresent()){
    System.out.println(possible.get())
}

If returnAnAbsentOptional returns null, we have a NPE all over again.

My question is, why did Guava/Java use possible.isPresent() as opposed to Optional.isPresent(possible) which could react to the null value accordingly?

回答1:

Because the correct way to react to the null value is by throwing a NullPointerException, and that's what you get when you call possible.isPresent() when possible == null.

Java as a language allows any value of a reference type to be null. It's up to the users of Java to not have null values when they don't want them, and to handle them correctly when they do want them. When they fail at that, a NullPointerException might be thrown.

Optional is not an alternative to writing == null. Optional is an alternative to using null. If you choose to not use null, and then use null anyway, you've created a bug.

What's the correct way to react to a programmer-introduced bug? Let's try your Optional.isPresent(value) idea in an example:

public abstract class BaseClass {
  static boolean optionalIsPresent(Optional<?> possible) {
    return (possible == null) ? false : possible.isPresent();
    // return possible.isPresent();
  }

  public final String name;

  public Optional<Integer> getID();

  protected BaseClass() {
    if (optionalIsPresent(getID())) {
      name = "number " + getID().get();
    } else {
      name = "nameless";
    }
  }
}

public class DerivedClass extends BaseClass {
  private final int id;

  public DerivedClass(int id) {
    this.id = id;
  }

  public Optional<Integer> getID() {
    return Optional.of(id);
  }
}

There is a programmer bug here, where they are trying to use a final field before it's set.

If optionalIsPresent(null) returns false, then the code above executes with no errors, and we have an object with behavior different from what we thought we specified: getID() is present, but name is "nameless". We get the same result as if we had never used Optional in the first place, and just used null and non-null values for "absent" and "present".

However, by using only absent() to represent absent(), our incorrect code throws a NullPointerException.



回答2:

The idea of Optional is not to prevent all NPEs. The idea is to make it clear that an API method can return an absent value, and to force the caller to be aware of that and deal with it.

Of course, if this method returns null instead of an Optional, you'll still get a NPE, but that's a giant design issue. A method returning an Optional should return an Optional (present or absent), not null.

And it's much more natural, in a OO language, to access the state of an object using methods of this object rather than a static method.



回答3:

JB Nizet makes a good answer but I'll expand around it from the prospective of Contract Oriented programming. This is also called Design by Contract.

Before Optional<T>, the only way to make a contract about callee's method potentially returning null is either

  1. Describing in the javadoc comment for the method "This may return null if xyz"
  2. Putting it in a design doc that no one will read
  3. Creating your own Optional class

With Optional<T>, you are reducing the frequency of NullPointerExceptions. If you make the "I'll use Optional if appropriate" contract in your package's contract:

  • If you return a String, your caller doesn't need to do the check. (If the caller crashes for a NPE, it is not their fault for crashing, it becomes your fault for breaking the contract. This is also why an Optional<String> method can't return null, it's breaking its contract to only return non-null objects)
  • If you return Optional<String>, your caller can't forget to do the optional check.

That last point being why it is Optional.isPresent() and not Optional.isPresent(Object). They could forget to call the later if there is a possibility of your method returning null. They can't forget to call the former.



回答4:

A basic design criterion of object-oriented languages is encapsulation, which says that the internal state of an object should only be visible to the object itself. Thus it is usually considered "cleaner" object-oriented design to access the state of an object through methods of the object than through static methods. In Java this may be a matter of taste, because static methods also belong to a class, but still the possible.isPresent() solution feels much more natural.