概要
本記事ではOptionalのApple公式ドキュメントの概要に掲載されている項目を元にして、SwiftのOptional
型とJava 8のOptional
型を比較します。若干今更感のある内容にはなりますが個人的な備忘録も兼ねて投稿に至りました。
記事の最後にはそれぞれの記法の比較表も掲載しています。あわせてご覧ください。
なお本記事に掲載してあるスニペットは多少簡略化されているので、そのままコピペしても動かないものがあります。ご了承ください。
Nil結合演算子(Nil-Coalescing Operator)
Swift
中身があるならそれを、なければ指定したデフォルト値を返すようにOptinal
をアンラップします。??
という演算子を使います。複数のOptional
型をオペランドとしてつなぐこともできます。
let wrappedValue: String? = nil
let defaultValue = "default"
let value = wrappedValue ?? defaultValue
print(value)
// "default"
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
型をつなぐことはできません。
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"
Optional<String> otherWrappedValue = Optional.of("other");
// これはできない
// String unwrappedValue = wrappedValue.orElse(otherWrappedValue).orElse(defaultValue);
短絡評価
Swift
SwiftのNil結合演算子は短絡評価します。したがって左オペランドがnil
でない限り右オペランドが評価されることはありません。
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
はしません。デフォルト値そのものを渡しているので当然と言えば当然ですね。
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"
// orElseは短絡評価しない
String value = Optional.of("non null").orElse(hoge());
System.out.println(value);
// "called"
// "non null"
使い分けについてですが、デフォルト値生成にコストがかかる場合などには短絡評価するorElseGet
を使うと良いでしょう。
public String fetchValue() {
// 時間のかかる処理
return result;
}
// △ - 必ずfetchValue()が呼ばれるので少なくともパフォーマンスに影響
String value = wrappedValue.orElse(fetchValue());
// ◯ - 短絡評価なので中身があれば呼ばれない
String value = wrappedValue.orElseGet(this::fetchValue);
// △ - 中身があると無駄なオブジェクト生成となる
Person person = wrappedPerson.orElse(new Person());
// ◯ - 短絡評価なので中身があればオブジェクトは生成されない
Person person = wrappedPerson.orElseGet(Person::new);
強制アンラップ(Unconditional Unwrapping)
Swift
中身があるかどうかにかかわらず値をアンラップします。演算子!
を使います。もしnil
であった場合は実行時エラーを引き起こします。
let number = Int("42")!
print(number)
// 42
Java 8
Java 8ではget
メソッドを使います。もしnull
であった場合は非検査例外であるNoSuchElementException
が投げられます。
※ なお、intFromString
は説明の便宜上Optional<Integer>
型を返していますが、通常整数を包む場合はOptionalInt
型を使う方が良いでしょう。
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
型を返します。
let intString: String? = "42"
let percentage: String? = intString.map { "\($0)%" }
print(String(describing: percentage))
// Optional("42%")
クロージャ内でOptional
型を返したいときは、(Wrapped) -> Optional<U>
型のクロージャを引数にとるflatMap
メソッドを使います。
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
というメソッドを使います。使い分けも同様です。
Optional<String> intString = Optional.of("42");
Optional<String> percentage = intString.map( str -> str + "%" );
System.out.println(percentage);
// Optional[42%]
Optional<String> intString = Optional.of("42");
Optional<Integer> integer = intString.flatMap(this::intFromString);
System.out.println(integer);
// Optional[42]
オプショナルバインディング(Optional Binding)
Swift
安全なアンラップ手段として馴染み深いことと思います。if
やguard
といったキーワードと併せることで、中身がある場合とない場合とで処理を分岐することができます。
let wrappedValue: String? = "value"
if let value: String = wrappedValue {
print(value)
} else {
print("no value")
}
// "value"
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>
型のオブジェクトを引数にとり、その中に中身があった場合の処理を記述します。
Optional<String> wrappedValue = Optional.of("value");
wrappedValue.ifPresent( str -> {
System.out.println(str);
});
// "value"
しかしながら中身がない場合の処理を記述できるメソッドはありません。したがって両方の処理を記述したい場合はisPresent
を使って中身の有無を確認する方法をとる必要があります。ただし、中身がある場合明示的に強制アンラップget
を記述しなければいけないためSwiftと比較すると冗長です。
Optional<String> wrappedValue = Optional.of("value");
if (wrappedValue.isPresent()) {
// 冗長だが明示的に強制アンラップする必要がある
String value = wrappedValue.get();
System.out.println(value);
} else {
System.out.println("no value");
}
// "value"
Optional<String> wrappedValue = Optional.of("value");
// カード節的に記述する
if (!wrappedValue.isPresent()) {
System.out.println("no value");
return;
}
// 冗長だが明示的に強制アンラップする必要がある
String value = wrappedValue.get();
System.out.println(value);
// "value"
Optional<String> wrappedValue = Optional.of("value");
wrappedValue.ifPresent( str -> {
System.out.println(str);
})// .orElse( () -> { // これはできない
// System.out.println("no value");
// })
;
またifPresent
に渡す処理のスコープはラムダ式内であるため、その処理内で呼び出し元のメソッドのリターンはできません。したがって早期リターンなどを行いたい場合はisPresent
を使った方法をとる他ありません。
public boolean foo(Optional<String> wrappedValue) {
// 値があるなら早期リターンする
if (wrappedValue.isPresent) {
String str = wrappedValue.get();
// 何かする
return true;
}
// 値がない場合の処理
return result;
}
public boolean foo(Optional<String> wrappedValue) {
// 値があるなら早期リターンする
wrappedValue.ifPresent( str -> {
// 何かする
// return true; // スコープがこのラムダ式内にあるため、これはできない
});
// 値がない場合の処理
return result;
}
オプショナルチェイニング(Optional Chaining)
Swift
中身があるときのみプロパティやメソッドへのアクセスを行います。後置演算子?
を使います。
let wrappedValue: String? = "value"
let uppercased: String? = wrappedValue?.uppercased()
print(String(describing: uppercased))
// Optional("VALUE")
class Hoge {
func hoge() { print("hoge") }
}
let wrappedValue: Hoge? = Hoge()
wrappedValue?.hoge()
// "hoge"
Java 8
Java 8では先に紹介したmap
メソッドが使えます。対象がメソッドであればメソッド参照を用いて簡潔に書くことができます。ただし、対象がOptional
型を返す場合にはflatMap
を使う必要があります。
Optional<String> wrappedValue = Optional.of("value");
Optional<String> uppercased = wrappedValue.map(String::toUpperCase);
System.out.println(uppercased);
// Optional[VALUE]
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
を使います。
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
のメソッド、or
とifPresentOrElse
を紹介します。Java 8の話は出てきません。完全なオマケです。
or
中身がない場合に与えられたSupplier<? extends Optional<? extends T>>
型のオブジェクトを実行してOptional
型のデフォルト値を得ます。これはちょうどSwiftのNil結合演算子で右オペランドにOptional
型を持ってきた場合と対応しています。
また短絡評価をするため、中身がある限り与えられたSupplier
オブジェクトが実行されることはありません。
let wrappedValue: String? = nil
let defaultValue = "default"
let otherWrappedValue: String? = nil
let value = wrappedValue ?? otherWrappedValue ?? defaultValue
print(value)
// "default"
// 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のオプショナルバイディングにかなり近づいています。
let wrappedValue: String? = "value"
if let value = wrappedValue {
print(value)
} else {
print("no value")
}
// "value"
// 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"