20200327のSwiftに関する記事は5件です。

iOS13でUIStoryboardから生成するUIViewControllerを独自initでビルドする拡張メソッドを作る

はじめに

取り急ぎ考えてみたので、手元で動かしてないです..!(Xcode11.4でコンパイルは通ります。)

Declare IBInjectable

@available(iOS 13, *)
protocol IBInjectable {
    associatedtype Input
    var input: Input { get set }
    init?(coder: NSCoder, input: Input)
}
extension IBInjectable where Self: UIViewController {
    var className: String {
        String(describing: self)
    }

    static func build(input: Input) -> Self? {
        return UIStoryboard(name: Self.className, bundle: nil).instantiateInitialViewController { coder -> Self? in
            Self.init(coder: coder, input: input)
        }
    }

    init?(coder: NSCoder, input: Input) {
        self.init(coder: coder)
        self.input = input
    }
}

Implement IBInjectable

class SampleViewController: UIViewController, IBInjectable {
    typealias Input = String
    var input: String!
}

guard let sampleViewController: SampleViewController = SampleViewController.build(input: "Sample") else {
    fatalError()
}
sampleViewController.input // print: "Sample"

まとめ

var input: Input { get set }にすると、コンパイルが通らなく渋々IUOを使用しているのが改善の余地ありそうです。?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS AppSync用にaws-appsync-codegenでiOS(Swift)のソースコードを生成する

AWS AppSyncでiOS(Swift)用のコードを生成する時に勘違いしていたことがあったので、それについて公式のサンプルから理解できるように細かく書いておきます。私自身がAppSync歴1日なので正確に理解できているかもわかりませんので、その点についてはお手数おかけいたしますがコメントなどでご指摘よろしくお願いします。

また、現状(2020年)ではAppSyncの公式ドキュメントはAmplifyを使うことが前提とされており、Amplifyを使わない場合はやり方を探すのはかなり難しい。難しいので書き残しておきますというモチベーションです。

サンプルはAWS AppSync公式で説明されているサンプルスキーマ「イベントアプリ」からです。

最初に結論

おそらくこのページを見ている方が最も知りたいのはコード生成のコメントaws-appsync-codegenについての使い方でしょうから最初に書いておきます。

# aws-appsync-codegen generate tmp/*.graphql --schema tmp/schema.json --target swift --output tmp/GraphQLAPI.swift
  • tmp/*.graphql
    • GraphQL リクエスト用のクエリを指定
      • *にしているのはリクエストが複数ある場合にそれをtmpディレクトリに置いているためです
      • 後述の「クエリ画面」からコピペしてファイルにします
  • tmp/schema.json
    • GraphQLのための仕様
      • 後述の「スキーマ画面」から「スキーマをエクスポート」でschema.jsonを選びます
        • 注意: jsonを選びましょう。schema.graphqlではありません
  • --targetはswiftを指定
    • 省略できますがこの説明のため丁寧に書いておきます

画面での説明

クエリ画面

スクリーンショット 2020-03-27 17.45.44.png

スキーマ画面

スクリーンショット 2020-03-27 16.45.32.png

aws-appsync-codegenの導入

npmをmacにDockerで入れることについて書いておきます。

iOSアプリ開発者的には、仕事でnpmの情報を追っているわけではなくバージョンの違いによる動作の違いを把握していないためシステムにnpm自身を入れたくないですし、なにか環境によるエラーが出た場合に面倒というのが強いです。

おそらくnpmをバージョン別に入れてそれを切り替えて使う方法(nodeenv?nodebrew?)もあるんでしょうが、そこはnpmをそこまで使うわけではないので考えたくないです。

nodeのimageをダウンロード

$ docker pull node

docker runしてコンソールでnpmを利用する

nodeのコンテナを起動し、コンソールに入ります

$ docker run -v (pwd)/graphql:/tmp -it --rm node /bin/bash
  • -it
    • インタラクティブにターミナルを起動
  • -v
    • ローカルとコンテナ内のボリュームを共有し、GraphQL リクエスト用のクエリとschema.jsonをコンテナ内でも使えるようにします
    • (pwd)
      • fishなので(pwd)
      • bashだと$(pwd)かも

コンテナ内でnpmを使いaws-appsync-codegenをインストール

コンテナ内なのでグローバルにインストールしても良いかな、という感じです

# npm install -g aws-appsync-codegen

aws-appsync-codegenでコード生成する

# aws-appsync-codegen generate tmp/queries.graphql --schema tmp/schema.json --target swift --output tmp/GraphQLAPI.swift
  • コード生成した結果も共有のボリュームに出力しています
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Firebaseのルール設定

Firebase firestoreでのルール設定について[備忘録]

個人開発アプリにてFirebaseを利用しているのですが、認証機能をつけずに運用していたところ、急にデータを取れなくなったのでメモ。

新規firebaseProject作成後、ルール設定を(誰でも閲覧可能)にしていたところ、1,2ヶ月が過ぎた段階で以下の警告がでて動かなくなった。
スクリーンショット 2020-03-27 9.35.11.png
ちなみにその時のセキュリティソースが以下

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // This rule allows anyone on the internet to view, edit, and delete
    // all data in your Firestore database. It is useful for getting
    // started, but it is configured to expire after 30 days because it
    // leaves your app open to attackers. At that time, all client
    // requests to your Firestore database will be denied.
    //
    // Make sure to write security rules for your app before that time, or else
    // your app will lose access to your Firestore database
    match /{document=**} {
      allow read, write: if request.time < timestamp.date(2020, 3, 21);
    }
  }
}

これをいかに変更して、認証機能をつけることで解決。ちなみに匿名の認証機能を採用しました。(規約変更しないとなあ、、、)

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if request.auth.uid != null;
    }
  }
}

より良い解決方法ありましたら、ご教授いただけると幸いです。m(_ _)m

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java 8のOptionalをSwiftのOptionalと比較する

概要

本記事では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")

Java 8

Java 8では先に紹介したmapメソッドが使えるでしょう。メソッド参照を用いればある程度簡潔に書くことができます。

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

まとめ

本記事ではSwiftでのOptional型の扱いを元に、Java 8とOptional型との比較を行いました。特に、オプショナルバインディングについては両者の違いがよくあらわれていました。言語レベルでNullセーフであるSwiftと比べてしまうと扱いづらさが目立ってしまうものの、ラムダ式やメソッド参照のおかげでかなり書きやすくなっているなと感じています。
もし何か間違いなどございましたらコメントにてご指摘ください。

オマケ - 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"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java 8のOptionalをSwiftと比較する

概要

本記事では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"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む