今更ながらJava8についてまとめてみた

今更ながらJava8についてまとめてみた:


はじめに

Java11が公式リリースされて2か月くらいが経ちました。

JDKの有償化など色々な意味で変化していくJavaですが、

ここで基本に立ち返って、Java8についてまとめてみました。

主によく使用する機能について記載していきます。

因みに筆者は前の現場まではJava7しか経験がなく、

Java8歴は半年くらいです。


よく使用する追加された機能

  • Lambda(ラムダ式)
  • Optional
  • Stream
それでは詳しく見ていきましょう。


Lambda(ラムダ式)

別名ではクロージャ(Closure)とも呼ばれているらしいです。(そう呼ぶ人間を見たことありませんが)

どんなものか実際の記述を見てみましょう。

Sample.java
(x, y) -> {return x + y;} 
/* 
  (実装するメソッドの引数) -> {処理} 
*/ 
実際に上記を走らせると、x + yの結果がresultに入ります。

…だからなんやねん、て感じですよね。


そもそもラムダ式とは

そもそもラムダ式が何なのかというと、
関数型インターフェースの実装を式として簡潔に記述できる機能です。

…また関数型インターフェースというよくわからない言葉が出てきました。

これを大まかに言いますと、抽象メソッドが1つしか実装されていないインターフェースのこと1と考えてください。

なのでもう少し細かくまとめますと、
抽象メソッドが1つしか実装されていないインターフェースの実装を簡潔に記述できる機能となります。


どんな時にラムダ式の旨味を感じるか

例えば以下のような関数型インターフェースを実装してみましょう。

Sample.java
@FunctionalInterface 
public interface SampleInterface { 
  void sayHi(); 
} 
普通にインターフェースを実装してみます。

SampleImpl1.java
public class SampleImple1 { 
 
  public void greeting() { 
 
    SampleInterface sample = new SampleInterface() {  // インターフェースを実装 
      void sayHi() { 
        System.out.println("Hi!");  // 実装したい処理を記述 
      } 
    } 
 
    sample.sayHi();                 // 処理実行 
  } 
} 
挨拶したいだけなのに、わざわざnewして処理を実装しないといけないのが非常に億劫ですね。

ではラムダ式を使ってみます。

SampleImpl2.java
public class SampleImpl2 { 
 
  public void greeting(String name) { 
    SampleInterface sample = () -> System.out.println("Hi!"); // 実装と処理記述をまとめてできる 
    sample.sayHi():                                           // 処理実行 
  } 
 
} 
大分すっきりしたと思いませんか?

簡潔に書けることは実装者にとって優しいのはもちろんですが、冗長な記述が不要になるので読み手にとっても分かりやすいコードになり、

プロジェクト全体にも良い影響を与えられます。

こうやって記事で目にしたり書籍で読むより、実際にコードを書いてみるとより理解しやすいと思いますので、

まだ書いたことがない、現場でも見たことがない方はぜひ書いてみてください!!


Optional

今でもかなりお世話になっているOptional。個人的には一番便利さを感じています。

一体どんな機能かというと、そのオブジェクトがnullかもしれないことを示してくれる機能です。

実際の細かい機能(メソッド)については公式リファレンスに譲りますが、

今回紹介するコードを読むためには以下のメソッドだけ予習していただければと思います。

  • ofNullable()
  • orElseThrow()
  • empty()
実際に記述と旨味を感じるシーンを見てみましょう。


実際の記述と旨味を感じるシーン

では例えばDB参照を行い、何も取得できなかった場合に独自例外を投げたいケースを見てみましょう。

まずはOptionalを使わなかった場合。

OptionalService1.java
SampleEntity sampleEntity = sampleDao.selectById(String id);                    // DB参照を実行 
  if (isNull(sampleEntity)) {                                                     // エンティティがnullだったら 
    throw new ApplicationException("errorCode", "DBから値が取得できませんでした");   // 例外を投げる 
  } 
比較的シンプルな部類ですが、わざわざif文を書かなきゃいけないのが嫌ですね。

この例だとネストを気にしなくてもよいですが、

