web-dev-qa-db-ja.com

Java 8+で、従来のnullポインターチェックの代わりにOptionalを使用するのはなぜですか?

最近、Java 8に移動しました。8。アプリケーションにOptionalオブジェクトが殺到しているのが見えます。

変更前Java 8(スタイル1)

_Employee employee = employeeServive.getEmployee();

if(employee!=null){
    System.out.println(employee.getId());
}
_

後Java 8(スタイル2)

_Optional<Employee> employeeOptional = Optional.ofNullable(employeeService.getEmployee());
if(employeeOptional.isPresent()){
    Employee employee = employeeOptional.get();
    System.out.println(employee.getId());
}
_

サービス自体がオプションを返すときに、Optional<Employee> employeeOptional = employeeService.getEmployee();の追加値が表示されません。

Java 6の背景から来ると、スタイル1はより明確でコード行が少ないと思います。ここで見逃している本当の利点はありますか?

blogでのすべての回答からの理解とさらなる研究から統合

118
user3198603

スタイル2は機能しませんJava 8完全な利点を表示するのに十分です。あなたはif ... useまったく。 Oracleの例 を参照してください。彼らのアドバイスを受けて、私たちは次のことを得る:

スタイル3

// Changed EmployeeServive to return an optional, no more nulls!
Optional<Employee> employee = employeeServive.getEmployee();
employee.ifPresent(e -> System.out.println(e.getId()));

またはより長いスニペット

Optional<Employee> employee = employeeServive.getEmployee();
// Sometimes an Employee has forgotten to write an up-to-date timesheet
Optional<Timesheet> timesheet = employee.flatMap(Employee::askForCurrentTimesheet); 
// We don't want to do the heavyweight action of creating a new estimate if it will just be discarded
client.bill(timesheet.orElseGet(EstimatedTimesheet::new));
116
Caleth

古いAPI間のOptionalを「互換性」レイヤーとして使用している場合でも、nullを返す可能性がある場合、最新の段階で(空ではない)オプションを作成すると役立つ場合があります。あなたはsureあなたは何かを持っていると思います。たとえば、あなたが書いた場所:

Optional<Employee> employeeOptional = Optional.ofNullable(employeeService.getEmployee());
if(employeeOptional.isPresent()){
    Employee employeeOptional= employeeOptional.get();
    System.out.println(employee.getId());
}

私は以下を選択します:

Optional.of(employeeService)                 // definitely have the service
        .map(EmployeeService::getEmployee)   // getEmployee() might return null
        .map(Employee::getId)                // get ID from employee if there is one
        .ifPresent(System.out::println);     // and if there is an ID, print it

重要なのは、あなたが知っている nullでない従業員がいることserviceであるため、Optional.of()を使用してOptionalにまとめることができます。 。次に、その上でgetEmployee()を呼び出すと、従業員を取得する場合としない場合があります。その従業員はIDを持っている場合があります(または、持っていない場合もあります)。次に、IDを取得した場合は、それを印刷します。

このコードでany null、存在などを明示的にチェックする必要はありません。

48
Joshua Taylor

単一の値のOptionalを持つことに付加価値はほとんどありません。ご覧のように、これはnullのチェックを存在のチェックに置き換えるだけです。

そのようなもののCollectionを持つことにはhuge付加価値があります。古いスタイルの低レベルのループを置き換えるために導入されたすべてのストリーミングメソッドは、Optionalのことを理解し、それらについて正しいことを行います(それらを処理しないか、最終的に別の未設定のOptionalを返します)。 nアイテムを個々のアイテムよりも頻繁に処理する場合(そしてすべての現実的なプログラムの99%が処理する場合)、オプションで存在するための組み込みサポートがあることは大きな改善です事。理想的なケースでは、nullまたはの存在を確認する必要はありません。

23
Kilian Foth

OptionalisNotNull()の空想的なAPIと同じように使用している限り、はい、nullを確認するだけで違いはありません。

使用すべきでないものOptional

値の存在を確認するためだけにOptionalを使用するのは、Bad Code™です。

