Java8について調べたこと

# 概要
「1.4」で止まってたJavaの知識をバージョンアップするため
Java7,8,9について学習しています。
今回は「8」で追加された機能について調べた事のみを記載します。

# 調べたこと
## Optional
戻り値にnullが含まれる事があるかどうかを明示的に表すことが出来るようになりました。
また、取り出した時に値が無かった場合の処理も設定できます。

```java
public Optional<String> getGyunyu(String name) {
  Map<String, String> gyunyu = new HashMap<>();
  gyunyu.put("牛乳", "コーヒー牛乳");
  return Optional.ofNullable(gyunyu.get(name));
}
```

```java
Optional<String> gyunyu = getGyunyu("紅茶");
System.out.println(gyunyu.orElse("牛乳じゃない")); // 「牛乳じゃない」が出力される
```

- ofNullable(T value)　：　Optionalを生成する。値がnullでない場合はその値を記述するOptionalを返し、それ以外の場合は空のOptionalを返す。
- orElse(T other)　：　存在する場合は値を返し、それ以外の場合はotherを返す。

`isPresent()`メソッドを使うことで値の存在チェックを行うことも出来ます。

```java
Optional<String> gyunyu = getGyunyu("紅茶");
if (!gyunyu.isPresent()) {
  System.out.println("牛乳じゃない");
}
```

これだと、`if(戻り値 == null)`と大差ない気がしますが、nullだったら値を取り直すとか
別の処理を追加で行いたい時に使用出来そう。

