こんにちは、kiyokawaです。
皆さんKotlinしてますか?
まだKotlinしていない方に、自分がJavaからKotlinに移行しようと勉強した際に
つまずいたことや、誰かに聞きたかったことを紹介していこうと思います。
全部書くとかなり長くなってしまいそうなので、今回はNull安全について書いていきます。
Kotlinはnull安全な言語です。言語の機能としてOptionalというものがあります。
これは、変数や引数にnullを許容させない仕組みです。
1 |
val a: String = null |
例えば、上のコードであればコンパイルエラーになります。
aという変数はnullを許容していない型だからです。
nullを入れたいのであれば、以下のように 型名? で変数を宣言する必要があります。
1 |
val a: String? = null |
ここまではすんなりと理解できたのですが、このnullを許容した変数の扱い方でつまずきました。
例えば、以下のJavaで作ったActivityをAndroid Studioの機能でKotlinに変換してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
public class DemoActivity extends AppCompatActivity { private TextView textView; private Button button; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_demo); textView = findViewById(R.id.textView); button = findViewById(R.id.button); } private void setText(String text) { textView.setText(text); } private void setClickEvent() { button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // do something } }); } } |
変換したものが以下になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class DemoActivity : AppCompatActivity() { private var textView: TextView? = null private var button: Button? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_demo) textView = findViewById(R.id.textView) button = findViewById(R.id.button) } private fun setText(text: String) { textView!!.text = text } private fun setClickEvent() { button!!.setOnClickListener { // do something } } } |
自動で変換したコードを見てみます。
textView!!や、button!!ってなんだろう…って初めて見たときに思いました。
この「!!」を消すとコンパイルエラーになります。ではこれは何かというとNullableの変数を強制的にNonNullの変数として扱い
もしNullであればNullPointerExceptionを投げるというものです。
つまり、Nullableの変数に対して「!!」を付けてアクセスするとせっかくのNull安全の仕組みが働かず
Javaで苦しめられたぬるぽに再度苦しめられることになるのです。
では、Null安全にアクセスするにはどうすればいいかというと以下のようにコードを修正すればよいです。
1 2 3 4 5 6 7 8 9 |
private fun setText(text: String) { textView?.text = text } private fun setClickEvent() { button?.setOnClickListener { // do something } } |
今度は「?」が後ろにつきました。
これは何かというと、「変数がもしnullじゃなかったら処理を行い、変数がもしnullだったら処理を実行せずにNullを返却する。」というものです。
今回のコードでいうと、もしもtextViewがnullじゃなかったらテキストをセットし、nullだったら何もしないという処理になります。
でも、まだ気になるところがありますよね。
このままだと、textViewもbuttonもonCreate()を通ればそれ以降はNonNullなのにずっとNullableとして扱わなければならないという点です。
このケースのように、「インスタンス作成時には値が決まらないけどonCreateなどで代入されるため実質NonNull」という場合はlateinit修飾子を使い、以下のように変数を宣言します。
1 2 |
private lateinit var textView: TextView private lateinit var button: Button |
lateinitを使うことで変数宣言時に初期値を入れる必要がなくなり、NonNullの変数として扱えるようになりました。
注意点としては
・lateinitはvarにしか付けれないということ
・インスタンス代入前にアクセスするとUninitializedPropertyAccessExceptionが投げられる
ということです。
なので、何でもlateinitを付けてNonNullするのではなく、非同期処理の結果を入れる変数など代入されるタイミングが定かではないものは素直にNullableとして扱うべきです。
以上がNull安全についての説明になります。
最後にその他説明できなかった、Nullableの変数に対するアプローチの仕方を記載しておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
val a: String? = "KotlinはNull安全" a?.let { // この中の it をNonNullとして扱える println(it) } // aがnullじゃなければaのlengthを、aがnullなら0を変数lengthに代入する val length = a?.length ?: 0 println("length: $length") if (a != null) { // aがnullではないことが自明なのでaをNonNullとして扱える val length = a.length println("length: $length") } |