2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Java 8のOptionalをSwiftと比較する

Last updated at Posted at 2020-03-26

概要

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

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

Swift

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

Swift
let wrappedValue: String? = nil
let defaultValue = "default"
let value = wrappedValue ?? defaultValue
print(value)
// "default"
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では演算子ではなくorElseorElseGetというメソッドです。orElseではデフォルト値そのものを、orElseGetではデフォルト値を提供するSupplier<? extends T>型のオブジェクトを渡します。しかしいずれのメソッドもラップしている型を返すので、SwiftのようにorElse複数のOptional型をつなぐことはできません

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
Optional<String> otherWrappedValue = Optional.of("other");
// これはできない
// String unwrappedValue = wrappedValue.orElse(otherWrappedValue).orElse(defaultValue);

短絡評価

Swift

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

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
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
// orElseは短絡評価しない
String value = Optional.of("non null").orElse(hoge());
System.out.println(value);
// "called"
// "non null"

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

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

// △ - 必ずfetchValue()が呼ばれるので少なくともパフォーマンスに影響
String value = wrappedValue.orElse(fetchValue());
// ◯ - 短絡評価なので中身があれば呼ばれない
String value = wrappedValue.orElseGet(this::fetchValue);
Java
// △ - 中身があると無駄なオブジェクト生成となる
Person person = wrappedPerson.orElse(new Person());
// ◯ - 短絡評価なので中身があればオブジェクトは生成されない
Person person = wrappedPerson.orElseGet(Person::new);

強制アンラップ(Unconditional Unwrapping)

Swift

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

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

Java 8

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

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
let intString: String? = "42"
let percentage: String? = intString.map { "\($0)%" }
print(String(describing: percentage))
// Optional("42%")

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

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でも同様にmapflatMapというメソッドを使います。使い分けも同様です。

Java
Optional<String> intString = Optional.of("42");
Optional<String> percentage = intString.map( str -> str + "%" );
System.out.println(percentage);
// Optional[42%]
Java
Optional<String> intString = Optional.of("42");
Optional<Integer> integer = intString.flatMap(this::intFromString);
System.out.println(integer);
// Optional[42]

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

Swift

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

Swift
let wrappedValue: String? = "value"
if let value: String = wrappedValue {
    print(value)
} else {
    print("no value")
}
// "value"
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
Optional<String> wrappedValue = Optional.of("value");
wrappedValue.ifPresent( str -> {
    System.out.println(str);
});
// "value"

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

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
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
Optional<String> wrappedValue = Optional.of("value");
wrappedValue.ifPresent( str -> {
    System.out.println(str);
})// .orElse( () -> { // これはできない
//     System.out.println("no value");
// })
;

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

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

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

Swift

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

Swift
let wrappedValue: String? = "value"
let uppercased: String? = wrappedValue?.uppercased()
print(String(describing: uppercased))
// Optional("VALUE")
Swift
class Hoge {
    func hoge() { print("hoge") }
}

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

Java 8

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

Java
Optional<String> wrappedValue = Optional.of("value");
Optional<String> uppercased = wrappedValue.map(String::toUpperCase);
System.out.println(uppercased);
// Optional[VALUE]
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
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 ※ 非短絡評価
orElseGet
強制アンラップ ! get
変換 map
flatMap
map
flatMap
オプショナルバインディング if let
guard let
ifPresent
もしくはisPresentで分岐した後にgetで強制アンラップ
オプショナルチェイニング ? Optional型を返すならflatMap
それ以外を返すならmap
値を返さないならifPresent

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

オマケ - Java 9のOptional

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

or

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

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

let otherWrappedValue: String? = nil
let value = wrappedValue ?? otherWrappedValue ?? defaultValue
print(value)
// "default"
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
let wrappedValue: String? = "value"
if let value = wrappedValue {
    print(value)
} else {
    print("no value")
}
// "value"
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"
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?