[Optionalクラス](https://docs.oracle.com/javase/jp/8/docs/api/java/util/Optional.html)

## interface
interfaceが実装を持てるようになりました。
defaultキーワードを記述する事で、実装を持つメソッドを記述する事ができます。

```java:Gyunyu.java
public interface Gyunyu {
  public default void print() {
    System.out.println("牛乳");
  }
}
```

```java:Coffee.java
public class Coffee implements Gyunyu {
  // printメソッドをオーバーライドしてないけどコンパイルエラーにならない
}
```

```java
Gyunyu gyunyu = new Coffee();
gyunyu.print();
```

`Gyunyu`インターフェースを継承した`Milk`インターフェースを作成し、
`Ichigo`クラスに実装して`print()`メソッドを呼び出すと、

```java:Milk.java
public interface Milk extends Gyunyu {
  public default void print() {
    System.out.println("ミルク");
  }
}
```

```java:Ichigo.java
public class Ichigo implements Milk {
}
```

```java
Gyunyu gyunyu = new Ichigo();
gyunyu.print(); //「ミルク」が出力される
```

メソッドを呼び出しているクラスから最も近い位置にあるメソッド（今回は`Milk`インターフェースの`print()`）が実行されます。

## Date and Time API
### 現在日時を取得
`now()`メソッドを使うことで現在日時を取得出来ます。

```java
//タイムゾーン情報なし
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime.getYear());
System.out.println(localDateTime.getMonthValue());
System.out.println(localDateTime.getDayOfMonth());
System.out.println(localDateTime.getHour());
System.out.println(localDateTime.getMinute());
System.out.println(localDateTime.getSecond());
System.out.println(localDateTime.getNano());
		
//タイムゾーン情報あり
ZonedDateTime zoneDatetime = ZonedDateTime.now();
System.out.println(zoneDatetime.getYear());
System.out.println(zoneDatetime.getMonthValue());
System.out.println(zoneDatetime.getDayOfMonth());
System.out.println(zoneDatetime.getHour());
System.out.println(zoneDatetime.getMinute());
System.out.println(zoneDatetime.getSecond());
System.out.println(zoneDatetime.getNano());
System.out.println(zoneDatetime.getZone().getId()); // 「Asia/Tokyo」が出力される
```

### 和暦で表示

```java
JapaneseDate japaneseDate = JapaneseDate.now();
System.out.println(japaneseDate.getEra());　//「Heisei」が出力される
System.out.println(japaneseDate.get(ChronoField.YEAR_OF_ERA)); //「29」が表示される
```

### 加算・減算

```java
LocalDateTime localDateTime = LocalDateTime.now();

LocalDateTime tomorrow = localDateTime.plusDays(1);
System.out.println(tomorrow.getDayOfMonth()); // 11月29日に実行した場合は「30」が出力される

LocalDateTime yesterday = localDateTime.minusDays(1);
System.out.println(yesterday.getDayOfMonth()); // 11月29日に実行した場合は「28」が出力される
```
- 年の加算を行いたい場合は、`plusYears(long years)`を用いる
- 年の減算を行いたい場合は、`minusYears(long years)`を用いる
- その他の加算・減算については、[LocalDateTimeクラス](https://docs.oracle.com/javase/jp/8/docs/api/java/time/LocalDateTime.html)

## Base64エンコード・デコード
標準でサポートされるようになりました。

```java
String coffee = "コーヒー牛乳";
		
Encoder encoder = Base64.getEncoder();
String encodeCoffee = encoder.encodeToString(coffee.getBytes());
System.out.println(encodeCoffee); // 「44Kz44O844OS44O854mb5Lmz」が出力される
		
Decoder decoder = Base64.getDecoder();
String decodeCoffee = new String(decoder.decode(encodeCoffee));
System.out.println(decodeCoffee); // 「コーヒー牛乳」が出力される
```

## 文字列の連結
`String.join()`を使うことで、複数の文字列を任意の文字列で結合する事が簡単にできるようになりました。

```java
String[] gyunyu = {"コーヒー牛乳", "れもん牛乳", "いちご牛乳"};
System.out.println(String.join("###", gyunyu)); //「コーヒー牛乳###れもん牛乳###いちご牛乳」で出力される
```

```java
List<String> gyunyu = new ArrayList<>();
gyunyu.add("コーヒー牛乳");
gyunyu.add("れもん牛乳");
gyunyu.add("いちご牛乳");
System.out.println(String.join("###", gyunyu)); //「コーヒー牛乳###れもん牛乳###いちご牛乳」で出力される
```

`java.util.StringJoiner`クラスでも同様の結合が出来ます。

```java
StringJoiner gyunyu = new StringJoiner("###");
gyunyu.add("コーヒー牛乳");
gyunyu.add("れもん牛乳");
gyunyu.add("いちご牛乳");
System.out.println(gyunyu); //「コーヒー牛乳###れもん牛乳###いちご牛乳」で出力される
```

StringJoinerクラスは、プレフィックスとサフィックスを指定して結合することも出来ます。

```java
StringJoiner gyunyu = new StringJoiner(" > ", "[好き]", "[嫌い]");
gyunyu.add("コーヒー牛乳");
gyunyu.add("れもん牛乳");
gyunyu.add("いちご牛乳");
System.out.println(gyunyu); //「[好き]コーヒー牛乳 > れもん牛乳 > いちご牛乳[嫌い]」で出力される
```

## ラムダ式
下記のような文法で記述される。

```java
(メソッドの引数) -> {処理}
```

ラムダ式を用いる事で、処理を簡潔に書けるようになったらしい・・・
下記のようなインターフェースと処理があった場合

```java
public interface Gyunyu {	
  public void output(String v);
}
```

```java
Gyunyu gyunyu = new Gyunyu() {
  @Override
  public void output(String v) {
    System.out.println(v);
  }
};
gyunyu.output("コーヒー牛乳"); //「コーヒー牛乳」と出力される
```

ラムダ式を用いると、下記のような記述にする事ができます。

```java
Gyunyu gyunyu = (String v) -> { System.out.println(v); };
gyunyu.output("コーヒー牛乳"); //「コーヒー牛乳」と出力される
```

さらに、引数の型は推論されるので省略可能。引数が1つの場合は、括弧「()」も省略可能。
式が1つの場合、波括弧「{}」も省略可能なので下記のような記述にする事も出来ます。

```java
Gyunyu gyunyu = v -> System.out.println(v);
gyunyu.output("コーヒー牛乳");
```

# 関数型インターフェース
抽象メソッドをひとつだけ宣言したインターフェースのこと。
ラムダ式のところに書いてあるGyunyuインターフェースを関数型インターフェースにすると、
下記のような記述になります。

```java:Gyunyu.java
@FunctionalInterface
public interface Gyunyu {
   public void output(String s);
}
```

`@FunctionalInterface`アノテーションを付けることで、関数型インターフェースである事を明示的に表すことができ、
関数型インターフェースの条件を満たしていない場合にコンパイルエラーを出すことが出来るようになります。

# メソッド参照

メソッド参照は、引数を省略できるため、ラムダ式よりも記述量を減らす事ができる。
staticメソッドや、インスタンスメソッドを実行させる方法。

- 表記方法

　`クラス名（インスタンス名）::メソッド名`

## インスタンスメソッドを参照する場合

```java
Gyunyu gyunyu = System.out::println;
gyunyu.output("コーヒー牛乳"); //「コーヒー牛乳」と出力される
```

上記をラムダ式で記述すると、下記のように記述する事ができます。

```java
Gyunyu gyunyu = v -> System.out.println(v);
gyunyu.output("コーヒー牛乳");
```

参照したいメソッドが自クラスの場合は、`this`を用いる

```java:Main.java
public void println(String v) {
  System.out.println(v);
}

public void execute() {
  List<String> gyunyu = Arrays.asList("コーヒー牛乳", "いちご牛乳", "れもん牛乳");
  gyunyu.forEach(this::println);
}

public static void main(String[] args) {
  (new Main()).execute();
}
```


## staticメソッドを参照する場合
```java:Main.java
public static void output(String v) {
  System.out.println(v);
}

public static void main(String[] args) {
  List<String> gyunyu = Arrays.asList("コーヒー牛乳", "いちご牛乳", "れもん牛乳");
  gyunyu.forEach(Main::output);
}
```

## Stream API
値の集計、データを使った処理を行うことが出来るAPI。
基本的な処理の流れとしては、
1. ストリーム生成
2. 中間処理
3. 終端処理
のようになる。

### ストリーム生成
- 配列の場合

```java
String[] gyunyu1 = {"コーヒー牛乳", "コーヒーミルク", "いちご牛乳", "いちごミルク", "れもん牛乳", "レモンミルク"};
Stream<String> stream1 = Arrays.stream(gyunyu1);

String[] gyunyu2 = {"メロン牛乳", "りんご牛乳", "メロン牛乳", "ぶどう牛乳"};
Stream<String> stream2 = Stream.of(gyunyu2);
```

- Collectionの場合

```java
List<String> gyunyu3 = Arrays.asList("フルーツ", "ばなな", "すいか");
Stream<String> stream3 = gyunyu3.stream();
```
- Mapの場合

```java
Map<String, String> gyunyu4 = new HashMap<>();
gyunyu4.put("1", "もも牛乳");
gyunyu4.put("2", "びわ");
gyunyu4.put("3", "いちじく牛乳");
Stream<Entry<String, String>> stream4 = gyunyu4.entrySet().stream();
```

参考：[ストリーム生成](http://pppurple.hatenablog.com/entry/2016/09/19/002315)

### 中間操作

Steamの絞り込み、編集などを行い、新しいStreamを作成するための処理。


- 要素を絞り込む場合
 - `filter`メソッドを用いる事で指定した要素のみのStreamを取得できます。

```java
// 牛乳を含む文字列だけを抽出したい
String[] gyunyu1 = {"コーヒー牛乳", "コーヒーミルク", "いちご牛乳", "いちごミルク", "れもん牛乳", "レモンミルク"};
Stream<String> stream1 = Arrays.stream(gyunyu1);
stream1.filter(s -> s.contains("牛乳"));
```

- 重複した要素を除きたい場合
  - `distinct`メソッドを用いる事で重複した要素を取り除いたStreamを取得できます。


```java
// 重複している牛乳を除きたい
String[] gyunyu2 = {"メロン牛乳", "りんご牛乳", "メロン牛乳", "ぶどう牛乳"};
Stream<String> stream2 = Stream.of(gyunyu2);
stream2.distinct();
```

- 要素を変換したい場合
 - `map`メソッドを用いる事で要素を更新したStreamを取得できます。

```java
// 牛乳を付け加えたい
List<String> gyunyu3 = Arrays.asList("フルーツ", "ばなな", "すいか");
Stream<String> stream3 = gyunyu3.stream();
stream3.map(s -> s + "牛乳");
```

参考：[中間操作](http://javazuki.blog.jp/articles/javase/stream-intermediate.html)

### 終端処理

終端処理を実行すると中間処理が評価される。

- 要素数を取得したい場合
 - `count`メソッドを用いることで要素数を取得する事ができます。

```java
// 牛乳を含む件数を出力したい
Map<String, String> gyunyu4 = new HashMap<>();
gyunyu4.put("1", "もも牛乳");
gyunyu4.put("2", "びわ");
gyunyu4.put("3", "いちじく牛乳");
Stream<Entry<String, String>> stream4 = gyunyu4.entrySet().stream();
System.out.println(stream4.filter(s -> s.getValue().contains("牛乳")).count()); // 「2」が出力される
```

- 要素を1つずつ取り出して操作したい場合
 - `forEach`メソッドを用いることで副作用を発生させる事ができます。

```java
String[] gyunyu1 = {"コーヒー牛乳", "コーヒーミルク", "いちご牛乳", "いちごミルク", "れもん牛乳", "レモンミルク"};
Stream<String> stream1 = Arrays.stream(gyunyu1);
stream1.filter(s -> s.contains("牛乳")).forEach(s -> System.out.println(s));
```

参考：[終端処理](http://javazuki.blog.jp/articles/javase/stream-terminal.html)

# 終わりに
業務で担当しているサービスが、Java5,6で動いているため、8を業務で使う機会がありませんが、
機会を作ってバージョンアップさせたいと思っています。
StreamAPIとラムダ式に未だ馴染めていないので、この2つの機能について、もっと学習が必要だと感じています。