web-dev-qa-db-ja.com

NullチェックチェーンとNullPointerExceptionのキャッチ

Webサービスは巨大なXMLを返すので、その深くネストされたフィールドにアクセスする必要があります。例えば:

return wsObject.getFoo().getBar().getBaz().getInt()

問題は、getFoo()getBar()getBaz()がすべてnullを返す可能性があることです。

ただし、すべての場合にnullをチェックすると、コードは非常に冗長になり読みにくくなります。さらに、一部のフィールドのチェックを見逃す場合があります。

if (wsObject.getFoo() == null) return -1;
if (wsObject.getFoo().getBar() == null) return -1;
// maybe also do something with wsObject.getFoo().getBar()
if (wsObject.getFoo().getBar().getBaz() == null) return -1;
return wsObject.getFoo().getBar().getBaz().getInt();

書くことは許されますか

try {
    return wsObject.getFoo().getBar().getBaz().getInt();
} catch (NullPointerException ignored) {
    return -1;
}

それともアンチパターンと見なされますか?

107
David Frank

NullPointerExceptionをキャッチすることは、ほとんどどこでも発生する可能性があるため、本当に問題のあることです。バグから1つを取得し、偶然にそれをキャッチし、すべてが正常であるかのように続行することは非常に簡単であるため、実際の問題を隠します。 対処するのは非常に難しいため、完全に避けるのが最善です。(たとえば、null Integerの自動アンボックス化について考えてください。)

代わりに Optional クラスを使用することをお勧めします。多くの場合、これは、存在する値または存在しない値で作業する場合の最良のアプローチです。

それを使用して、次のようなコードを書くことができます。

public Optional<Integer> m(Ws wsObject) {
    return Optional.ofNullable(wsObject.getFoo()) // Here you get Optional.empty() if the Foo is null
        .map(f -> f.getBar()) // Here you transform the optional or get empty if the Bar is null
        .map(b -> b.getBaz())
        .map(b -> b.getInt());
        // Add this if you want to return an -1 int instead of an empty optional if any is null
        // .orElse(-1);
        // Or this if you want to throw an exception instead
        // .orElseThrow(SomeApplicationException::new);
}

なぜオプションなのですか?

存在しない可能性のある値にOptionalの代わりにnullsを使用すると、その事実が読者に非常にわかりやすく明確になり、型システムは誤ってそれを忘れないようにします。

maporElse のような、より便利にそのような値を操作するためのメソッドにもアクセスできます。


欠席は有効ですか、エラーですか?

しかし、中間メソッドがnullを返すことが有効な結果であるか、それがエラーの兆候であるかについても考えてください。常にエラーである場合は、特別な値を返すよりも、中間メソッド自体が例外をスローするよりも、おそらく例外をスローする方が良いでしょう。


多分もっとオプション?

一方、中間メソッドからの欠落値が有効である場合、それらについてもOptionalsに切り替えることができますか?

次に、次のように使用できます。

public Optional<Integer> mo(Ws wsObject) {
    return wsObject.getFoo()
        .flatMap(f -> f.getBar())
        .flatMap(b -> b.getBaz())
        .flatMap(b -> b.getInt());        
}

なぜオプションではないのですか?

Optionalを使用しないと考えることができる唯一の理由は、これがコードの本当にパフォーマンスに重要な部分にあり、ガベージコレクションのオーバーヘッドが問題になる場合です。これは、コードが実行されるたびにいくつかのOptionalオブジェクトが割り当てられ、VMmightがそれらを最適化できないためです。その場合、元のifテストの方が良いかもしれません。

133
Lii

Objects.requireNonNull(T obj, String message) を検討することをお勧めします。次のように、例外ごとに詳細なメッセージを含むチェーンを構築できます。

requireNonNull(requireNonNull(requireNonNull(
    wsObject, "wsObject is null")
        .getFoo(), "getFoo() is null")
            .getBar(), "getBar() is null");

-1などの特別な戻り値を使用しないことをお勧めします。それはJavaスタイルではありません。 Javaは、C言語から来たこの昔ながらの方法を避けるために、例外のメカニズムを設計しました。

