Kotlinのスコープ関数を活用するプログラミング。

初めに

こんにちわ,OWNR by RENOSY Android開発のSugeunです。 今回は今までKotlinの関数を使ってはいましたがあんまり詳しく分からず使っていたので、 Kotlinの関数を深く見たいと思います。BlogのコードはKotlin 1.3バージョンで書かれています。🕵️‍♂️

www.renosy.com

Kotlinのスコープ関数とは

Kotlinで内蔵されている関数を使う利点としては

  • コードの量が飛躍的に減らせる。
  • オブジェクトのNull Checkが簡単にできる。
  • Methodのチェーンがしやすくなる。

などです。よく使われる関数は大きく五つ(apply, with, let, also, run)になります。 深く調べる前にまず共通で使われるdata classは下記になります。

data class Fruit(
    var createdYear: Int,
    var name: String,
    var color: String
)

applyについて

apply関数を呼び出しオブジェクトをブロックのレシーバーに渡して、そのオブジェクト自体を返してくれます。 レシーバーとはすぐ繋がるブロック内からメソッドにすぐ接近できるオブジェクトのことを意味します。

Fruitモデルをインスタンス化してモデル中にある変数をprintしようとしてみます。

// 使い方
val fruit = Fruit(createdYear = 2020, name = "apple", color = "red")
println(fruit.createdYear)
println(fruit.name)
println(fruit.color)

Fruit(createdYear = 2020, name = "apple", color = "red").apply{
    println(this.createdYear)  // thisは省略可能です。
    println(this.name)
    println(this.color)
}

applyを使うことでFruitデータモデルにあるプロパティーを別の変数に入れなくても使うことが可能になりました。

// 使用例
// newInstanceメソッドはnameパラメタをBundleに渡せようとしています。
// applyを使ったことでFragmentのmethodを即利用ができます。
companion object {
    private const val FRUIT_NAME = "fruit_name"
    fun newInstance(name : String)  {
        Fragment.apply{
            argument = Bundle().apply {
                putString(FRUIT_NAME, name)
            }
        }
    }
}

runについて

runはローカル変数の範囲を制限したいときや匿名関数を使いたいときに使用します。applyと見た目では同じくみえますが、applyはオブジェクトを生成した同時に連続の作業が必要な時に使われて、runはすでに生成されているオブジェクトから連続の作業が必要な時に使われます。

// 使い方
val apple: String = run {
    "delicious"
} 
// 戻り値がStringなのでこの場合appleは"delicious"になります。

val fruit1 = Fruit(createdYear = 2020, name = "apple", color = "red")
val fruit2 = fruit1.run {
    this.createdYear = 2021  // thisは省略可能です。
    this.name = "banana"
    this.color = "yellow"
}
// 戻り値がないのでこの場合fruit2はUnit Typeになります。

run関数は最後の変換データによってデータタイプが決められます。fruit2に何も返していないのでUnit Typeになりました。

  • Unit Type : Unit Typeはretrunされるデータがありませんの意味で、JavaのVoidと似ています。しかしKotlinでUnitはタイプとして扱われることができ、一般的にタイプとして使われる全ての所で使うことができます。
// 使用例
// repositoryのメソッドがNullの場合Runブロック内部の処理を行います。
// ブロック内部の処理が1行で終わる処理になるとわざわざrunを使う必要はありません。
val myStatus = currentInfoRepository.getStatus() ?: run {
    Single.error(MissingOwnerStatusException())
    view?.finishView()
}

letについて

let関数はインスタンス化されたオブジェクトをローカル変数として扱えることができます。 さらにletにを付けることでオブジェクトがNullなのかCheckもできます。

// 使い方
// ブロック内の結果物を返す場合に使えます。
val fruit = Fruit(createdYear = 2020, name = "apple", color = "red")
val result = fruit.let {
    it.name + it.color
}

次は?.let{}を使ってオブジェクトがNullかどうかの判断をやってみます。

// ?.letを使ってない場合のNull Checkです。
val fruit : Fruit? = null
if(fruit == null){
    // fruitのオブジェクトがNullの場合の処理を書く。
}else{
    // fruitのオブジェクトがNullではない場合の処理を書く。
}

// ?.letを使ったNull Checkです。
val fruit : Fruit? = null
fruit?.let {
    // fruitのオブジェクトがNullではない場合の処理を書く。
} ?: run { 
    // fruitのオブジェクトがNullの場合の処理を書く。
}
  • ?:とは: ?:の文字を右側に90度回転するとエルビスプレスリーと似ているのでエルビス演算子と呼ばれます。エルビス演算子は左のインスタンスや値がNullの場合右の関数や初期化処理を行います。
// 使用例
// fragmentでBundleからIntを取得する場合に`EXT_ARTICLE_ID`のキーから取得したデータが
// Nullではない場合ブロックの処理を行い、Nullの場合には現在の画面を閉じます。
arguments?.getInt(EXT_ARTICLE_ID)?.let {
    // パラメータを使用する
} ?: activity?.finish()

withについて

引数のオブジェクトをブロックに渡します。withはNullにならないオブジェクトで、結果が必要ではない場合に使われます。

// 使い方
val fruit = Fruit(createdYear = 2020, name = "apple", color = "red")
with(fruit) {
    println(this.createdYear) // thisは省略可能です。
    println(this.name)
    println(this.color)
}

// withを使ってない場合のコード
println(fruit.createdYear)
println(fruit.name)
println(fruit.color)
// 使用例
lateinit var fruit : Fruit
fun convertModel(entity : ResponseFruitEntity) {
    with(entity){
        fruit(
            this.createdYear,
            this.name,
            this.color
        )
    }
}

alsoについて

alsoはlet関数と使う見た目がほぼ似ていて、apply関数と同じくオブジェクトを返してくれますがブロック内部結果を他のオブジェクトを返したり、戻り値を変化するのはできません。

// 使い方
val fruit = Fruit(createdYear = 2020, name = "apple", color = "red").also{
    println(it.createdYear)
    println(it.name)
    println(it.color)
}
// 使用例
fruitModel.apply {  
    // ここでthisはfruitModelを指します。
    UserType.findUserType(user.type)?.also {  
        // UserTypeはenum classでこの場合itはUserTypeになります。
        setUserImage(it.adminUser)
        setCurrentUserStatus(it.adminUser)
    }
    // ...
}

最後に

今回はkotlinの関数について調べてみました。関数を意識しながら書くのは少し時間がかかるかもしれませんが、関数を使うことによってコードの量が減らすことができたり、可読性が上がります。公式ドキュメントにスコープ関数の色々な例があるのでご参考してみてください。

まだ慣れていない方はまずはリファクタリングをして慣れていきましょう!💪

Scope Functions - Kotlin Programming Language