序文
ヌルポインターは、私たちが嫌う最も一般的で迷惑な例外です。ヌルポインターの例外を防ぐために、コードに多くの非ヌルの判断を記述してはなりません。
Java 8は、新しいオプションクラスを紹介します。ヌルポインターの発生を回避するために、オプションでデータをロードする必要がある限り、多くのif(obj!=null)判断を記述する必要はありません。オブジェクトをラップするコンテナです。
Null Pointerの例外に遭遇したプログラマーはJavaプログラマーではないと言われており、Nullは実際に多くの問題を引き起こしています。 Java 8は、nullによって引き起こされる多くの問題を回避するために、 java.util.Optionalという新しいクラスを紹介します。
ヌルの参照がどのような害を及ぼすかを見てみましょう。最初にクラスコンピューターを作成します。構造は、次の図に示されています。
次のコードを呼び出すとどうなりますか?
string version = computer.getSoundCard()。getUsb()。getVersion();
上記のコードは問題ないようですが、多くのコンピューター(Raspberry PIなど)には実際にはサウンドカードがありません。したがって、 getSoundcard()メソッドを呼び出すと、間違いなくヌルポインターの例外がスローされます。
通常のが悪い方法は、コンピューターにサウンドカードがないことを示すためにヌルの参照を返すことですが、これはgetUSB()メソッドが空の参照で呼び出されることを意味します。それについて考えてください、あなたのプログラムがクライアントコンピューターで実行されているときに突然現れるのはどれほど恥ずかしいことですか?
偉大なコンピューターサイエンスのトニー・ホアレはかつて次のように書いています。
では、プログラムが実行されているときに、nullポインターの例外を回避するにはどうすればよいですか?あなたは警戒し、このようなヌルポインターの可能性を常にチェックする必要があります。
文字列version = "nown"; if(computer!= null){soundcard soundcard = computer.getSoundCard(); if(soundcard!= null){usb usb = soundcard.getusb(); if(usb!= null){version = usb.getversion(); }}}ただし、上記のコードにはあまりにも多くのヌルチェックがあり、コード構造全体が非常にugいになることがわかります。しかし、この判断を使用して、システムが実行されているときにヌルポインターがないことを確認する必要があります。当社のビジネスコードにこのような空の参照がたくさんあるかどうかを判断するのは単に迷惑です。また、コードの読みやすさも低下します。
値が空であるかどうかを確認するのを忘れた場合、ヌルの参照にも大きな潜在的な問題があります。この記事では、値が存在しない表現としてnull参照を使用することが悪い方法であることを証明します。 null参照を再度使用するのではなく、値が存在しないことを示すより良いモデルが必要です。
Java 8は、Haskell LanguageとScala Languageに触発されたjava.util.Optional<T>という新しいクラスを導入しました。このクラスには、以下の図とコードに示すように、任意の値を含めることができます。オプションは、値を含む値と考えることができます。オプションに値が含まれていない場合、下の図に示すように、空は空です。
パブリッククラスコンピューター{プライベートオプション<SoundCard> SoundCard; public Optional <SoundCard> getSoundCard(){...} ...} public class soundcard {private optional <USB> usb; public Optional <USB> getUsb(){...}} public class usb {public string getVersion(){...}}上記のコードは、コンピューターがサウンドカードを置き換える可能性があることを示しています(サウンドカードには存在する場合と存在しない場合があります)。サウンドカードには、USBポートも含まれる場合があります。これは改善方法であり、モデルは特定の値が存在しない可能性があることをより明確に反映できます。
しかし、 Optional<Soundcard>オブジェクトを扱う方法は?結局のところ、あなたが取得したいのはUSBポート番号です。とても簡単です。オプションのクラスには、値が存在するかどうかを処理する方法がいくつか含まれています。 NULL参照と比較して、オプションのクラスは、値が関連しているかどうかを処理することを強制します。
オプションのクラスは、null参照を置き換えないことに注意する必要があります。それどころか、設計されたAPIを理解しやすくするために、関数の署名を見ると、関数に渡される値が存在しないかどうかを判断できます。これにより、実際の値を処理するためにオプションのクラスを開くように求められます。
オプションモードを採用します
たくさん言った後、いくつかのコードを見てみましょう!まず、従来のヌル参照検出を書き換えるためにオプションを使用する方法を見てみましょう。この記事の最後に、オプションの使用方法がわかります。
string name = computer.flatmap(computer :: getSoundCard).flatmap(soundcard :: getUsb).map(usb :: getversion).orelse( "nown");
オプションのオブジェクトを作成します
空のオプションオブジェクトを作成できます。
オプション<SoundCard> sc = optional.empty();
次に、非ヌル値を含むオプションのコンセントを作成することです。
SoundCard SoundCard = new SoundCard(); optional <SoundCard> sc = optional.of(soundcard);
サウンドカードがnullの場合、nullポインターの例外はすぐにスローされます(これは、サウンドカード属性を取得するときにそれを投げるよりも優れています)。
Nullableを使用することにより、NULL参照を含む可能性のあるオプションのオブジェクトを作成できます。
オプション<SoundCard> sc = optional.ofnullable(soundcard);
サウンドカードがNULLリファレンスの場合、オプションのオブジェクトは空です。
オプションでの値の処理
オプションのオブジェクトがあるので、対応するメソッドを呼び出して、オプションのオブジェクトの値が存在するかどうかを処理できます。 null検出と比較して、このようなifpresent()メソッドを使用できます。
オプション<SoundCard> SoundCard = ...; SoundCard.ifpresent(System.out :: println);
これにより、null検出を行う必要はありません。オプションのオブジェクトが空の場合、情報は印刷されません。
isPresent()メソッドを使用して、オプションのオブジェクトが実際に存在するかどうかを確認することもできます。さらに、存在する場合、オプションのオブジェクトに含まれる値を返すget()メソッドもあります。それ以外の場合、nosuchelementexceptionがスローされます。これらの2つの方法は、例外を避けるために、次のように一緒に使用できます。
if(soundcard.ispresent()){system.out.println(soundcard.get());}ただし、この方法は推奨されません(NULL検出と比較して改善はありません)。以下では、通常の作業方法について説明します。
デフォルト値と関連操作を返します
NULLに遭遇した場合、定期的な操作は、デフォルト値を返すことです。これを使用して実装することができます。
soundcard soundcard = maybesoundcard!= null? MayBesoundCard:new SoundCard( "Basic_sound_card");
オプションのオブジェクトを使用する場合、 orElse()を使用してオーバーライドできます。オプションが空の場合、 orElse()デフォルト値を返すことができます:
SoundCard SoundCard = MayBesoundCard.orelse(new SoundCard( "defaut"));
同様に、オプションが空の場合、Orelsethrow()を使用して例外をスローできます。
soundcard soundcard = maybesoundcard.orelsethrow(IllegalStateException :: new);
フィルターを使用して特定の値をフィルタリングします
多くの場合、オブジェクトメソッドを呼び出して、その特性を判断します。たとえば、USBポート番号が特定の値であるかどうかを確認する必要がある場合があります。安全上の理由から、USBを指す医療用途がnullかどうかを確認し、次のようにgetVersion()メソッドを呼び出す必要があります。
usb usb = ...; if(usb!= null && "3.0" .equals(usb.getversion())){system.out.println( "ok");}オプションを使用する場合、フィルター関数を使用して書き換えることができます。
optional <usb> mayceusb = ...;たぶんusb.filter(usb-> "3.0" .equals(usb.getversion()).ifpresent(() - > system.out.println( "ok"));
フィルター方式には、パラメーターとして反対の述語が必要です。オプションの値が存在し、予測を満たす場合、フィルター関数は条件を満たす値を返します。それ以外の場合、空のオプションオブジェクトが返されます。
MAPメソッドを使用して、データを抽出および変換します
一般的なパターンは、オブジェクトの一部のプロパティを抽出することです。たとえば、SoundCardオブジェクトの場合、USBオブジェクトを取得してからバージョン番号を決定する必要がある場合があります。通常、私たちの実装は次のようなものです:
if(soundcard!= null){usb usb = soundcard.getusb(); if(usb!= null && "3.0" .equals(usb.getversion()){system.out.println( "ok");}}} MAPメソッドを使用して、この検出ヌルをオーバーライドしてから、オブジェクトタイプのオブジェクトを抽出できます。
オプション<USB> usb = maybesoundcard.map(soundcard :: getUsb);
これは、ストリームを使用してマップ関数を使用するのと同じです。ストリームを使用するには、パラメーターとして関数をマップ関数に渡す必要があり、通過した関数はストリーム内の各要素に適用されます。空間と時間をストリーミングするとき、何も起こりません。
オプションに含まれる値は、渡された関数によって変換されます(ここでは、サウンドカードからUSBを取得する関数です)。オプションのオブジェクトが時空の場合、何も起こりません。
次に、MAPメソッドとフィルターメソッドを組み合わせて、3.0ではなくUSBバージョン番号でサウンドカードをフィルタリングします。
maybesoundcard.map(soundcard :: getusb).filter(usb-> "3.0" .equals(usb.getversion()).ifpresent(() - > system.out.println( "ok"));
このようにして、私たちのコードは、ヌル検出なしで、最初に与えたもののように見え始めます。
フラットマップ関数を使用してオプションオブジェクトを渡します
ここで、オプションを使用してコードをリファクタリングする方法の例が導入されました。では、次のコードを安全な方法でどのように実装する必要がありますか?
string version = computer.getSoundCard()。getUsb()。getVersion();
上記のコードはすべて、1つのオブジェクトから別のオブジェクトを抽出していることに注意してください。これはマップ関数を使用して実装できます。前の記事では、コンピューターにOptional<Soundcard>オブジェクトを設定し、SoundCardにはOptional<USB>オブジェクトが含まれているため、この方法でコードをリファクタリングできます。
string version = computer.map(computer :: getsoundCard).map(soundcard :: getUsb).map(usb :: getversion).orelse( "nown");
残念ながら、上記のコードはエラーをコンパイルしているので、なぜですか?コンピューター変数はOptional<Computer>のタイプであるため、マップ関数を呼び出すのに問題はありません。ただし、 getSoundcard()メソッドはOptional<Soundcard>オブジェクトを返します。これは、 Optional<Optional<Soundcard>>のタイプのオブジェクトを返します。 2番目のマップ関数が呼び出された後、 getUSB()関数への呼び出しが違法になります。
次の図は、このシナリオを説明しています。
マップ関数のソースコードの実装は次のとおりです。
public <u> optional <u> map(function <?super t、?extends u> mapper){objects.requirenonnull(mapper); if(!ispresent())return empty(); else {return optional.ofnullable(mapper.apply(value)); }}マップ関数がOptional.ofNullable()を再度呼び出し、 Optional<Optional<Soundcard>>が返されることがわかります。
オプションは、オプションオブジェクトの値(マップ操作など)の値を変換し、2レベルのオプションを1つに圧縮するように設計されたFlatMap関数を提供します。次の図は、マップとフラットマップを呼び出すことによるタイプ変換のオプションオブジェクトの違いを示しています。
だから私たちはこれを書くことができます:
string version = computer.flatmap(computer :: getSoundCard).flatmap(soundcard :: getUsb).map(usb :: getversion).orelse( "nown");
最初のフラットマップは、返品がオプションOptional<Optional<Soundcard>>ではなくオプションのOptional<Soundcard>であることを保証し、2番目のフラットマップが同じ関数を実装して、返品がOptional<USB>になるようにします。 getVersion()オプションのオブジェクトの代わりに文字列オブジェクトを返すため、 map()は3回目と呼ばれます。
最終的に、使用したばかりのネストされたヌルチェックのugいコードを書き直しました。これは非常に読みやすく、ヌルポインターの例外の発生も回避します。
要約します
この記事では、Java 8が提供する新しいクラスjava.util.Optional<T>を採用します。このクラスの当初の意図は、ヌル参照を置き換えることではなく、デザイナーがより良いAPIを設計できるようにすることです。関数の署名を読んで、関数が存在するかどうかの値を受け入れるかどうかを知ります。さらに、オプションではオプションをオンにして、値が存在するかどうかを処理することができます。これにより、コードは潜在的なヌルポインターの例外を回避できます。
さて、上記はこの記事のコンテンツ全体です。この記事の内容には、すべての人の研究や仕事に特定の参照値があることを願っています。ご質問がある場合は、メッセージを残してコミュニケーションをとることができます。 wulin.comへのご支援ありがとうございます。