覚えたら書く

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

Kotlin - 簡単なラムダ式を試す

ラムダ式(lambda exoression)または単にラムダ(lambda)とは、本質的には他の関数に渡すことが可能なコードの断片です。
ラムダを利用することで、共通のコード構造を抜き出してライブラリ関数へ渡すこことが可能です。
Kotlin の標準ライブラリはラムダを多用していて、ラムダの最も一般的な利用用途はコレクションの操作です。


以下でKotlinのコードで簡単なコレクションの操作を行ってみます。

data class Product(val name: String, val price: Int)

上記クラスのインスタンスを複数詰め込んだリスト内から最も高い価格(price)のものを見つけるという処理を行います。

サンプルコードで対象のリストは以下のものとします

val products = listOf(Product("A", 10), Product("B", 1), Product("C", 1000))


命令的に、この処理を素直に記述すると以下のようになります。

fun findHighestProduct(products: List<Product>): Product? {
    var maxPrice = 0
    var highestProduct: Product? = null

    for (product in products) {
        if (product.price > maxPrice) {
            maxPrice = product.price
            highestProduct = product
        }
    }

    return highestProduct
}

実行コードは以下のよになります

val products = listOf(Product("A", 10), Product("B", 1), Product("C", 1000))
val highestProduct1 = findHighestProduct(products)  // -> Product(name=C, price=1000)


ラムダ式

Kotlin ではコレクションに対して maxBy 関数を利用することが可能です。
引数には、最大要素を見つけるために比較する値を指定する関数を指定する必要があります。

以下のように記述できます。波括弧内のコードはラムダ式であり、関数の引数として渡します。

val highestProduct = products.maxBy({p: Product -> p.price})

この記述はもっと簡単な記述にできます。
Kotlin では構文規約でラムダ式が関数呼び出しの最終引数であれば括弧の外に移動することが可能です。
この規約を利用すると以下のように記述できます

val highestProduct = products.maxBy() {p: Product -> p.price}

ラムダが関数の唯一の引数であれば、関数呼び出しから空の括弧を取り除いて記述できます。そうすると以下のようになります。

val highestProduct = products.maxBy {p: Product -> p.price}

上記3つの構文は同じことを意味しています。


ラムダの引数の型を推論できる場合は、明示的な指定は不要です。maxBy関数では、その引数の型は常にコレクションの要素の型と同じです。
そのため以下のように型を省いて記述することもできます。

val highestProduct = products.maxBy {p -> p.price}


ちなみにさらなる短絡構文で it を利用して以下のように記述することもできます

val highestProduct = products.maxBy { it.price }

it を用いてコレクション要素を引数として受け取っています。


今回比較する値は price であり、この値は price プロパティに保持されています。
ラムダが単に関数やプロパティを呼び出すだけの場合は、ラムダはメンバ参照を用いて記述することも可能です。

val highestProduct = products.maxBy(Product::price)


まとめ

今回は、Kotlinにおけるコレクションの簡単な操作をラムダ式を使って試してみました。

Kotlin - オブジェクト式

Kotlin の object キーワードは、無名オブジェクト(anonymous object)の宣言のためにも使用できます。
無名オブジェクト は Javaにおける無名内部クラスを置き換えるものとなります。

Javaで無名内部クラスの典型的な利用シーンというと、イベントリストが必要となるケースがあります。

例えば以下のようなJavaのコードがあったとした場合(import 等もろもろ省略)

JWindow window = new JWindow();
window.addMouseListener(new MouseAdapter() {
    @Override
    public void mouseClicked(MouseEvent e) {
        System.out.println("Mouse Clicked");
    }

    @Override
    public void mousePressed(MouseEvent e) {
        System.out.println("Mouse Pressed");
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        System.out.println("Mouse Released");
    }
});

上記コードは Kotlin ではオブジェクト式で以下のように記述できます。

val window = JWindow()
window.addMouseListener(
    object : MouseAdapter() {
        override fun mouseClicked(e: MouseEvent) {
            println("Mouse Clicked")
        }

        override fun mousePressed(e: MouseEvent) {
            println("Mouse Pressed")
        }

        override fun mouseReleased(e: MouseEvent) {
            println("Mouse Released")
        }
    }
)

オブジェクトの名前を省略しているという点をを除いて、オブジェクト宣言と同じです。
オブジェクト式はクラスを宣言し、そのクラスのインスンスを生成しています。


Javaの無名クラスと同様にオブジェクト式の中のコードは、それが生成された関数内の変数を参照することができます。
しかし、Javaとは違って、その変数がfinalでなければならないという制約はありません。
そのため、オブジェクト式の中から変数の値を変更することも可能です。