NullPointerExceptionを投げることも最適なオプションではありません。独自の例外を提供して(checkedにして、ユーザーによって処理されることを保証するか、uncheckedより簡単な方法で処理するか、使用しているXMLパーサーからの特定の例外を使用します。

11
Andrew Tobilko

ケースのように、クラス構造が実際に制御できないと仮定すると、パフォーマンスが主要な懸念事項でない限り、質問で提案されているようにNPEをキャッチすることは実際に合理的なソリューションであると思います。小さな改善の1つは、スロー/キャッチロジックをラップして混乱を避けることです。

static <T> T get(Supplier<T> supplier, T defaultValue) {
    try {
        return supplier.get();
    } catch (NullPointerException e) {
        return defaultValue;
    }
}

これで、次のことが簡単にできます。

return get(() -> wsObject.getFoo().getBar().getBaz().getInt(), -1);
6
shmosel

コメントの Tom で既に指摘したように、

次のステートメントは、 デメテルの法則 に違反しています。

wsObject.getFoo().getBar().getBaz().getInt()

必要なのはintで、Fooから取得できます。 デメテルの法則は言う、見知らぬ人とは決して話さない。あなたの場合、FooBarの内部で実際の実装を隠すことができます。

これで、Fooからintを取得するメソッドをBazに作成できます。最終的に、FooBarになり、BarではIntに直接Bazを公開せずにFooにアクセスできます。したがって、nullチェックはおそらく異なるクラスに分割され、必要な属性のみがクラス間で共有されます。

5
CoderCroc

私の答えは@jankiとほぼ同じ行にありますが、コードスニペットを次のように少し変更したいと思います。

if (wsObject.getFoo() != null && wsObject.getFoo().getBar() != null && wsObject.getFoo().getBar().getBaz() != null) 
   return wsObject.getFoo().getBar().getBaz().getInt();
else
   return something or throw exception;

そのオブジェクトがnullになる可能性がある場合は、wsObjectのnullチェックも追加できます。

4
Arka Ghosh

読みやすくするために、次のような複数の変数を使用できます。

Foo theFoo;
Bar theBar;
Baz theBaz;

theFoo = wsObject.getFoo();

if ( theFoo == null ) {
  // Exit.
}

theBar = theFoo.getBar();

if ( theBar == null ) {
  // Exit.
}

theBaz = theBar.getBaz();

if ( theBaz == null ) {
  // Exit.
}

return theBaz.getInt();
3
JimmyB

一部のメソッドは「null」を返す場合がありますが、どのような状況でnullを返すかについては述べていません。 NullPointerExceptionをキャッチすると言いますが、なぜキャッチするのかは言いません。この情報不足は、例外が何のためのものであり、なぜそれらが代替より優れているのかを明確に理解していないことを示唆しています。

アクションを実行することを意図しているクラスメソッドを考えてみてください。ただし、そのメソッドは制御できません(実際には -の場合)ため、アクションを実行できませんguaranteeall Javaのメソッド )。そのメソッドを呼び出して戻ります。そのメソッドを呼び出すコードは、成功したかどうかを知る必要があります。どうやって知ることができますか?成功または失敗の2つの可能性に対処するために、どのように構成できますか?

例外を使用して、事後条件として成功した​​メソッドを記述することができます。メソッドが戻る場合、成功しました。例外をスローした場合、失敗していました。これは明確にするための大きな勝利です。通常の成功事例を明確に処理するコードを記述し、すべてのエラー処理コードをcatch句に移動できます。メソッドが失敗した方法または理由の詳細は呼び出し側にとって重要ではないことがよくあるため、同じcatch句をいくつかのタイプの失敗の処理に使用できます。また、メソッドは例外をキャッチする必要がない場合がよくありますat allが、例外をits呼び出し元に伝搬できるようにするだけです。プログラムのバグによる例外は、後者のクラスにあります。バグがある場合、適切に反応できるメソッドはほとんどありません。

したがって、nullを返すメソッド。

  • null値はコードのバグを示していますか?その場合は、例外をまったくキャッチしないでください。また、コードが自分自身を推測しようとしてはなりません。それが機能すると仮定して、明確で簡潔なものを書いてください。メソッド呼び出しのチェーンは明確かつ簡潔ですか?次に、それらを使用します。
  • null値は、プログラムへの無効な入力を示していますか?その場合、NullPointerExceptionは通常、バグを示すために予約されているため、スローする適切な例外ではありません。おそらく、IllegalArgumentException未チェックの例外 が必要な場合)またはIOException(チェック済みの例外が必要な場合)から派生したカスタム例外をスローする必要があります。無効な入力がある場合、プログラムは詳細な構文エラーメッセージを提供する必要がありますか?その場合、各メソッドでnull戻り値をチェックしてから、適切な診断例外をスローすることしかできません。プログラムが詳細な診断を提供する必要がない場合、メソッド呼び出しを連結して、NullPointerExceptionをキャッチし、カスタム例外をスローするのが最も明確で簡潔です。