// BAD CODE ™ --  just check getEmployee() != null  
Optional<Employee> employeeOptional =  Optional.ofNullable(employeeService.getEmployee());  
if(employeeOptional.isPresent()) {  
    Employee employee = employeeOptional.get();  
    System.out.println(employee.getId());
}  

Optionalを使用する必要があるもの

Nullを返すAPIメソッドを使用するときに必要に応じて値を生成することにより、値が存在しないようにします。

Employee employee = Optional.ofNullable(employeeService.getEmployee())
                            .orElseGet(Employee::new);
System.out.println(employee.getId());

または、新しい従業員の作成にコストがかかりすぎる場合:

Optional<Employee> employee = Optional.ofNullable(employeeService.getEmployee());
System.out.println(employee.map(Employee::getId).orElse("No employee found"));

また、あなたのメソッドが値を返さないかもしれないことをみんなに知らせてください(何らかの理由で上記のようなデフォルト値を返すことができない場合):

// Your code without Optional
public Employee getEmployee() {
    return someCondition ? null : someEmployee;
}
// Someone else's code
Employee employee = getEmployee(); // compiler doesn't complain
// employee.get...() -> NPE awaiting to happen, devs criticizing your code

// Your code with Optional
public Optional<Employee> getEmployee() {
    return someCondition ? Optional.empty() : Optional.of(someEmployee);
}
// Someone else's code
Employee employee = getEmployee(); // compiler complains about incompatible types
// Now devs either declare Optional<Employee>, or just do employee = getEmployee().get(), but do so consciously -- not your fault.

そして最後に、他の回答ですでに説明されているすべてのストリームのようなメソッドがありますが、それらは実際にはyoOptionalの使用を決定するのではなく、Optional他の人から提供されました。

18
walen

Optionalを使用する上での重要なポイントは、開発者が関数の戻り値かどうかを確認する必要がある場合に明確にしておくことです。

//service 1
Optional<Employee> employeeOptional = employeeService.getEmployee();
if(employeeOptional.isPresent()){
    Employee employeeOptional= employeeOptional.get();
    System.out.println(employee.getId());
}

//service 2
Employee employee = employeeService.getEmployeeXTPO();
System.out.println(employee.getId());
12
Rodrigo Menezes

あなたの主な間違いは、あなたがまだより手続き的な言葉で考えているということです。これは、人としてのあなたの批判ではなく、単なる観察です。より機能的な用語で考えることは時間と実践に伴うものであり、したがってメソッドは現在存在し、あなたに求められる最も明白な正しいもののようになります。 2番目の小さな間違いは、メソッド内にオプションを作成することです。オプションは、何かが値を返す場合と返さない場合があることを文書化するのに役立ちます。あなたは何も得ないかもしれません。

これにより、完全に合理的であるように見える完全に読みやすいコードを書くことができましたが、getとisPresentである卑劣な双子の誘惑に誘惑されました。

もちろん、質問はすぐに「なぜisPresentであり、そこに到達するのか」になります。

ここで多くの人が見落としているのは、isPresent()は、gosh-darnの有用なラムダがどのように機能するか、および機能的なものが好きな人によって完全に作成された新しいコード用に作成されたものではないということです。

しかし、それは私たちにいくつかの(2)良い、素晴らしい、魅力的な(?)利益を与えます:

  • 新しい機能を使用するためのレガシーコードの移行が容易になります。
  • オプションの学習曲線を緩和します。

最初のものはかなりシンプルです。

次のようなAPIがあるとします。

public interface SnickersCounter {
  /** 
   * Provides a proper count of how many snickers have been consumed in total.
   */
  public SnickersCount howManySnickersHaveBeenEaten();

  /**
    * returns the last snickers eaten.<br>
    * If no snickers have been eaten null is returned for contrived reasons.
    */
  public Snickers lastConsumedSnickers();
}

そして、あなたはこれをそのまま使用するレガシークラスを持っていました(空白を埋めてください):