以下のようなコードを書くことも可能です

var count = 0
val window = JWindow()
window.addMouseListener(
    object : MouseAdapter() {
        override fun mouseClicked(e: MouseEvent) {
            println("Mouse Clicked")
            count++
        }

        override fun mousePressed(e: MouseEvent) {
            println("Mouse Pressed")
            count++
        }

        override fun mouseReleased(e: MouseEvent) {
            println("Mouse Released")
            count++
        }
    }
)


まとめ

オブジェクト式により Java の無名内部クラスを置き換えることができることがわかりました。

Kotolin - コンパニオンオブジェクト 2

Kotlin のコンパニオンオブジェクトは、クラス内に宣言された通常のオブジェクトであり、
名前を付けたり、インターフェースを実装したり、拡張関数やプロパティを持つこともできます。


コンパニオンオブジェクトに名前を付ける

前回のエントリですでにやってやってますが、コンパニオンオブジェクトに名前を付けることが可能です。

import com.google.gson.Gson

class Person(val name: String, val age: Int) {
    // 名前付きのコンパニオンオブジェクトの宣言
    companion object Decoder {
        fun fromJSON(jsonStr: String): Person {
            return Gson().fromJson(jsonStr, Person::class.java)
        }
    }
}

呼び出し側のコードは以下の通りで、コンパニオンオブジェクトの名前を明示することも省略することも可能です。

// コンパニオンオブジェクトの名前を指定して関数を呼び出せる
val p1 = Person.Decoder.fromJSON("""{"name": "Kotlin Taro", "age": 32}""")
println("${p1.name} is ${p1.age} years old.")  // -> Kotlin Taro is 32 years old.

// コンパニオンオブジェクトの名前を省略することもできる
val p2 = Person.fromJSON("""{"name": "Java Taro", "age": 39}""")
println("${p2.name} is ${p2.age} years old.")  // -> Java Taro is 39 years old.


コンパニオンオブジェクトでインターフェースを実装する

他のオブジェクト宣言と同じくコンパニオンオブジェクトはインターフェースを実装できます。
インターフェースを実装しているオブジェクトのインスタンスとして、そのオブジェクトを含むクラスの名前を使用できます。

JSONFactory というインターフェースを介してオブジェクトが生成されるものとする場合、
以下のようなサンプルコードを書くことができます。

import com.google.gson.Gson

interface JSONFactory<T> {
    fun fromJSON(jsonStr: String): T
}

class Person(val name: String, val age: Int) {
    // 名前付きのコンパニオンオブジェクトの宣言
    companion object : JSONFactory<Person> {
        override fun fromJSON(jsonStr: String): Person {
            return Gson().fromJson(jsonStr, Person::class.java)
        }
    }
}


JSONFactoryを引数に取る関数を定義し多とした場合、
その関数を呼び出す場合には、JSONFactoryをコンパニオンオブジェクトが実装しているので Person オブジェクトを引数に渡すことが可能です。

fun <T> loadFromJSON(jsonStr: String, factory: JSONFactory<T>): T {
    return factory.fromJSON(jsonStr)
}

fun main() {
    // JSONFactoryインターフェースの
    val p1 = loadFromJSON("""{"name": "Java Taro", "age": 45}""", Person) // コンパニオンオブジェクトのインスタンスを関数に渡す
    println("${p1.name} is ${p1.age} years old.")
}


コンパニオンオブジェクトの拡張関数

クラスがコンパニオンオブジェクトを持っていれば、コンパニオンオブジェクトに対して拡張関数を定義することが実現できます。
例えば ClazzA がコンパニオンオブジェクトを持つ時、ClazzA.Companion に対する拡張関数 function を定義したと仮定すると、
その関数を ClazzA.function() として呼び出すことができます。


すでに定義した Person クラスは、そのドメインに特化したビジネスロジックのみを持つものとしておきたい場合に、
データのシリアライズやデシリアライズのルールは分離しておきたい(Personの核となるドメインとは別にしておきたい)ことがあります。
このような場合に拡張関数を利用すると実現可能です。

import com.google.gson.Gson

// domain core module
class Person(val name: String, val age: Int) {
    // 空のコンパニオンオブジェクトを宣言しておく
    companion object {
    }
}

// client / server communication module
fun Person.Companion.fromJSON(jsonStr: String): Person {
    return Gson().fromJson(jsonStr, Person::class.java)
}

以下のように実行可能です。