答えの1つは、連鎖メソッド呼び出しが Demeterの法則 に違反しているため、悪いと主張しています。その主張は間違っています。

  • プログラムの設計に関しては、何が良いのか、何が悪いのかについての絶対的なルールはありません。ヒューリスティックのみがあります:ほぼ常に(ほとんどすべての)ルールです。プログラミングのスキルの一部は、これらの種類の規則を破るのがいつよいかを知ることです。したがって、「これはルールに反しているX」という簡潔な主張は、実際にはまったく答えではありません。これは、ルールshouldが破られる状況の1つですか?
  • Law of Demeterは、実際にはAPIまたはクラスインターフェイスの設計に関するルールです。クラスを設計するとき、抽象化の階層があると便利です。言語プリミティブを使用して操作を直接実行し、言語プリミティブよりも高いレベルの抽象化でオブジェクトを表す低レベルのクラスがあります。低レベルクラスに委任する中レベルクラスがあり、低レベルクラスよりも高いレベルで操作と表現を実装します。中レベルのクラスに委任する高レベルのクラスがあり、さらに高レベルの操作と抽象化を実装します。 (ここでは、抽象化の3つのレベルについてのみ説明しましたが、さらに多くの可能性があります)。これにより、各レベルで適切な抽象化の観点からコードを表現できるため、複雑さが隠されます。 Law of Demeterの論理的根拠は、メソッド呼び出しのチェーンがある場合、低レベルの詳細を直接処理するために中レベルのクラスを介して到達する高レベルのクラスがあることを示唆しているためです。中レベルのクラスが、高レベルのクラスに必要な中レベルの抽象操作を提供していないこと。しかし、これはnotのようです。メソッド呼び出しのチェーンでクラスを設計しなかったのは、自動生成されたXMLシリアル化コードの結果です(右?)シリアル化されていないXMLはすべて抽象化階層の同じレベルにあるため、呼び出しのチェーンは抽象化階層を下っていません(右?)
3
Raedwald

昨日からこの投稿をフォローしています。

私は、NPEをキャッチするのは悪いと言うコメントにコメント/投票してきました。これが私がそうしている理由です。

package com.todelete;

public class Test {
    public static void main(String[] args) {
        Address address = new Address();
        address.setSomeCrap(null);
        Person person = new Person();
        person.setAddress(address);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            try {
                System.out.println(person.getAddress().getSomeCrap().getCrap());
            } catch (NullPointerException npe) {

            }
        }
        long endTime = System.currentTimeMillis();
        System.out.println((endTime - startTime) / 1000F);
        long startTime1 = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            if (person != null) {
                Address address1 = person.getAddress();
                if (address1 != null) {
                    SomeCrap someCrap2 = address1.getSomeCrap();
                    if (someCrap2 != null) {
                        System.out.println(someCrap2.getCrap());
                    }
                }
            }
        }
        long endTime1 = System.currentTimeMillis();
        System.out.println((endTime1 - startTime1) / 1000F);
    }
}

  public class Person {
    private Address address;

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }
}

package com.todelete;

public class Address {
    private SomeCrap someCrap;

    public SomeCrap getSomeCrap() {
        return someCrap;
    }

    public void setSomeCrap(SomeCrap someCrap) {
        this.someCrap = someCrap;
    }
}

package com.todelete;

public class SomeCrap {
    private String crap;

    public String getCrap() {
        return crap;
    }

    public void setCrap(String crap) {
        this.crap = crap;
    }
}

出力

3.216

0.002

ここに明確な勝者がいます。 ifチェックは、例外をキャッチするよりもはるかに安価です。そのJava-8のやり方を見てきました。現在のアプリケーションの70%がまだJava-7で実行されていることを考えると、この答えを追加しています。

下線ミッションクリティカルなアプリケーションでは、NPEの処理にコストがかかります。

2
NewUser

