16
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

KotlinAdvent Calendar 2018

Day 13

Kotlinで変数をNonNullにする4つの方法

Last updated at Posted at 2018-12-12

Kotlinの特徴の一つにNullSafetyの仕組みがあります。
Javaではif文で判定するしかなかったNull判定を、変数宣言時や代入時に行うことで後続処理でのNullPointerExceptionの発生を防げます。

そこで、KotlinでのNonNullで変数を定義する方法をまとめます。

対象読者

Kotlinをがっつりとは書いていない方
最近AndroidアプリをKotlinに移行し始めた方

lateinitを使う

lateinitは変数宣言に初期値を代入せず、後から代入されることを約束する際につけます。

lateinit.kt
class LateInit{
    var user:User // コンパイルエラー
    var user2:User? = null // エラーにはならないけどNullable😵
    lateinit var user3:User //Good👍
    lateinit val user4:User // valはfinal扱いなので、あとから代入はできないためコンパイルエラー
}

使う所

Androidを開発しているとよく使います
ActivityやFragmentは、システム側がインスタンスを生成するため、引数付きコンストラクタやinitを使う事はできません。
そのため、各値の初期化はonCreate()内にて行います。

MainActivity.kt
class MainActivity(){
    lateinit var user: User

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val name = getIntent().getStringExtra("name") ?: ""
        user = User(name)
    }
   //...
}

代入する前に呼び出したら…

落ちます

UnInit.kt
class UnInit {
    lateinit var user: User

    fun run() {
        println(user) //ここで落ちます
        println(user.name) //ここには到達しない
    }
}

出力結果

Exception in thread "main" kotlin.UninitializedPropertyAccessException: lateinit property user has not been initialized
	at NonNull.run(NonNull.kt:5)
	at MainKt.main(Main.kt:6)

lateinitを宣言した変数に初期化されていない時専用の例外が出ます。
Javaで同じ様に書くと、一行下に書いたuser.name でNullPointerExceptionが出力されるはずなのでより正確に例外の原因がわかります

by lazyを使う

変数を最初に呼び出されるまで初期化を遅延します。

lazy.kt
    val user: User by lazy {
        User("John")
    }
    fun onCreate3() {
        print("${user}")
    }

実行結果

User(name=John)

注意点

by lazyによって取得された値は、AndroidのLifecycle関係なく保持されます
findViewByIdで取得したレイアウトはFragmentが再生成された場合でも古いViewを取得し続けます
参考URL:https://qiita.com/takahirom/items/49e18ec7084bb3b1937e
kotlinを使い始めた時、
変数をvalに統一したい時によく使っていたのですが良くない実装だったので今はlateinitやKotlin Android Extensions
を使っています。

使うところ

上記理由により、今はあまり使う場面に遭遇していないです。
取得処理が重いファイル読み込みなどに使う事はあるかもしれません

?: を使う(エルビス演算子)

Nullableの型をそのまま使うと不正な文字列になったりクラッシュになったりする

ShowUser.kt
    fun showUser(user: User?) {
        val showName = user?.name // nullableなので、?.nameにしないとコンパイルエラー
        println("名前:$showName") // showNameはString?になる😵
    }

    fun run(){
        showUser(User("John"))
        showUser(null) // アプリの画面にnullって出る😭
    }

出力結果

名前:John
名前:null

なので、Java同様Nullチェックが必要になる

showUser.kt
    fun showUser(user: User?) {
        if(user == null)return
        val showName = user.name // nullじゃないのは確定したため、?.じゃなくてもコンパイルが通る
        println(showName) // showNameはStringになる
    }

Kotlinではif文の他に?:というエルビス演算子を使って書くことができる

showUser.kt
    fun showUser(user: User?) {
        val showName = user?.name ?: return // userがnullなら代入せず離脱する
        val showName2 = user?.name ?: "名無し" // userがnullならshowName2に"名無し"を代入する
        val showName3 = user?.name ?: throw Exception() // userがnullなら例外を投げる
        println(showName)
    }

letを使う(スコープ関数)

?:だとうまくいかない場合

エルビス演算子は便利だけど、メンバ変数をNullチェックをしたい時にはすんなり行かない

showUser.kt
class ShowUser(){
    var user: User? = null

    fun showUser() {
        if (user == null) return
        val showName = user.name
        val showAge = user.age
        println("名前:$showName 年齢:$showAge")
    }
}

こういうコードを書くと、Smart cast to 'User' is impossible, because 'user' is a mutable property that could have been changed by this time とでてコンパイルエラーになる
user メンバ変数として定義しているため、Nullチェック後に値が変わるかもしれない(例えば、非同期処理で同時にuserに値を代入する)
そのためuserをNullチェック後でもNonNullとして扱うことができず、Nullableのままになってしまう

解決方法

showUser.kt
    var user: User? = null

    fun showUser() {
        user?.let { user ->
            val showName = user.name
            val showAge = user.age
            println("名前:$showName 年齢:$showAge")
        }
    }

?.とスコープ関数を使って、関数内はNonNullであることを確定させた状態で処理を行う

showUser.kt
        user?.let { user ->
            //...
        } ?: throw Exception() //エルビス演算子と組み合わせる事もできる(nullの時例外を投げる)

あとがき

ここで紹介した方法以外にNonNullにする方法はあると思います。
知っている方はコメントで紹介していただけると嬉しいです😊

16
11
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?