覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。twitter: @yyoshikaw

Kotlin - ローカル関数

例えば以下のような User クラスとそのUserのオブジェクトを登録する関数が存在するとします。

class User(val id: Int, val firstName: String, val lastName: String)

fun registerUser(user: User) {

    if (user.firstName.isEmpty()) {
        throw IllegalArgumentException("Invalid User id=${user.id} : firstName is Empty")
    }

    if (user.lastName.isEmpty()) {
        throw IllegalArgumentException("Invalid User id=${user.id} : lastName is Empty")
    }

    // Register to persistence area ...
}

登録関数の registerUser は実際の登録処理前に、Userの firstName と lastName が空文字列でないかのバリデーションを行い、
空であれば IllegalArgumentException をスローします。


一応実行させてみます

fun main() {
    registerUser(User(100, "", "Yamada"))
}

結果は以下の通りです

Exception in thread "main" java.lang.IllegalArgumentException: Invalid User id=100 : firstName is Empty
    at net.yyuki.sandbox.localfunc.LocalFuncKt.registerUser(LocalFunc.kt:10)
    at net.yyuki.sandbox.localfunc.LocalFuncKt.main(LocalFunc.kt:21)
    at net.yyuki.sandbox.localfunc.LocalFuncKt.main(LocalFunc.kt)


firstName と lastName のチェックを行っている処理は重複していて、DRY原則に従うのであれば、関数化して重複を取り除く必要があるでしょう。

こういった場合に、バリデーションの処理部分をローカル関数として配置する事で重複を取り除く事ができます。
validation というローカル関数を、 registerUser関数の中に配置する事でバリデーション処理の重複を取り除く事ができました。

class User(val id: Int, val firstName: String, val lastName: String)

fun registerUser(user: User) {

    fun validation(user: User, value: String, fieldName: String) {
        if (value.isEmpty()) {
            throw IllegalArgumentException("Invalid User id=${user.id} : $fieldName is Empty")
        }
    }

    validation(user, user.firstName, "firstName")
    validation(user, user.lastName, "lastName")

    // Register to persistence area ...
}

先ほどと同様の呼び出しで実行してみると結果は以下の通りで、予定通りに例外がスローされています

Exception in thread "main" java.lang.IllegalArgumentException: Invalid User id=100 : firstName is Empty
    at net.yyuki.sandbox.localfunc.LocalFuncKt$registerUser$1.invoke(LocalFunc.kt:11)
    at net.yyuki.sandbox.localfunc.LocalFuncKt.registerUser(LocalFunc.kt:15)
    at net.yyuki.sandbox.localfunc.LocalFuncKt.main(LocalFunc.kt:22)
    at net.yyuki.sandbox.localfunc.LocalFuncKt.main(LocalFunc.kt)


ローカル関数は、外側の関数の全ての引数や変数にアクセスする事ができるため、validation 関数に Userのオブジェクトを渡す必要はありません。
そのため、以下のように書き換え可能です。

class User(val id: Int, val firstName: String, val lastName: String)

fun registerUser(user: User) {

    fun validation(value: String, fieldName: String) {
        if (value.isEmpty()) {
            throw IllegalArgumentException("Invalid User id=${user.id} : $fieldName is Empty")
        }
    }

    validation(user.firstName, "firstName")
    validation(user.lastName, "lastName")

    // Register to persistence area ...
}

ローカル関数へ Userのオブジェクトを渡さなくなったので、だいぶすっきりしました。

さらに、validation 関数をUserクラスの拡張関数へと移動する事もできます

class User(val id: Int, val firstName: String, val lastName: String)

private fun User.validation() {
    fun validation(value: String, fieldName: String) {
        if (value.isEmpty()) {
            throw IllegalArgumentException("Invalid User id=$id : $fieldName is Empty")
        }
    }

    validation(firstName, "firstName")
    validation(lastName, "lastName")
}

fun registerUser(user: User) {
    user.validation()

    // Register to persistence area ...
}