現場コードだとすでにネストが結構深かったりしてif分岐すら煩わしく感じる時があります。

それではOptionalを使いましょう。

OptionalService2.java
Optional<SampleEntity> sampleEntity = Optional.ofNullable(sampleDao.selectById(String id)) 
                                             .orElseThrow(() -> new ApplicationException("errorCode", "DBから値が取得できませんでした")); 
…なんと一行で終わりました。ちょっと長くはなっちゃいましたが。。

それとちゃっかりラムダ式出てきます。便利だから出てきちゃう。仕方ない。

以下、簡単な解説です。


  1. ofNullable()を使用して、sampleDao.selectById()の戻り値がnullならOptional.empty()を、何か取得できていればOptional<SampleEntity>のオブジェクトを返してくれます。

  2. orElseThrow()で取得結果がOptional.empty()の時に例外をスローしてくれます。
つまりここで例外がスローされなかったら、sampleEntityはOptional.empty()ではないことが確定しています。分かりやすい!

先ほど紹介したリファレンスをちょこっと除いた方なら分かるかと思いますが、
maporElseGetなどなど、他にも便利な機能が提供されています。がしがし使っていきましょう!


注意点

先述した通り、Optionalはnullかもしれないことを表現してくれます。

その方法はOptionalでラップする際、または中の値を取り出す際のメソッドで表現します。

  • Optionalにラップするメソッド

    • of…ラップするオブジェクトがnullの場合に例外をスローする
    • ofNullable
  • Optionalから値を取り出すメソッド

    • get…取り出そうとしたOptionalがemptyだった場合に例外をスローする
    • orElseGet
    • orElseThrow
そのため、絶対にnullがあり得ないオブジェクト(例えばDB制約でNOT NULLなカラムなど)に対してofNullableは使用しないで、ofを使用するようにしましょう。

それにより後からそのコードを見た人が「この項目はnullの可能性がないんだな」と理解できます

不要なテストも書かずに済みます。

結局どんなに便利な機能が提供されても、使う人が理解していないと効果を発揮しません。(戒め)


Stream

名前がかっこいいですね。

Streamは配列やListの操作を簡潔に書ける機能です。

またまた細かい機能は公式リファレンスに譲らせていただきまして、

以下について予習していただければと思います。

  • map()
  • forEach()


実際の記述と旨味を感じるシーン

DBから一括取得できた名前を順に出力する処理があったとしましょう。

拡張for文でやってみます。

ForSample.java
for (SampleEntity entity : entityList) {  // Listの中身を一つずつ取得 
    System.out.println(entity.getName()); 
  } 
冷静に考えなくても人の名前を出力する処理なんてあんまりないと思いますが。例なので。

またネスト深くなっちゃいますね。嫌いです。

Streamを使いましょ。

StreamSample.java
entityList.stream.map(x -> x.getName()).forEach(System.out::println); 
これまた1行で終わりました。それに短い。

簡単な説明です。


  1. map()でListから取り出したEntityから名前を取得しストリームに返します。(中間操作)

  2. forEach()がストリームの各要素に対して`System.out::println'を実行します。(終端操作)
中間操作で配列、リストの要素に対して処理を行いストリームに格納、終端操作でストリームに対してまとめて実行すると大まかに認識していただければ大丈夫かと思います。

他にもfilter()sorted()distinct()など便利すぎるメソッドがありますので、

そちらも使ってみてください。


最後に

改めて書いてみると、新機能というよりは、既存機能の記述を簡単にできるものが多く追加され、

またそれを実際によく使っているんだなと思いました。

えらい人たちもシンプルが一番!と思っているようですし、

これらが読んだり書けたりするだけでよりエンジニアらしくなるんではないかと思います!

最後までお読みいただきありがとうございました!

ご指摘などコメントお待ちしております!!



  1. 厳密には異なります。詳細を知りたい方はこちらをご覧ください。 


コメント

このブログの人気の投稿

投稿時間:2021-06-17 22:08:45 RSSフィード2021-06-17 22:00 分まとめ(2089件)

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2021-06-17 05:05:34 RSSフィード2021-06-17 05:00 分まとめ(1274件)