Java 8のOptionalをSwiftと比較する

# 概要

本記事では[OptionalのApple公式ドキュメント](https://developer.apple.com/documentation/swift/optional)の概要に掲載されている項目を元にして、Swiftの`Optional`型とJava 8の`Optional`型を比較します。若干今更感のある内容にはなりますが個人的な備忘録も兼ねて投稿に至りました。
[記事の最後](#まとめ)にはそれぞれの記法の比較表も掲載しています。あわせてご覧ください。
なお本記事に掲載してあるスニペットは多少簡略化されているので、そのままコピペしても動かないものがあります。ご了承ください。

# Nil結合演算子(Nil-Coalescing Operator)

### Swift

中身があるならそれを、なければ指定したデフォルト値を返すように`Optinal`をアンラップします。`??`という演算子を使います。複数の`Optional`型をオペランドとしてつなぐこともできます。


```swift:Swift
let wrappedValue: String? = nil
let defaultValue = "default"
let value = wrappedValue ?? defaultValue
print(value)
// "default"
```

```swift:Swift
let wrappedValue: String? = nil
let defaultValue = "default"

let otherWrappedValue: String? = nil
// 右オペランドがOptionalである限り??でつなぐことができる
let value = wrappedValue ?? otherWrappedValue ?? defaultValue
print(value)
// "default"
```

### Java 8

Java 8では演算子ではなく`orElse`、`orElseGet`というメソッドです。`orElse`ではデフォルト値そのものを、`orElseGet`ではデフォルト値を提供する`Supplier<? extends T>`型のオブジェクトを渡します。しかしいずれのメソッドもラップしている型を返すので、Swiftのように`orElse`で**複数の`Optional`型をつなぐことはできません**。

```java:Java
Optional<String> wrappedValue = Optional.empty();
String defaultValue = "default";
String value = wrappedValue.orElse(defaultValue);
System.out.println(value);
// "default"

String otherValue = wrappedValue.orElseGet( () -> defaultValue );
System.out.println(otherValue);
// "default"
```

```java:Java
Optional<String> otherWrappedValue = Optional.of("other");
// これはできない
// String unwrappedValue = wrappedValue.orElse(otherWrappedValue).orElse(defaultValue);
```

## 短絡評価

### Swift

SwiftのNil結合演算子は短絡評価します。したがって左オペランドが`nil`でない限り右オペランドが評価されることはありません。

```swift:Swift
func hoge() -> String {
    print("called")
    return "hoge"
}

let value = nil ?? hoge()
print(value)
// "called"
// "hoge"

// 短絡評価なのでhoge()は呼ばれない
let value = "non nil" ?? hoge()
print(value)
// "non nil"
```

### Java 8

Java 8の`orElseGet `も短絡評価ですが、**`orElse`はしません**。デフォルト値そのものを渡しているので当然と言えば当然ですね。

```java:Java
public String hoge() {
    System.out.println("called");
    return "hoge";
}

String value = Optional.<String>empty().orElseGet(this::hoge);
System.out.println(value);
// "called"
// "hoge"

// 短絡評価なのでhoge()は呼ばれない
String value = Optional.of("non null").orElseGet(this::hoge);
System.out.println(value);
// "non null"
```

```java:Java
// orElseは短絡評価しない
String value = Optional.of("non null").orElse(hoge());
System.out.println(value);
// "called"
// "non null"
```

使い分けについてですが、デフォルト値生成にコストがかかる場合などには短絡評価する`orElseGet`を使うと良いでしょう。

```java:Java
public String fetchValue() {
    // 時間のかかる処理
    return result;
}

// △ - 必ずfetchValue()が呼ばれるので少なくともパフォーマンスに影響
String value = wrappedValue.orElse(fetchValue());
// ◯ - 短絡評価なので中身があれば呼ばれない
String value = wrappedValue.orElseGet(this::fetchValue);
```

```java:Java
// △ - 中身があると無駄なオブジェクト生成となる
Person person = wrappedPerson.orElse(new Person());
// ◯ - 短絡評価なので中身があればオブジェクトは生成されない
Person person = wrappedPerson.orElseGet(Person::new);
```


# 強制アンラップ(Unconditional Unwrapping)

### Swift

中身があるかどうかにかかわらず値をアンラップします。演算子`!`を使います。もし`nil`であった場合は実行時エラーを引き起こします。

```swift:Swift
let number = Int("42")!
print(number)
// 42
```

### Java 8

Java 8では`get`メソッドを使います。もし`null`であった場合は非検査例外である`NoSuchElementException`が投げられます。
※ なお、`intFromString`は説明の便宜上`Optional<Integer>`型を返していますが、通常整数を包む場合は`OptionalInt`型を使う方が良いでしょう。

```java:Java
public Optional<Integer> intFromString(String string) {
    try {
        return Optional.of(Integer.parseInt(string));
    } catch (NumberFormatException ignored) {
        return Optional.empty();
    }
}

int number = intFromString("42").get();
System.out.println(number);
// 42
```

# 変換

### Swift

値を変換します。`map`というメソッドを使います。中身があれば与えられた`(Wrapped) -> U`型のクロージャに従って値を変換し、`nil`であれば`nil`のまま流します。したがって`Optional`型を返します。

```swift:Swift
let intString: String? = "42"
let percentage: String? = intString.map { "\($0)%" }
print(String(describing: percentage))
// Optional("42%")
```

クロージャ内で`Optional`型を返したいときは、`(Wrapped) -> Optional<U>`型のクロージャを引数にとる`flatMap`メソッドを使います。

```swift:Swift
let intString: String? = "42"
// Int(String)はInt?を返す
let integer: Int? = intString.flatMap { Int($0) }
print(String(describing: integer))
// Optional(42)
```


### Java 8

Java 8でも同様に`map`、`flatMap`というメソッドを使います。使い分けも同様です。

```java:Java
Optional<String> intString = Optional.of("42");
Optional<String> percentage = intString.map( str -> str + "%" );
System.out.println(percentage);
// Optional[42%]
```

```java:Java
Optional<String> intString = Optional.of("42");
Optional<Integer> integer = intString.flatMap(this::intFromString);
System.out.println(integer);
// Optional[42]
```


# オプショナルバインディング(Optional Binding)

### Swift

安全なアンラップ手段として馴染み深いことと思います。`if`や`guard`といったキーワードと併せることで、中身がある場合とない場合とで処理を分岐することができます。

```swift:Swift
let wrappedValue: String? = "value"
if let value: String = wrappedValue {
    print(value)
} else {
    print("no value")
}
// "value"
```

```swift:Swift
let wrappedValue: String? = "value"
guard let value: String = wrappedValue else {
    print("no value")
    return
}
print(value)
// "value"
```


### Java 8

Java 8では`ifPresent`というメソッドを介して安全にアンラップすることができます。`Consumer<? super T>`型のオブジェクトを引数にとり、その中に中身があった場合の処理を記述します。

```java:Java
Optional<String> wrappedValue = Optional.of("value");
wrappedValue.ifPresent( str -> {
    System.out.println(str);
});
// "value"
```

しかしながら中身がない場合の処理を記述できる**メソッドはありません**。したがって両方の処理を記述したい場合は`isPresent`を使って中身の有無を確認する方法をとる必要があります。ただし、中身がある場合明示的に強制アンラップ`get`を記述しなければいけないため**Swiftと比較すると冗長です**。

```java:Java
Optional<String> wrappedValue = Optional.of("value");
if (wrappedValue.isPresent()) {
    // 冗長だが明示的に強制アンラップする必要がある
    String value = wrappedValue.get();
    System.out.println(value);
} else {
    System.out.println("no value");
}
// "value"
```

```java:Java
Optional<String> wrappedValue = Optional.of("value");
// カード節的に記述する
if (!wrappedValue.isPresent()) {
    System.out.println("no value");
    return;
}
// 冗長だが明示的に強制アンラップする必要がある
String value = wrappedValue.get();
System.out.println(value);
// "value"
```

```java:Java
Optional<String> wrappedValue = Optional.of("value");
wrappedValue.ifPresent( str -> {
    System.out.println(str);
})// .orElse( () -> { // これはできない
//     System.out.println("no value");
// })
;
```

また`ifPresent`に渡す処理のスコープはラムダ式内であるため、その処理内で呼び出し元のメソッドのリターンはできません。したがって早期リターンなどを行いたい場合は`isPresent`を使った方法をとる他ありません。

```java:Java
public boolean foo(Optional<String> wrappedValue) {
    // 値があるなら早期リターンする
    if (wrappedValue.isPresent) {
        String str = wrappedValue.get();
        // 何かする
        return true;
    }
    // 値がない場合の処理
    return result;
}
```

```java:Java
public boolean foo(Optional<String> wrappedValue) {
    // 値があるなら早期リターンする
    wrappedValue.ifPresent( str -> {
        // 何かする
//         return true; // スコープがこのラムダ式内にあるため、これはできない
    });
    // 値がない場合の処理
    return result;
}
```


# オプショナルチェイニング(Optional Chaining)

### Swift

中身があるときのみプロパティやメソッドへのアクセスを行います。後置演算子`?`を使います。

```swift:Swift
let wrappedValue: String? = "value"
let uppercased: String? = wrappedValue?.uppercased()
print(String(describing: uppercased))
// Optional("VALUE")
```

```swift:Swift
class Hoge {
    func hoge() { print("hoge") }
}

let wrappedValue: Hoge? = Hoge()
wrappedValue?.hoge()
// "hoge"
```

### Java 8

Java 8では先に紹介した`map`メソッドが使えます。対象がメソッドであればメソッド参照を用いて簡潔に書くことができます。ただし、対象が`Optional`型を返す場合には`flatMap`を使う必要があります。

```java:Java
Optional<String> wrappedValue = Optional.of("value");
Optional<String> uppercased = wrappedValue.map(String::toUpperCase);
System.out.println(uppercased);
// Optional[VALUE]
```

```java:Java
class User {
    final String id;
    Optional<String> mail = Optional.empty();
    User(String id) { this.id = id; }
}

Optional<User> wrappedUser = Optional.of(new User("user1"));
Optional<String> mail = wrappedUser.flatMap( user -> user.mail );
System.out.println(mail);
// Optional.empty
```

使い分けを含め[変換](#変換)のときと全く同じ記法ですね。Java 8ではラップしている値のフィールドやメソッドにアクセスするためだけの特別な記法やメソッドが用意されているわけではありません。
値を返さないメソッドへのアクセスは先に紹介した`ifPresent`を使います。

```java:Java
class Hoge {
    void hoge() { System.out.println("hoge"); }
}

Optional<Hoge> wrappedValue = Optional.of(new Hoge());
wrappedValue.ifPresent(Hoge::hoge);
// "hoge"
```

# まとめ

本記事ではSwiftでの`Optional`型の扱いを元に、Java 8と`Optional`型の比較を行いました。以下にそれぞれの記法の比較表を掲載します。特にオプショナルバインディングとオプショナルチェイニングについては両者の違いがよくあらわれています。Java 8のオプショナルバインディングでは分岐と同時にアンラップできない分Swiftより少し冗長です。またJava 8にはSwiftの`?`演算子のようなラップしている値にアクセスするための専用手段は用意されていません。

|  | Swift | Java |
|----|----|----|
| Nil結合演算子 | `??` | `orElse` ※ 非短絡評価 <br> `orElseGet` |
| 強制アンラップ | `!` | `get` |
| 変換 | `map` <br> `flatMap` | `map` <br> `flatMap` |
| オプショナルバインディング | `if let` <br> `guard let` | `ifPresent` <br> もしくは`isPresent`で分岐した後に`get`で強制アンラップ <br> |
| オプショナルチェイニング | `?` | `Optional`型を返すなら`flatMap` <br> それ以外を返すなら`map` <br> 値を返さないなら`ifPresent` |


もし何か間違いなどございましたらコメントにてご指摘ください。

## オマケ - Java 9のOptional

ここではJava 9で追加された`Optional`のメソッド、`or`と`ifPresentOrElse`を紹介します。Java 8の話は出てきません。完全なオマケです。

### or

中身がない場合に与えられた`Supplier<? extends Optional<? extends T>>`型のオブジェクトを実行して`Optional`型のデフォルト値を得ます。これはちょうどSwiftのNil結合演算子で右オペランドに`Optional`型を持ってきた場合と対応しています。
また**短絡評価をする**ため、中身がある限り与えられた`Supplier`オブジェクトが実行されることはありません。

```swift:Swift
let wrappedValue: String? = nil
let defaultValue = "default"

let otherWrappedValue: String? = nil
let value = wrappedValue ?? otherWrappedValue ?? defaultValue
print(value)
// "default"
```

```java:Java
// available Java 9 or later
Optional<String> wrappedValue = Optional.empty();
String defaultValue = "default";

Optional<String> otherWrappedValue = Optional.empty();
// Java 9以降ならデフォルト値にOptional型を指定できる
String value = wrappedValue.or( () -> otherWrappedValue ).orElse(defaultValue);
System.out.println(value);
// "default"
```

### ifPresentOrElse

中身がある場合とない場合とで処理を分岐することができます。中身がある場合には`Consumer<? super T>`型のオブジェクトが、ない場合には`Runnable`型のオブジェクトが実行されます。`null`だった場合の処理も記述できるので、Java 8と比べるとSwiftのオプショナルバイディングにかなり近づいています。

```swift:Swift
let wrappedValue: String? = "value"
if let value = wrappedValue {
    print(value)
} else {
    print("no value")
}
// "value"
```

```java:Java
// available Java 9 or later
Optional<String> wrappedValue = Optional.<String>empty();
wrappedValue.ifPresentOrElse( str -> {
    System.out.println(str);
}, () -> {
    System.out.println("no value");
});
// "no value"
```