拡張関数にすることで、余分な修飾もなくUserオブジェクトのメンバにアクセスできます。

Kotlin - トリプルクォート文字列

現在のJavaにはなくてKotlinにはあるものの一つに トリプルクォート文字列(triple-quoted string) があります。

固定文字列は、"(ダブルクォート)で囲って記述しますが、
この際に、その文字列内に ダブルクォート を入れたり 改行を入れたりしようとすると 必ずエスケープが必要になってきます。

例えば以下のようなクラスを定義したとして

public class Person {
    private final long id;
    private final String name;
    private final int age;

    public Person(long id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

以下のJSON文字列から上記クラスのオブジェクトへと変換するものとします

{
    "id" : 10001,
    "name" : "KotorinTaro",
    "age" : 25
}

JSON文字列からこのオブジェクトに変換する処理をJavaで書こうとすると以下のようになります。
(今回の例では、変換に Gsonライブラリを使用しています)


JSON文字列からオブジェクトへの変換のコード(一部抜粋)

import com.google.gson.Gson;

// JSON文字列の定義
String json = "{ \"id\": 10001, \"name\": \"KotorinTaro\", \"age\": 25 }";

Gson gson = new Gson();
Person person = gson.fromJson(json, Person.class);

System.out.println(json + "\n -> " + person);

実行結果は以下の通りです

{ "id": 10001, "name": "KotorinTaro", "age": 25 }
 -> Person{id=10001, name='KotorinTaro', age=25}


一応、問題なく変換できているようですが、
そもそも元のJSON文字列の定義がエスケープだらけでJSON文字列として正しいのかよく分かりません。

String json = "{ \"id\": 10001, \"name\": \"KotorinTaro\", \"age\": 25 }";

同じ階層に3項目しかないJSONなので、まだマシと言えばマシですがかなり分かりにくいです。


普通のアプリで、わざわざJSONを文字列リテラルで記述することなんか無いでしょう・・・と思いたくなりますが、
たとえばユニットテストのインプットとしてJSON文字列などを使いたい(外部からJSONの電文が渡された想定の)場合などに、
JSON文字列を直接記述したくなります。

が、JSONを素直に記述することもできないので、JSONファイルを用意してその中にJSONを書いて、
ユニットテスト時に一旦そのファイルから読み取った文字列をテストケースに渡す。
といったことになりがちです。


トリプルクォート文字列

Kotlinのトリプルクォート文字列は以下のような記述をします

val str = """なんかの文字列"""


たとえば改行やダブルクォート等を含んだ文字列でもエスケープが必要ありません

◾️サンプルコード1

val str = """|"My Name is Kotorin Taro."|"""

println(str)

◾️実行結果

|"My Name is Kotorin Taro."|


◾️サンプルコード2

val str = """
     for (ch in "something") {
        println(ch)
    }
    """

    println(str)

◾️実行結果

    for (ch in "something") {
        println(ch)
    }


文字列の先行する空白行(左のマージン)を取り除いた状態にしたい場合は、マージンの終わりをマークするマージンプレフィックス(規定のマージンプレフィックスは | (パイプ))と trimMargin関数を使用します。

◾️サンプルコード3

val str = """
    |for (ch in "something") {
    |    println(ch)
    |}
    """.trimMargin()

println(str)

◾️実行結果

for (ch in "something") {
    println(ch)
}


当初のJSON文字列をオブジェクト化するサンプルをKotlinで以下のように書く事ができます

import com.google.gson.Gson

val json = """
    |{
    |    "id" : 10001,
    |    "name" : "KotlinTaro",
    |    "age" : 25
    |}
    """.trimMargin()

val person = Gson().fromJson(json, Person::class.java)

println("$json \n-> $person")

実行結果は以下の通りです

{
    "id" : 10001,
    "name" : "KotlinTaro",
    "age" : 25
} 
-> Person{id=10001, name='KotlinTaro', age=25}


まとめ

Kotlin の トリプルクォート文字列 を利用する事で、通常であればエスケープを必要とするような文字列も素直に記述できる事が分かりました。
ここには書きませんでしたが、正規表現の記述の場合にも トリプルクォート文字列 が有効に利用できます。

Kotlin - 文字列の分割

Java で 文字列分割する時は、 String クラスの split メソッドを利用することになります。
分割した文字列を配列にして返してくれます。

例えば、"100-123-A500-10" という文字列があったとして、"-" で区切りたいと思った時は以下のようになります

String str = "100-123-A500-10";
String[] array = str.split("-");

System.out.println(java.util.Arrays.toString(array));

実行結果は以下の通りです。予定通り "-"(ハイフン)で区切られている事が分かります。

[100, 123, A500, 10]


次に、 "100.123.A-500.10" という文字列があったとして、"." で区切りたいと思った時に以下のようなコードを書いたとします。

String str = "100.123.A-500.10";
String[] array = str.split(".");

System.out.println(java.util.Arrays.toString(array));

この場合の実行結果は以下の通りで、空の配列です。なんだか予定したものとは感覚的にずれます。

[]

正直、Java経験者ならこれはほとんどの人が一度は通る道なような気がします。
String#split メソッドは、引数として正規表現をとります。
"."(ドット)を引数に指定してしまうと、任意の文字を表現する正規表現 となってしまい、こんな結果になります。

例えば以下のように書く事で、 望む結果を得ることができます。

String str = "100.123.A-500.10";
String[] array = str.split("\\.");

System.out.println(java.util.Arrays.toString(array));

実行結果は以下の通りです。

[100, 123, A-500, 10]


Kotlinの場合

Kotlinでは、上記のような紛らわしいメソッドを隠蔽して、異なる引数を持つ split という名前のオーバーロードされた拡張関数で置き換わっています。

split に対して正規表現を渡すためには、String ではなく Regax 型の値が必要になります。
これによって、メソッドに渡される値が、プレーンテキストとして解釈されるのか、正規表現として解釈されるのかが明確になります。

正規表現として渡す場合のコードは以下のようになります。文字列を toRegax 関数で正規表現に変換しています。

val str = "100.123.A-500.10"
val array = str.split("\\.".toRegex())  // 正規表現として渡している事が明確

println(array)

実行結果は以下の通りです

[100, 123, A-500, 10]


"."(ドット) だけではなく "-"(ハイフン)でも分解したい場合は以下のようになります

val str = "100.123.A-500.10"
val array = str.split("\\.|-".toRegex())  // 正規表現として渡している事が明確

println(array)

実行結果は以下の通りです

[100, 123, A, 500, 10]


toRegax関数を使わずに、そのまま "." (ドット)を渡すとプレーンテキストとして扱われ、その文字列がデリミタとなります。

val str = "100.123.A-500.10"
val array = str.split(".")  // プレーンテキストのデリミタとして扱われる

println(array)

実行結果は以下の通りです

[100, 123, A-500, 10]


プレーンテキストを引数に渡すパターンで、 "."(ドット) だけではなく "-"(ハイフン)でも分解したい場合は以下のように書けます

val str = "100.123.A-500.10"
val array = str.split(".", "-")  // プレーンテキストのデリミタとして扱われる

println(array)

実行結果は以下の通りです

[100, 123, A, 500, 10]


まとめ

文字列の分割という基礎的な処理に対しても、Javaに比べて直感的で分かりやすい操作(関数)が提供されていることが分かりました。

tail -f した内容をgrepしてリアルタイムで出力する

Linuxでログファイルの状態をリアルタイムで確認する場合 tailコマンドに -f オプションをつけて tail -f {ログファイル名} という使い方をすることが多々あります。

例えば、今まさにログが追記されている application.log というログファイルの内容を
リアルタイムで確認するには以下のように実行します。

tail -f application.log


そして、こういう時には往々にして、出力される内容を特定の文字列が含むものだけに絞り込みたい場合があります。
例えば ERROR というキーワードが付いた行だけ出力したい。となった場合は以下のようにパイプで繋いで grep するようにします。

tail -f application.log | grep "ERROR"

こうすると ERROR という文字列が含まれる行だけ出力されるようになります。


さらに絞りこんだ内容に test-user が含まれる行は除外したい となった場合は、さらにパイプで繋いで以下のようにします。

tail -f application.log | grep "ERROR" | grep -v "user-test"

しかし、こうすると結果がリアルタイムで出力されません。
これは、grep がバッファリングしているためで、バッファが一定量になるまで出力されません。

こういう時は、grep に --line-buffered のオプションを付ける事でバッファリングしなくなり、リアルタイムで出力されるよになります。


先ほどの例は以下のように書き換えできます。

tail -f application.log | grep --line-buffered "ERROR" | grep --line-buffered -v "user-test"

これでgrepを重ねても、リアルタイムで出力されるようになります。


(ただし、--line-buffered を使うとパフォーマンスが若干悪くなる場合があるようです。一応意識はしておいた方がいいかもしれません)



関連エントリ

Kotlin - 中置呼び出し・分解宣言

Kotlin で マップを生成するために mapOf という関数を利用します。

例えば以下のように呼び出します。

val map = mapOf(1 to "1yen", 5 to "5yen", 10 to "10yen", 50 to "50yen")


この mapOf 関数の宣言は以下のようになされています

public fun <K, V> mapOf(vararg pairs: Pair<K, V>): Map<K, V>

Pair型 の 可変長引数を受け取る関数となっています。戻り値の関係性から キーと値のペアである必要があります。


mapOf を呼び出している引数から

1 to "1yen"

の部分が、Pair(1, "1yen") を作り出していることが分かります。


to というキーワードは組み込みの機能ではなく、メソッド呼び出し方法の特別な種類で 中置呼び出し(infix call)と呼ばれるものです。
中置呼び出しでは、メソッド名が対象オブジェクト名と引数の間に隣接して置かれます。余分なセパレータはありません。

以下2つの呼び出しは等価となります。

1.to("1yen")  // 普通の方法での to関数 の呼び出し
1 to "1yen"  // 中置記法を使った to関数 の呼び出し


といことなので冒頭の mapOf 関数でのマップ生成は以下のように書こうと思えば書けます

val map = mapOf(1.to("1yen"), 5.to("5yen"), 10.to("10yen"), 50.to("50yen"))


中置呼び出しは、1つの必須な引数を持つ通常のメソッドや拡張関数で使用可能です。
中置記法を使って関数を呼び出すためには、その関数に infix 修飾子を付ける必要があります。

ちなみに、to関数の宣言は以下のようになっています。

public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)

to関数は、Pairのインスンタスを返します。これは、Kotlinの標準ライブラリのクラスで要素のペアを表現しています。


Pair の中身を使って、以下のように二つの変数を直接初期化する事ができます。

val (num, yen) = 10 to "10yen"

この例では、num に 10 が yen に "10yen" が格納されます。

この機能を 分解宣言(destructuring declaration)と呼びます。


分解宣言は、ペアに対してだけ限定して利用されるものではなく、2つの変数keyとvalueをマップのエントリで初期化することにも利用できます。

例えば以下のようにループ内でマップのエントリをkeyとvalueで初期化しながら出力する事が可能です

val map = mapOf(1 to "1yen", 5 to "5yen", 10 to "10yen", 50 to "50yen")

for ((key, value) in map) {
    println("$key -> $value")
}

◾️実行結果

1 -> 1yen
5 -> 5yen
10 -> 10yen
50 -> 50yen


コレクションのループでwithIndex 関数を使う場合にも利用可能です

val list = listOf("1yen", "5yen", "10yne", "50yen", "100yne")

for ((index, element) in list.withIndex()) {
    println("list[$index] -> $element")
}

◾️実行結果

1 -> 1yen
5 -> 5yen
10 -> 10yen
50 -> 50yen