val p1 = Person.fromJSON("""{"name": "Java Taro", "age": 45}""")
println("${p1.name} is ${p1.age} years old.")


まとめ

今回は、Kotlin のコンパニオンオブジェクトへの名前付け、インターフェースの実装、拡張関数の定義が可能であることを確認しました。



関連エントリ

Kotlin - コンパニオンオブジェクト

Javaと異なり Kotlin のクラスは静的なメンバを持つことができません。Javaにおける static キーワードは Kotlin には含まれていません。

この Java における static の代替手段として以下を利用します。

  • トップレベル関数関数
    • 多くの状況で Java の staticメソッドを置き換えることが可能
  • オブジェクト宣言
    • Javaの静的フィールドと静的メソッドの置き換えをします

多くの状況では トップレベル関数 を使うことが推奨されているようです。
しかし、トップレベル関数は、クラスのprivateメンバにアクセスできないという制約が存在します。
クラスのインスタンスなしで、クラス内部にアクセスする必要がある関数を記述する必要がある場合、
対象クラスの内部のオブジェクト宣言のメンバとして関数を書くことが可能です。


クラス内で定義されているオブジェクトの1つに companion というキーワードを付けることが可能です。
このキーワードを付与することで、オブジェクトの名前を明示せずに、クラス名を使って直接そのオブジェクトのメソッド名やプロパティにアクセス可能となります。
Javaにおける静的メソッド呼び出しと全く同じ状態となります。

class Printer {
    companion object {
        fun print(data : String) {
            println("Printer printed... [$data]")
        }
    }
}

呼び出し側のコードは以下のようになります

Printer.print("Kotlin")  // -> Printer printed... [Kotlin]


companion のキーワードを付ける際にどこまで意味があるのか不明ですが、オブジェクト宣言で名前を指定することも可能です

class Printer {
    companion object Java {
        fun print(data : String) {
            println("Printer printed... [$data]")
        }
    }
}

呼び出し側のコードでは companionを付与したオブジェクト宣言の名前を経由してもしなくても内部の関数にアクセス可能です

Printer.print("Kotlin")  // -> Printer printed... [Kotlin]
Printer.Java.print("Java Lang")  // -> Printer printed... [Java Lang]


このコンパニオンオブジェクトの利用の好例が ファクトリメソッド(Javaでいうところのstaticファクトリ) になると思われます。

Javaでも同じですが複数のコンストラクタを持つクラスの場合、コンストラクタには名前を付けることができないためコード上の意味づけがしにくくなります。
そういった場合にはファクトリメソッド(staticファクトリ)を用意して、意味のある名前を付けます。


Kotlin で以下のような2つのセカンダリコンストラクタを持つクラスが存在したとします。

class User {
    val userName : String
    
    constructor(handleName: String) {
        this.userName = handleName
    }

    constructor(firstName: String, lastName: String) {
        this.userName = "$firstName-$lastName"
    }
}


これはコンパニオンオブジェクトを利用して以下のように書き換え可能です。

class User {
    val userName : String

    private constructor(handleName: String) {
        this.userName = handleName
    }

    private constructor(firstName: String, lastName: String) {
        this.userName = "$firstName-$lastName"
    }

    companion object {
        fun handleNamUser(handleName: String) =
                User(handleName)

        fun fullNameUser(firstName: String, lastName: String) =
            User(firstName, lastName)
    }
}


呼び出しコードは以下のようになります

val user1 = User.handleNamUser("handle-001")
println(user1.userName)  // -> handle-001

val user2 = User.fullNameUser("Taro", "Kotlin")
println(user2.userName)  // -> Taro-Kotlin


まとめ

サンプルコードとしては微妙だった気もしますが、コンパニオンオブジェクトによってファクトリメソッド(staticファクトリ)が実現できることがわかりました。



関連エントリ

Kotlin - シングルトン

Java でプログラムを書いている場合に、対象クラスのインスタンスがただ1つだけ存在する状態にしたい場合があります。

そういった場合には、一般的に Singleton パターン(シングルトン・パターン) というデザインパターンを用いてクラスを宣言する事になります。

Java でシングルトンを実現する方法はいくつかありますが、例えば以下のような記述をすることでシングルトンのクラスとなります。
(以下コードでは import文等を一部省略しています)

public final class ProductList {

    public static final ProductList INSTANCE = new ProductList();

    public static ProductList getInstance() {
        return INSTANCE;
    }

    private final List<String> products =
            Arrays.asList("Product1", "Product1.2", "ProductB", "ProductC");

