Edited at

Java開発者に送るKotlinの関数とラムダ


関数の定義

関数は以下のように定義します。

// Int型の引数をインクリメントして返す関数

fun increment(i: Int): Int {
return i + 1
}

fun 関数名(引数リスト): 戻り値の型

これが基本形です。


名前付き引数

引数の多い関数の呼び出しでは、どの値がどの引数に対応しているのか

分かりづらくなることがあります。

Kotlinはこの問題を解消する機能があります。

/*

* たこ焼きを注文します
*
* @param isKarashiMayo からしマヨネーズを選択するか否か
* @param isPutAonori 青のりののせるか否か
*
* @return たこ焼きのインスタンスが返ります
*/
fun oderTakoyaki(isKarashiMayo: Boolean, isPutAonori: Boolean): Takoyaki {
// ...
}

val takoyaki = oderTakoyaki(true, false)
// どっちフラグが青のりだっけ?

val takoyaki = oderTakoyaki(isKarashiMayo = true, isPutAonori = false)
// 呼び出し側に仮引数名をつけることができます
// コードの意図が読み取り易い

val takoyaki = oderTakoyaki(isPutAonori = false, isKarashiMayo = true)
// 名前を付けると、関数に定義されている順番でなくてもOK


デフォルト引数

引数の数が違うだけのオーバーロードが多数存在するクラスが、よくあると思います。

例えば...

Kotlinではデフォルト引数を用いることで、このようにバリエーションが増えてしまう状況を回避できます。

/*

* たこ焼きを注文します
*
* @param isKarashiMayo からしマヨネーズを選択するか否か
* @param isPutAonori 青のりののせるか否か
*
* @return たこ焼きのインスタンスが返ります
*/
fun oderTakoyaki(isKarashiMayo: Boolean = false, isPutAonori: Boolean = true): Takoyaki {
// ...
}

// 呼び出し時に引数を省略可能。省略した場合は、デフォルト値が使用される。
val takoyaki = oderTakoyaki()


拡張関数

既存のクラスに、後から勝手に関数を追加できます。

独自の日付フォーマットでパースする関数をString型に追加してみましょう。

fun String.toDate(): Date {

val format = SimpleDateFormat("yyyy年MM月dd日")
return format.parse(this)
}

val str = "2019年03月09日"
val date = str.toDate()

fun 拡張対象の型.関数名(引数リスト): 戻り値

↑のように定義します。

ちなみに、通常の関数は...

fun 関数名(引数リスト): 戻り値の型

↑こうでした。

関数名の前に拡張対象の型をつけると拡張関数になります。

ちなみに、この拡張対象のオブジェクトをKotlinではレジーバーオブジェクトと呼びます。


単一式関数

関数が1つの式からなる場合は、{ }を省略することが可能です。

単一式関数は戻り値の型が推論可能な場合、戻り値の型を省略することもできます。

// 通常の関数

fun double(x: Int): Int {
return x * 2
}

// 単一式関数で記述
fun double(x: Int): Int = x * 2

// 単一式関数で戻り値の型を推論
fun double(x: Int) = x * 2


ラムダ式

ラムダ式は↓のように書きます。

val sum = { x: Int, y: Int ->

x + y
}

{引数リスト-> 本体 }

または引数がなければ

{ 本体 }

となります。

ラムダ式のパラメータが1つの場合は暗黙の変数itが使用できます。

val double: (Int)->Int = {

it * 2
}

尚、関数型は次のように記述します。

(引数の型) -> 戻り値の型


inline関数

ラムダ式は実行コストの高い仕組みです。

Kotlinのラムダ式は無名クラスとして実現されています。変数をキャプチャしたラムダ式は実行されるたびに無名クラスのインスタンスが生成されます。

そこで、inline関数です。

inline関数は呼び出される箇所に関数本体を置き換えてコンパイルされます。

inline fun edit(context: DataBaseContext, block: (DataBaseContext.Transaction)->Unit) {

val transaction = context.beginTransaction() // 1. トランザクション開始
block(transaction) // 2. 引数ラムダ実行
transaction.commit() // 3. トランザクション終了
}

inline関数が効力を発揮するのは

引数にラムダ式を受け、それを内部で実行する関数です。

inline関数では、引数のラムダ式も一緒に呼び出し元に展開します。

上のinline関数editを呼び出すサンプルです。

val dataBaseContext = DataBaseContext()

edit(dataBaseContext) { transaction ->
val newObject = Any()
transaction.add(newObject)
}

edit関数はコンパイル時に展開され、次のようなコードになります(イメージです)

val dataBaseContext = DataBaseContext()

// ここからedit関数が展開
val transaction = dataBaseContext.beginTransaction() // 1. トランザクション開始
val newObject = Any() // 2. 引数ラムダ展開
transaction.add(newObject) // 2. 引数ラムダ展開
transaction.commit() // 3. トランザクション終了
// ここまでedit関数が展開


もう一つのinlineの出番

Kotlinのジェネリックは実行時に型情報は失われます。

例えば、次の関数はコンパイルエラーとなります。

fun <T> mapFilter(list: List<Any>): List<T> {

return list.filter { it is T }
.map { it as T }
}

mapFilter関数の型パラメータTは実行時に失われるためです。

そこで、inline関数にreified修飾子を付加します。

//          ↓ここ!

inline fun <reified T> mapFilter(list: List<Any>): List<T> {
return list.filter { it is T }
.map { it as T }
}

このようにすることで、実行時に型情報を保持したまま展開されるため

mapFilter関数がコンパイルできるようになります。