Snickers lastSnickers = snickersCounter.lastConsumedSnickers();
if(null == lastSnickers) {
  throw new NoSuchSnickersException();
}
else {
  consumer.giveDiabetes(lastSnickers);
}

確かに人為的な例。しかし、ここで我慢してください。

Java 8がリリースされ、乗り出すためにスクランブルをかけています。したがって、私たちが行うことの1つは、古いインターフェイスをOptionalを返すものに置き換えたいということです。どうして?他の誰かがすでに上品に言及しているので:これは何かがnullになる可能性があるかどうかの当て推量を取り除きますこれはすでに他の人によって指摘されています。しかし今、私たちは問題を抱えています。想像してみてください(無実のメソッドでalt + F7を押している間に失礼します)、このメソッドが、他の点では優れた仕事をする十分にテストされたレガシーコードで呼び出される46か所。これらすべてを更新する必要があります。

これがisPresentが輝く場所です。

今から:Snickers lastSnickers = snickersCounter.lastConsumedSnickers(); if(null == lastSnickers){新しいNoSuchSnickersException();をスローします。 } else {consumer.giveDiabetes(lastSnickers); }

になる:

Optional<Snickers> lastSnickers = snickersCounter.lastConsumedSnickers();
if(!lastSnickers.isPresent()) {
  throw new NoSuchSnickersException();
}
else {
  consumer.giveDiabetes(lastSnickers.get());
}

そして、これは新しいジュニアに与えることができる簡単な変更です:彼は何か便利なことをすることができ、同時にコードベースを探求するでしょう。双方にとって好都合。結局のところ、このパターンに似たものはかなり広まっています。そして、ラムダなどを使用するためにコードを書き直す必要はありません。 (この特定のケースではそれは取るに足らないことですが、読者にとって練習として難しいと思われる例を考えておくことにします。)

これは、実行した方法が、コストのかかる書き換えを行わずにレガシーコードを処理する方法であることを意味しています。では、新しいコードはどうでしょうか?

まあ、あなたの場合、単に何かを印刷したいだけなら、あなたは単にそうするでしょう:

snickersCounter.lastConsumedSnickers()。ifPresent(System.out :: println);

これは非常にシンプルで、完全に明確です。そのとき、表面に向かってゆっくりと泡立っている点は、get()とisPresent()のユースケースが存在することです。これらは、既存のコードを機械的に変更して、新しいタイプを使用することをあまり気にせずに使用できるようにするためのものです。したがって、あなたがしていることは、次のように見当違いです。

  • Nullを返す可能性のあるメソッドを呼び出しています。正しい考えは、メソッドがnullを返すということです。
  • ラムダの空想を含む美味しい新しいメソッドを使用する代わりに、従来のバンドエイドメソッドを使用してこのオプションを処理しています。

Optionalを単純なnull-safetyチェックとして使用したい場合は、次のようにする必要があります。

new Optional.ofNullable(employeeServive.getEmployee())
    .map(Employee::getId)
    .ifPresent(System.out::println);

もちろん、この見栄えの良いバージョンは次のようになります。

employeeService.getEmployee()
    .map(Employee::getId)
    .ifPresent(System.out::println);

ちなみに、必須ではありませんが、読みやすいように、操作ごとに改行することをお勧めします。読みやすく、理解しやすいので、どの曜日でも簡潔になります。

もちろん、これは非常に単純な例であり、私たちがやろうとしているすべてのことを簡単に理解できます。現実の世界では、これは必ずしも簡単なことではありません。しかし、この例では、私たちが表現しているものが私たちの意図であることに注意してください。従業員とIDを取得し、可能であれば印刷します。これはオプションの2番目の大きな勝利です。これにより、より明確なコードを作成できます。また、多くのことを行うメソッドを作成してマップにフィードできるようにすることは、一般に良い考えだと私は思います。

8
Haakon Løtveit

私の答えは:そうではありません。それが本当に改善されているかどうかについて、少なくとも2度考えてください。

次のような式(別の回答から引用)

Optional.of(employeeService)                 // definitely have the service
        .map(EmployeeService::getEmployee)   // getEmployee() might return null
        .map(Employee::getId)                // get ID from employee if there is one
        .ifPresent(System.out::println);     // and if there is an ID, print it