    public List<String> allProducts() {
        return products;
    }

    private ProductList() {
    }
}

クラスがシングルトンとなっているのは上記宣言の以下のようなコードの記述の組み合わせによります。

  • コンストラクタが private (外部からの通常のコンストラクタでのインスタンス化ができないようにする)
  • static final で自分自身のインスタンスを保持する。(これが対象クラスの唯一のインスタンス)
  • 保持しているインスタンスを外部から取得するためのメソッドを定義(ここでは getInstance)


Javaでは上記のようなコードの組み合わせによってシングルトンを実現するわけですが、
Kotlin では、シングルトンの定義をするためのサポートを第一級言語機能として提供しています。
それが オブジェクト宣言(object declaration)です。

オブジェクト宣言は、クラス宣言(class declaration)とそのクラスのシングルインスタンス(single instance)の宣言が組み合わさっています。

先ほどの Java で定義したシングルトンのクラスを Kotlin で記述すると以下のようになります。(Listのクラスが若干違いますが、そこは無視で。)

object ProductList {

    private val products = arrayListOf("Product1", "Product1.2", "ProductB", "ProductC")

    fun allProducts() : List<String> {
        return products
    }
}

オブジェクト宣言は、 object キーワードで始まります。
クラスと同様に、オブジェクト宣言は、プロパティ、メソッド、初期化ブロックなどの宣言を含めることも可能です。
コンストラクタ(プライマリコンストラクタ、セカンダリコンストラクタ)は含めることができません。
通常のクラスのインスタンスと異なり、オブジェクト宣言はコード中の他の場所からのコンストラクタ呼び出しではなく、定義の時点で即座にインスタンスが生成されます。

オブジェクト宣言した内容の呼び出しは、<オブジェクト名>.<メソッド名> という形式になります。
上記でオブジェクト宣言した内容以下のようになります

ProductList.allProducts()


オブジェクト宣言は、クラスやインターフェースを継承することも可能です。

java.util.Comparator インターフェースを実装するクラスは、compare メソッドで2つのオブジェクトを引数に取り、
いずれのオブジェクトが大きいかを示す整数値を返却します。 このクラスは、基本的にcompare メソッドの引数にのみ依存して動きが制御され、基本的にクラス自体が状態を持つことはほぼありません。
そのため、インスタンスは1つだけ存在すればよいことになります。

こういった場面で オブジェクト宣言 が利用できます。

import java.io.File

object FilePathCmparator : Comparator<File> {

    override fun compare(f1: File, f2: File): Int {
        return f1.path.compareTo(f2.path, ignoreCase = true)
    }
}

このオブジェクトを利用してソートを実行したコードは以下の通りです

val pathList = listOf(
    File("/tmp"), File("/storage1/dir001/f001"), File("/storage1/dir002"),
    File("/storage1/dir001"), File("/storage1"), File("/root")
)

val sortedList = pathList.sortedWith(FilePathCmparator)

println("$pathList\n->\n$sortedList")

実行結果以下の通りです

[/tmp, /storage1/dir001/f001, /storage1/dir002, /storage1/dir001, /storage1, /root]
->
[/root, /storage1, /storage1/dir001, /storage1/dir001/f001, /storage1/dir002, /tmp]


Java からKotlinのオブジェクトを呼び出す

Kotlin のオブジェクト宣言は、1インスタンスを静的フィールドに保持するようなクラスにコンパイルされます。
その静的フィールドの名前は INSTANCE となっています。
Javaでシングルトンのインスタンスを宣言したのとほぼ同じ状態と言えます。

そのためKotlinのオブジェクトをJavaのコードから使うためには、以下のようにINSTANCEフィールドにアクセスする方法となります。
(Kotlinでオブジェクト宣言した ProductList のメソッドを呼び出している例です)

public class Main {
    public static void main(String[] args) {
        System.out.println(ProductList.INSTANCE.allProducts());
    }
}


シングルトンの補足

デザインパターンの中でも シングルトンパターン は特殊な部類に入り、シングルトンパターンのクラスは必ず実装クラスとなります。
そのため、そのクラスに依存するクラスのユニットテスト時に他の実装に差し替えること等ができません。
シングルトンパターンを利用するクラスとは密結合することになります、また、使い方を誤るとグルーバル変数のような物体と化します。
そのため、シングルトンパターンで作成したオブジェクトを多用しすぎないように注意が必要です。


まとめ

Kotlin では シングルトンのクラスを宣言するためのサポートが 言語機能として提供されているため、簡単にシングルトンのオブジェクトの作成ができることが分かりました。