NullPointerExceptionは実行時例外であるため、一般的に言えばキャッチすることはお勧めできませんが、回避するために。

メソッドを呼び出す場所で例外をキャッチする必要があります(または、スタックを伝播します)。それにもかかわらず、あなたの場合、値-1でその結果で作業を続けることができ、nullである可能性のある「ピース」を使用していないため、伝播しないことが確実である場合、それは私にとって正しいようですそれを捕まえて

編集:

後の answer @xenterosに同意します。たとえば、InvalidXMLExceptionと呼ぶことができる-1を返す代わりに、独自の例外を起動する方が良いでしょう。

2
SCouto

コードをリファクタリングしたくない場合にJava 8を使用できれば、メソッド参照を使用できます。

最初の簡単なデモ(静的内部クラスを言い訳する)

public class JavaApplication14 
{
    static class Baz
    {
        private final int _int;
        public Baz(int value){ _int = value; }
        public int getInt(){ return _int; }
    }
    static class Bar
    {
        private final Baz _baz;
        public Bar(Baz baz){ _baz = baz; }
        public Baz getBar(){ return _baz; }   
    }
    static class Foo
    {
        private final Bar _bar;
        public Foo(Bar bar){ _bar = bar; }
        public Bar getBar(){ return _bar; }   
    }
    static class WSObject
    {
        private final Foo _foo;
        public WSObject(Foo foo){ _foo = foo; }
        public Foo getFoo(){ return _foo; }
    }
    interface Getter<T, R>
    {
        R get(T value);
    }

    static class GetterResult<R>
    {
        public R result;
        public int lastIndex;
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) 
    {
        WSObject wsObject = new WSObject(new Foo(new Bar(new Baz(241))));
        WSObject wsObjectNull = new WSObject(new Foo(null));

        GetterResult<Integer> intResult
                = getterChain(wsObject, WSObject::getFoo, Foo::getBar, Bar::getBar, Baz::getInt);

        GetterResult<Integer> intResult2
                = getterChain(wsObjectNull, WSObject::getFoo, Foo::getBar, Bar::getBar, Baz::getInt);


        System.out.println(intResult.result);
        System.out.println(intResult.lastIndex);

        System.out.println();
        System.out.println(intResult2.result);
        System.out.println(intResult2.lastIndex);

        // TODO code application logic here
    }

    public static <R, V1, V2, V3, V4> GetterResult<R>
            getterChain(V1 value, Getter<V1, V2> g1, Getter<V2, V3> g2, Getter<V3, V4> g3, Getter<V4, R> g4)
            {
                GetterResult result = new GetterResult<>();

                Object tmp = value;


                if (tmp == null)
                    return result;
                tmp = g1.get((V1)tmp);
                result.lastIndex++;


                if (tmp == null)
                    return result;
                tmp = g2.get((V2)tmp);
                result.lastIndex++;

                if (tmp == null)
                    return result;
                tmp = g3.get((V3)tmp);
                result.lastIndex++;

                if (tmp == null)
                    return result;
                tmp = g4.get((V4)tmp);
                result.lastIndex++;


                result.result = (R)tmp;

                return result;
            }
}

出力

241
4

ヌル
2

インターフェイスGetterは単なる機能的なインターフェイスであり、同等のものを使用できます。
GetterResultクラス、わかりやすくするためにアクセサーを削除し、ゲッターチェーンの結果(ある場合)、または最後に呼び出されたゲッターのインデックスを保持します。

メソッドgetterChainは、自動(または必要に応じて手動)で生成できる単純な定型コードです。
繰り返しブロックが自明になるようにコードを構造化しました。


ゲッターの数ごとにgetterChainのオーバーロードを1つ定義する必要があるため、これは完全なソリューションではありません。

代わりにコードをリファクタリングしますが、できない場合は、長いゲッターチェーンを使用して自分自身を見つけることができます。多くの場合、2〜10のゲッターを取るオーバーロードでクラスを構築することを検討できます。

2
Margaret Bloom

他の人が言ったように、デメテルの法則を尊重することは間違いなく解決策の一部です。別の部分は、可能な限り、nullを返さないように、これらのチェーンされたメソッドを変更することです。代わりに、空のnull、空のString、または呼び出し元がCollectionで行うことを意味または実行するその他のダミーオブジェクトを返すことで、nullを返すことを回避できます。