もともとnullを許容する戻りメソッドを処理する適切な機能的な方法のようです。それは見た目が良く、投げることはなく、「不在」の場合の処理​​について考えさせることもありません。

古いスタイルの方法よりもよく見えます

Employee employee = employeeService.getEmployee();
if (employee != null) {
    ID id = employee.getId();
    if (id != null) {
        System.out.println(id);
    }
}

これにより、else句が存在する可能性があることが明らかになります。

機能的なスタイル

  • 完全に異なって見えます(そして、未使用の人には判読できません)
  • デバッグするのは面倒です
  • 「不在」のケースを処理するために簡単に拡張することはできません(orElseは必ずしも十分ではありません)
  • 「不在」のケースを無視することに誤解を招く

決して万能薬として使用するべきではありません。普遍的に適用できる場合は、.演算子のセマンティクスを ?.演算子 のセマンティクス、NPEは忘れられ、すべての問題は無視されます。


大きなJavaファンである一方で、機能的なスタイルは単なる貧弱なものであるとJava

employeeService
.getEmployee()
?.getId()
?.apply(id => System.out.println(id));

他の言語でよく知られています。この声明に同意しますか

  • (本当に)機能していないのですか?
  • 古いスタイルのコードに非常に似ていますが、はるかに単純ですか?
  • 機能的なスタイルよりもはるかに読みやすいですか?
  • デバッガーフレンドリーですか?
0
maaartinus

オプションは、関数型プログラミングからの概念(高次タイプ)です。これを使用すると、ネストされたnullチェックが不要になり、Doomのピラミッドから解放されます。

0
Petras Purlys

「employeeService」自体がnullの場合はどうなりますか?どちらのコードスニペットでも、nullポインタ例外が発生します。

次のようにオプションを使用せずに処理できます。

if(employeeServive != null) {
    Employee employee = employeeServive.getEmployee();
    if(employee!=null){
        System.out.println(employee.getId());
    }
}

しかし、ご覧のとおり、複数のifチェックがあり、employeeService.getEmployee()。​​getDepartment()。getName().... getXXX()。getYYY()などの長いネストされた構造を持つ長い階層に拡張できます。

このタイプのシナリオを処理するには、次のようにOptionalクラスを使用できます。

Optional.ofNullable(employeeService)
     .map(service -> service.getEmployee())
     .map(employee -> employee.getId())
     .ifPresent(System.out::println);

そして、どんなサイズのネストも処理できます。

Javaオプションについてのすべてを学ぶために、このトピックの私の記事を読んでください: オプションJava 8

0
Shubham Bansal

スタイル2にはメリットがありません。ヌルチェックが必要であることを理解する必要がありますが、現在はさらに大きく、読みにくくなっています。

より良いスタイルは、employeeServive.getEmployee()がOptionalを返し、コードが次のようになる場合です。

Optional<Employee> employeeOptional = employeeServive.getEmployee();
if(employeeOptional.isPresent()){
    Employee employee= employeeOptional.get();
    System.out.println(employee.getId());
}

この方法では、employeeServive.getEmployee()。​​getId()を呼び出すことができず、オプションの値を何らかの方法で処理する必要があることに気付きます。そして、コードベース全体でメソッドがnullを返さない場合(または、チームによく知られているこのルールの例外がほとんどない場合)は、NPEに対する安全性が向上します。

0
user470365

私の最大の利点は、メソッドシグネチャがEmployeeを返す場合はしないでください nullをチェックすることです。その署名によって、従業員を返すことが保証されていることがわかります。失敗した場合に対処する必要はありません。オプションで、あなたはあなたが知っています。

私は、nullが可能かどうかを判断するためにコードをトレースしたくないので失敗しないnullチェックがあることをコードで確認しました。これにより、コードは少し遅くなりますが、さらに重要なことに、コードのノイズが多くなります。

ただし、これを機能させるには、このパターンを一貫して適用する必要があります。

0
Sled