2
Kevin Krumwiede

NullPointerExceptionをキャッチしないでください。あなたはそれがどこから来たのかわかりません(あなたの場合はそうではないかもしれませんが、多分何か他のものがそれを投げた)そしてそれは遅いです。指定されたフィールドにアクセスしたいので、他のすべてのフィールドはnullでない必要があります。これは、すべてのフィールドをチェックする完全な正当な理由です。読みやすくするためのメソッドを作成する場合は、おそらく1つチェックします。他の人が指摘したように、すでに-1を返すことは非常に古いですが、理由があるかどうかはわかりません(たとえば、別のシステムと話す)。

public int callService() {
    ...
    if(isValid(wsObject)){
        return wsObject.getFoo().getBar().getBaz().getInt();
    }
    return -1;
}


public boolean isValid(WsObject wsObject) {
    if(wsObject.getFoo() != null &&
        wsObject.getFoo().getBar() != null &&
        wsObject.getFoo().getBar().getBaz() != null) {
        return true;
    }
    return false;
}

編集:WsObjectはおそらくデータ構造にすぎないため、デメテルの法則に反するかどうかは議論の余地があります( https://stackoverflow.com/a/26021695/152888 を確認してください)。

2
DerM

エラーの意味に焦点を当てた回答を追加したいと思います。ヌル例外自体は、完全なエラーを意味するものではありません。そのため、それらを直接扱うことは避けてください。

コードがうまくいかないケースが数千あります:データベースに接続できない、IO例外、ネットワークエラー...これらを1つずつ処理する場合(ここでのnullチェックのように)、面倒すぎる。

コード内:

wsObject.getFoo().getBar().getBaz().getInt();

どのフィールドがヌルであるかを知っていても、何が間違っているのかわかりません。たぶんBarはnullですが、それは期待されていますか?それともデータエラーですか?あなたのコードを読む人について考えてください

Xenterosの答えのように、私はカスタム未チェック例外を使用することを提案します。たとえば、この状況では:Fooはnull(有効なデータ)である可能性がありますが、BarとBazは決してnull(無効なデータ)であってはなりません

コードは書き直すことができます:

void myFunction()
{
    try 
    {
        if (wsObject.getFoo() == null)
        {
          throw new FooNotExistException();
        }

        return wsObject.getFoo().getBar().getBaz().getInt();
    }
    catch (Exception ex)
    {
        log.error(ex.Message, ex); // Write log to track whatever exception happening
        throw new OperationFailedException("The requested operation failed")
    }
}


void Main()
{
    try
    {
        myFunction();
    }
    catch(FooNotExistException)
    {
        // Show error: "Your foo does not exist, please check"
    }
    catch(OperationFailedException)
    {
        // Show error: "Operation failed, please contact our support"
    }
}
2
Hoàng Long

独自の例外を作成することを検討する価値があります。 MyOperationFailedExceptionと呼びましょう。代わりに値を返す代わりに投げることができます。結果は同じになります-関数を終了しますが、ハードコードされた値-1(Javaアンチパターン)は返されません。 Javaでは、例外を使用します。

try {
    return wsObject.getFoo().getBar().getBaz().getInt();
} catch (NullPointerException ignored) {
    throw new MyOperationFailedException();
}

編集:

コメントの議論によると、以前の考えに何かを追加させてください。このコードには2つの可能性があります。 1つはnullを受け入れること、もう1つはエラーであることです。

エラーで発生した場合、ブレークポイントが十分でないときにデバッグ目的で他の構造を使用してコードをデバッグできます。

許容範囲内であれば、このnullがどこに現れるかは気にしません。そうした場合、これらのリクエストをチェーン化しないでください。

1
xenteros

効率が問題になる場合は、「キャッチ」オプションを検討する必要があります。 「catch」が伝播するため(「SCouto」で述べたように)使用できない場合は、ローカル変数を使用して、メソッドgetFoo()getBar()、およびgetBaz()への複数の呼び出しを避けます。

1
JEP
return wsObject.getFooBarBazInt();

デメテルの法則を適用することにより、

class WsObject
{
    FooObject foo;
    ..
    Integer getFooBarBazInt()
    {
        if(foo != null) return foo.getBarBazInt();
        else return null;
    }
}

class FooObject
{
    BarObject bar;
    ..
    Integer getBarBazInt()
    {
        if(bar != null) return bar.getBazInt();
        else return null;
    }
}

class BarObject
{
    BazObject baz;
    ..
    Integer getBazInt()
    {
        if(baz != null) return baz.getInt();
        else return null;
    }
}

class BazObject
{
    Integer myInt;
    ..
    Integer getInt()
    {
        return myInt;
    }
}
1
Khaled.K

あなたが持っている方法は長いですが、非常に読みやすいです。私があなたのコードベースに来た新しい開発者であれば、あなたが何をしていたかをかなり早く見ることができました。他の回答のほとんど(例外のキャッチを含む)は物事をより読みやすくしているようには見えず、私の意見では読みにくいものもあります。

生成されたソースを制御できない可能性が高く、ここでいくつかの深くネストされたフィールドに本当にアクセスする必要があると仮定すると、それぞれの深くネストされたアクセスをメソッドでラップすることをお勧めします。

private int getFooBarBazInt() {
    if (wsObject.getFoo() == null) return -1;
    if (wsObject.getFoo().getBar() == null) return -1;
    if (wsObject.getFoo().getBar().getBaz() == null) return -1;
    return wsObject.getFoo().getBar().getBaz().getInt();
}

これらのメソッドをたくさん書いている場合、またはこれらのパブリックな静的メソッドを作成したい場合は、別のオブジェクトモデルを作成し、好きなようにネストし、関心のあるフィールドのみで、Webから変換しますオブジェクトモデルをオブジェクトモデルにサービスします。

リモートWebサービスと通信する場合、「リモートドメイン」と「アプリケーションドメイン」を持ち、2つを切り替えるのが非常に一般的です。リモートドメインは多くの場合、Webプロトコルによって制限されます(たとえば、純粋なRESTfulサービスでヘルパーメソッドを前後に送信できず、複数のAPI呼び出しを回避するために深くネストされたオブジェクトモデルが一般的です)。あなたのクライアント。

例えば:

public static class MyFoo {

    private int barBazInt;

    public MyFoo(Foo foo) {
        this.barBazInt = parseBarBazInt();
    }

    public int getBarBazInt() {
        return barBazInt;
    }

    private int parseFooBarBazInt(Foo foo) {
        if (foo() == null) return -1;
        if (foo().getBar() == null) return -1;
        if (foo().getBar().getBaz() == null) return -1;
        return foo().getBar().getBaz().getInt();
    }

}
1
Pace

他のすべてとは異なるように見える答えを与える。

NULLsでifを確認することをお勧めします。

理由:

プログラムがクラッシュする可能性は一度も残さないでください。 NullPointerはシステムによって生成されます。 システム生成例外の動作は予測できません。自分でプログラムを処理する方法がすでにある場合は、プログラムをSystemの手に委ねないでください。さらに、安全性を高めるために例外処理メカニズムを追加します。!!

コードを読みやすくするには、条件を確認するためにこれを試してください:

if (wsObject.getFoo() == null || wsObject.getFoo().getBar() == null || wsObject.getFoo().getBar().getBaz() == null) 
   return -1;
else 
   return wsObject.getFoo().getBar().getBaz().getInt();

編集:

ここで、いくつかの変数にこれらの値wsObject.getFoo()wsObject.getFoo().getBar()wsObject.getFoo().getBar().getBaz()を保存する必要があります。その関数の戻り値の型がわからないので、私はそれをしていません。

どんな提案でも大歓迎です。

0
Janki Gadhiya

オブジェクトのツリーをナビゲートするパスを定義できるSnagというクラスを作成しました。その使用例を次に示します。

Snag<Car, String> ENGINE_NAME = Snag.createForAndReturn(Car.class, String.class).toGet("engine.name").andReturnNullIfMissing();

インスタンスENGINE_NAMEは、渡されたインスタンスでCar?.getEngine()?.getName()を効果的に呼び出し、いずれかの参照がnullを返した場合はnullを返します。

final String name =  ENGINE_NAME.get(firstCar);

Mavenには公開されていませんが、誰かがこれを便利だと思ったら ここ (もちろん保証なし!)

それは少し基本的ですが、仕事をするようです。明らかに、Javaのより新しいバージョンと、安全なナビゲーションまたはOptionalをサポートする他のJVM言語ではより古くなっています。

0
Rich