はじめに
Kotlinの基本を流します。
すべては やりません
また、let, applyなどのスコープ関数やfilter関数など
Kotlinの真髄を見たい方は基本編ではなく上級編を見てください
Kotlinの魅力
個人的な意見ですが、以下が挙げられます。
- Nullセーフ
- 型推論
- Scope関数が便利すぎ(上級編)
- Extensionが使える(上級編)
- 高階関数が使える(上級編)
- 可読性の高さ
- Java(既存プロジェクト)との共存
- Javaコーダーから見たときの慣れの速さ = 学習コストが低い
- コーディングスピード
- 何より書いてて楽しい
結果、バグを未然に防げることが多いのではないかと思います。
また、javascriptにもできるそうなので、
ReactNativeなどにも使えるのかもしれませんね。(適当)
サポートツール
Kotlinを勉強、感覚をつかむ上で
便利な情報をまとめておきます。
Try Kotlin
お試しで書いてみたいなー、という方は
Try KotlinからWebブラウザ上で簡単に確認できます。
リファレンス
offlineで読みたい、という方はpdfもあります。
また、非公式ではありますが[Android版]
(https://play.google.com/store/apps/details?id=ranjih.kotlinandroid&hl=en)もあります。
移動中にふんわり見てみるのもいいでしょう。
Kotlin助走読本
Kotlinユーザグループの方々がドキュメントを作ってくださっているうえに公開されています。
かなりわかりやすいので、オススメです。
IDEで一括変換
慣れないうちは、IntelliJやAndroidStudioで
Javaで書いた後に一発変換を行なってもいいと思います。
この機能は完全に最適化をしてくれるわけではありませんが、
Kotlinを始めようとしてる人の助けになるはずです。
一括変換後、ガリガリと最適化をしていくと
「こうできるのか!」と理解が深まるのでオススメです。
File
ソースファイル | 拡張子 |
---|---|
Java | .java |
Kotlin | .kt |
Basic
Package
基本的にはJavaと同じように書きます
ただ、メソッドはfunctionとして書きます
package foo.bar
import foo.Bar
import foo.*
fun baz() {}
class Goo {}
// ...
/* ... */
Variable
Kotlinではすべての変数がクラスとして扱われています
そのため、Javaではint
だったのにInt
に統合されています
ここは多く語ることないので、一気にいきます
// Mutable
var arg1 = 1
arg1 = 2 // possible
// Read only
val arg2 = 1
arg2 = 2 // impossible
Type inference
Kotlinは型推論に対応しています
// Type inference
val number = 1 // Int
// Define type
val text: String
Nullable
Javaとの大きな違いはここでしょう
KotlinはSwiftのようにNull許容型と非許容型で変数の型が異なります
// Non Null
val nonNull: String
nonNull = null // impossible
// Nullable
val nullable: String?
nullable = null // possible
これはなかなかKotlinでも肝になるもので、
Nullを考慮したコーディングでまとめています
Array
Kotlinの配列定義は少し特殊です
// ["a", "b", "c"]
val array = arrayOf("a", "b", "c")
// Empty array[100]
val emptyArray: Array<String?> = arrayOfNulls(100)
// ["0", "1", "4", ...]
val ascArray = Array(100, { i -> (i * i).toString() })
// Get and Set
array[0] = "d"
println(array[0])
array.set(0, "d")
println(array.get(0))
List
// List<>
val list = listOf(1, 2, 3)
// MutableList<>
val mutableList = mutableListOf(1, 2, 3)
// ArrayList<>
val arrayList = arrayListOf(1, 2, 3)
// Get and Set
mutableList[0] = 4
println(mutableList[0])
mutableList.set(0, 4)
println(mutableList.get(0))
Map
// Map<>
val map = mapOf(1 to "A", 2 to "B")
// MutableMap<>
val mutableMap = mutableMapOf(1 to "A", 2 to "B")
// HashMap<>
val hashMap = hashMapOf(1 to "A", 2 to "B")
// Get and Set
mutableMap[1] = "a" // the value in [] means the key, Not index.
println(mutableMap[1])
mutableMap.set(1, "aa")
println(mutableMap.get(1))
Numaeric literals
可読性をあげるため、Kotlinでは数字やバイト表記の際に_
で区切りをつけられます
(使ったことないけど便利そう)
val oneMillion = 1_000_000
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010
Getter/Setter
Kotlinでは Getter/Setterが自動で作られます
varのときはGetter/Setter, valのときはGetterだけ作られます
そのため、Javaではこうしていたものが
class Data {
private int param;
Data(int param) {
this.param = param;
}
public int getParam() {
return this.param;
}
public void setParam(int param) {
this.param = param;
}
}
こう書けます
class Data {
val param: Int
constructor(param: Int) {
this.param = param
}
}
余談ですが、もっとコンパクトにこうも書けます
class Data(var param: Int)
他クラスからこれらを参照する場合、
これらのGetter、Setterは変数名をコールするだけで呼ばれます
val data = Data()
data.param = 1 // called setter
println(data.param) // called getter
Customize Getter/Setter
Kotlinでは Getter/Setterが自動で作られます が、
これをカスタムするときは以下のようにします
var upperString: String
get() = field
set(value) {
field = value.toUpperCase()
}
Getter/Setter内ではそのパラメータ自身をfield
と表現します
また、例えば簡単なGetter関数であれば以下のように
変数として定義することができます
fun isAdult(): Boolean {
return age >= 20
}
val isAdult get() = age >= 20 // Property type inferred to be 'Boolean'
Javaでは取得用の関数を多く作っているケースも多いと思いますが、
Kotlinでは大抵のGet関数を変数として1行で表現できちゃう ので、かなり便利です
例えばAndroidのActivityで取得できるgetLayoutInflater()などの、
特定のインスタンスを返すものはこれに変換されているため
書き方がJavaとKotlinで異なります=getの表現が省略されるようです
ただしgetSystemService()などの、
内部処理が複雑なものはgetは省略されないようです
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final LayoutInflater inflater = getLayoutInflater();
// or
final LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val inflater = layoutInflater
// or
val inflater = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
}
Private Setter
内部クラスからは設定したいが、外部からのアクセスは禁止したい場合、
Customize Setter に private
をつければOKです
var upperString: String
get() = field
private set(value) {
field = value.toUpperCase()
}
Any
KotlinではすべてのクラスがAnyクラスを継承しています
JavaのObjectに似ていますが、違うようです
何のメンバも持たない、空っぽクラスだそうです
late initialize
インスタンス変数定義にlateinit
をつけることで、
あとで初期化を行うような変数 をつくることができます
(なぜかはわかりませんが、Int
などのプリミティブ型はダメだそうです)
例えばAndroidでonCreateで初期化する変数などがある場合に便利です
private lateinit var name: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
name = intent.getStringExtra("NAME")
}
利点は、あとで初期化を行えるRead-only のような 変数を NonNullの型で定義 できること
気をつけないといけないことは、影響として完全なNullSafeではなくなるということです
それでも利用したい場面は多々あるかと思います
by lazy
これは 初めて使われたタイミングで初期化ができるRead-onlyな変数 の定義が可能です
設計次第では、タイミングによって値が変わる定義値などもあると思います
Javaではそのときfinal
を外してmutableな変数を用意するしかないですが
Kotlinでは初めて呼ばれたタイミングで初期化ができるのが強みです
定義方法は、変数の後ろに by
とラムダをつけます
こちらもラムダ内のオケツが戻り値を指します
val answer by lazy {
println("Calculating the answer...")
42
}
利用するイメージはこちら
if (needAnswer()) {. // returns the random value
println("The answer is $answer.") // answer is calculated at this point
} else {
println("Sometimes no answer is the answer...")
}
Class
基本はこれだけです
closureをつけてもいいですが
中身を定義するときには必須です
class Empty
インスタンス生成にnew
は不要です
val instance = Test()
val customer = Customer("Name")
Constructor
Primary Constructor
コンストラクタを書くだけであれば、このように書けます
class Person constructor(firstName: String) {}
class Person(var firstName: String) {}
このようにclassに続けて記載するコンストラクタをKotlinでは プライマリコンストラクタ と呼びます
また、先に記載した2つの例には大きな違いがあります
constructor
をつけずに書くと、その値はメンバ変数になります
つまりJavaではこうだったものが
class Person {
private String firstName;
Person(String firstName) {
this.firstName = firstName;
}
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
return this.firstName = firstName;
}
}
こうなるのです!!スマーーート!!
class Person(firstName: String)
ちなみに、valをつけるとGetterだけ作ってくれます
いやん便利
class Person(val firstName: String)
Secondary Constructor
もちろん複数のコンストラクタも書くことができます
firstName
に加えてa
とb
を足してみましょう
class Person (val firstName: String) {
constructor(firstName: String, lastName: String, age: Int) : this(firstName, lastName) {
println("$firstName, $lastName, $age")
}
constructor(firstName: String, lastName: String) : this(firstName) {
println("$firstName, $lastName")
}
}
この場合 インスタンス変数として扱われるのはプライマリコンストラクタの引数のみ です
Initializer blocks
Kotlinでは各クラスにinitializer blocksが用意されており、
これを利用することで コンストラクタが呼ばれる前の処理を記載することが可能です
この中では プライマリコンストラクタの引数を利用することが可能です
class Person (val firstName: String) {
init {
println(firstName)
}
constructor(firstName: String, lastName: String, age: Int) : this(firstName, lastName) {
println("$firstName, $lastName, $age")
}
constructor(firstName: String, lastName: String) : this(firstName) {
println("$firstName, $lastName")
}
}
こう書くと
Person("Hiroki", "Honda")
Person("Hiroki", "Honda", 30)
出力結果はこうなります
Hiroki
Hiroki, Honda
Hiroki
Hiroki, Honda
Hiroki, Honda, 30
Inheritance
クラスの継承やメソッドのオーバーライドには
open
あるいはabstract
をつける必要があります
open
... 継承、オーバーライドの「許可」
abstract
... 継承、オーバーライドの「強制」
Class
自前のスーパークラスを作成する場合はopen
と定義する必要があります
class Person(name: Name) : Human()
open class Human
Method
メソッドをオーバーライドするには スーパークラスのメソッドにopen
をつける必要があります
あとはoverride
をつけるだけで、@
は不要です
class Derived : Base() {
override fun v() {}
// Can Not do this
//override fun nv() {}
}
open class Base {
open fun v() {}
fun nv() {}
}
またfinal
をつけることで、さらにOverrideされることを防ぐことができます
class Another() : Derived() {
// Can Not do this
//override fun v() {}
}
open class Derived : Base() {
final override fun v() {}
}
open class Base {
open fun v() {}
fun nv() {}
}
Property
Kotlinではインスタンス変数もOverrideさせることができます(やったね!)
class Derived(value: Int?) : Base() {
override val param get() = 4
}
open class Base {
// Need initialize
open val param: Int = 0
}
Abstract
Java同様、抽象クラスや抽象メソッドの定義に用いる
openなメソッドのオーバーライドも可能
class Derived(val value: Int) : Base() {
override val param: Int get() = value
override fun f() = super.print()
}
abstract class Base : Core() {
abstract val param: Int
override abstract fun f()
fun print() {
super.f()
}
}
open class Core {
open fun f() {
println("Core")
}
}
この場合、Coreクラスで実装されている f() メソッドだが、
Base抽象クラスでラップすることにより、
それを継承するDerivedクラスではOverrideが必要なものとして扱われる
Interface
Javaと比較して異なるのは以下
-
open
が省略されている - 変数の枠を作れる(中身は入れられない)
- メソッドの中身をいれられる
interface Foo {
val count: Int
//val average: Double = 0 // can not do this
fun f()
fun b() { print("b") }
}
class Bar : Foo {
// necessary to override
override var count: Int = 0
// necessary to override
override fun f() {}
}
Data class
data class User(val name: String, val age: Int)
これはClassとほぼ変わりませんが、以下の点が大きく違います
- パラメータの内容を出力する
toString()
が自動生成される - パラメータの内容をコピーする
copy()
が自動生成される
これらからもわかるように、データクラスには積極的に使いましょう
val userA = User("hiroki", 30)
// Copy age from user A
val userB = userA.copy(name = "honda")
// print all parameters by default toString()
println(userA)
println(userB)
User(name=hiroki, age=30)
User(name=honda, age=30)
このようにtoString()は自動生成され、
copy()で指定した値以外は、新しいインスタンスへコピーされます
指定方法は ([変数名] = [value], ...)
で変更箇所だけ指定すればOKです
Enum
定義方法はJavaと比較するとclass
をつける必要があります
(Javaもほぼクラスだったし、違和感はないはず)
enum class Direction {
NORTH, SOUTH, WEST, EAST
}
Parameter
もちろん、各定義にパラメータを持たせることも可能です
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
Method
そしてKotlinではabstract
なメソッドを用意することで
それぞれの定義ごとにメソッドの動作を変えることが可能です
enum class ProtocolState {
WAITING {
override fun signal() = TALKING
},
TALKING {
override fun signal() = WAITING
};
abstract fun signal(): ProtocolState
}
長くなると見辛そうだけど
シンプルなメソッドばかりであればとても見やすいと思います
Seals class
Enumでは定義値を作っていましたが、
Seaks class ではClassを定義します
Enumとの大きな違いは、
インスタンス単位でパラメータを持たせることが可能なことです。
sealed class Expr {
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
}
こう定義することで、Exprを継承するクラスが
Const, Sum, NotANumber の3つだけ、という書き方になります
なので、whenを使うことでクラス単位で場合分けを行うことができます。
fun eval(expr: Expr): Double = when(expr) {
is Expr.Const -> expr.number
is Expr.Sum -> eval(expr.e1) + eval(expr.e2)
Expr.NotANumber -> Double.NaN
// the `else` clause is not required because we've covered all the cases
}
Function
Javaとは書き方が大きく異なるので、
並べて比較してみて、理解を深めてください
public int double(int x) {
return x * 2;
}
public void printSum(int a, int b) {
println("sum of " + a + " and " + b + " is " + {a + b})
}
fun double(x: Int): Int {
return x * 2
}
fun printSum(a: Int, b: Int) {
println("sum of $a and $b is ${a + b}")
}
また、書き方もいくつかあります
以下例はすべて同じ意味になります
fun double(x: Int): Int {
return x * 2
}
fun double(x: Int): Int {
x * 2 // return is ommited
}
// for single-expression
fun double(x: Int): Int = x * 2
// for single-expression
fun double(x: Int) = x * 2
個人的に一番最後の、戻り値を型推論にするのは
パッと見でわからないのでオススメしません
戻り値がない場合、KotlinではUnitを返します
また、これは省略可能です
fun printSum(a: Int, b: Int): Unit {
println("sum of $a and $b is ${a + b}")
}
fun printSum(a: Int, b: Int) {
println("sum of $a and $b is ${a + b}")
}
// for single-expression
fun printSum(a: Int, b: Int): Unit = println("sum of $a and $b is ${a + b}")
// for single-expression
fun printSum(a: Int, b: Int) = println("sum of $a and $b is ${a + b}")
Default Arguments
Kotlinでは引数のデフォルト値を指定することができます
は?どゆこと?
Javaで書くケースと見比べるとわかりやすいです
fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size) {
...
}
void read(Array<Byte> b) {
read(b, 0, b.size);
}
void read(Array<Byte> b, int off) {
read(b, off, b.size);
}
void read(Array<Byte> b, int len) {
read(b, 0, len);
}
void read(Array<Byte> b, int off, int len) {
...
}
このように デフォルト指定した引数がない場合のfunctionが自動で作成されます
Named Arguments
たとえば複数の同型デフォルト引数があるfunctionを思い浮かべます
利用時、具体的にどの引数を指定されるかが判断つきません
fun reformat(str: String,
normalizeCase: Boolean = true,
upperCaseFirstLetter: Boolean = true,
divideByCamelHumps: Boolean = false,
wordSeparator: Char = ' ') {
...
}
Kotlinではコール時に[変数名] = [値]
と指定することで、
特定の引数を指定することが可能です(data class の copyもこれです)
reformat(str, wordSeparator = '_')
全てを指定する場合はもちろん省略可能です
reformat(str, true, true, false, '_')
// or
reformat(str,
normalizeCase = true,
upperCaseFirstLetter = true,
divideByCamelHumps = false,
wordSeparator = '_'
)
Companion Objects
Kotlinではstaticなクラスやメソッドは存在しません
ではどのように表現するのか、Companion Objectsを使います
class MyClass {
companion object {
fun create(): MyClass = MyClass()
}
}
あとはstaticのように使えます
val instance = MyClass.create()
実はラベルのようなものをつけることができるのですが
ぼくは用途がよくわかっていません...
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
val instance = MyClass.create()
// or
val instance = MyClass.Factory.create()
Control Flow
if
基本的には使い方は同じです
// Traditional usage
var max = a
if (a < b) max = b
// With else
var max: Int
if (a > b) {
max = a
} else {
max = b
}
ただし それぞれのクロージャの最後が if, else に対する戻り値を指します
(なお、Kotlinでは三項演算子が使えません(ショック))
// As expression
val max = if (a > b) a else b
val max = if (a > b) {
print("Choose a")
a
} else {
print("Choose b")
b
}
Range
in
を使うことで値の範囲もとれます
if (x in 1..10) { ... }
!in
とすると「その範囲外」と表現することも可能です
val list = listOf("a", "b", "c")
if (-1 !in 0..list.lastIndex) {
println("-1 is out of range")
}
if (list.size !in list.indices) {
println("list size is out of valid list indices range too")
}
0..3
とやると、0〜3までの範囲を指します(Kotlinでは IntRange
というクラスになります)
0..list.lastIndex
とやると、有効なindex値の配列を指します
また list.indices
は配列やコレクションに用意されているfunctionで、同じ意味です
when
Javaでいうswitch
です(後述しますが、それ以外にもある多機能なヤツです)
ただこちらもif同様、 それぞれのクロージャの最後が戻り値を指します
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
3, 4 -> print("x == 3 or x == 4")
else -> { // Note the block
print("x is neither 1 nor 2")
}
}
// possible
val y = when (x) {
1 -> "x == 1"
2 -> "x == 2"
3, 4 -> "x == 3 or x == 4"
else -> { // Note the block
"x is neither 1 nor 2"
}
}
Range
値の範囲をとる、あるいはそれ以外、という書き方も可能です
when (x) {
in 1..10 -> print("x is in the range")
in validNumbers -> print("x is valid") // validNumbers is array of Int
!in 10..20 -> print("x is outside the range")
else -> print("none of the above")
}
if else
また、よくやる if {} else if {} else if {}...
という書き方も
実はこのwhenを使って表現することができます。
when {
x.isOdd() -> print("x is odd")
x.isEven() -> print("x is even")
else -> print("x is funny")
}
var items = ArrayList<String>()
items.add("orange")
items.add("apple")
when {
"orange" in items -> println("juicy")
"apple" in items -> println("apple is fine too")
else -> println("nothing")
}
最後のケースでは"juicy"
が返り、appleのケースにははいりません
つまり完全にifとして機能しています
Kotlinのwhen
はJavaのswitch
でもありif
でもあるのです。
Smart cast
when
の中で型の一致を検証した場合、その中のclosureではスマートキャストが行われます
fun hasPrefix(x: Any): Boolean {
return when(x) {
// 'is' means 'instanceof'
is String -> x.startsWith("prefix")
else -> false
}
}
is String
が true ということは、必ず x は String型なので、
その中の x は String型として扱われるために
Stringのメンバー関数である startsWith()
が利用できます
蛇足ですが、Kotlinはできる子なので、これくらいであれば一行で書けます。
fun hasPrefix(x: Any) = (x as? String)?.startsWith("prefix") ?: false
for
Kotlinではforの書き方は
Javaでいうforeach for (item in items)
の書き方しか用意されていません
for (item in collection) print(item)
for index
かといってindexをとりたいときがあると思います
そんなときは、配列やコレクションについている withIndex()
を使いましょう
for ((index, value) in array.withIndex()) {
println("the element at $index is $value")
}
forEach
配列やコレクションを持っているときは
foreachという高階関数を使うことができます
一応紹介しますが、詳しくは上級編でやる予定です
items.forEach {
println(it)
}
// if you want to use index
items.forEachIndexed { index, item ->
print("index=$index, item=$item")
}
Range
値の範囲をとる方法はいくつかあります
for (i in 1..100) { ... } // closed range: includes 100
for (i in 1 until 100) { ... } // half-open range: does not include 100
for (x in 2..10 step 2) { ... }
for (x in 10 downTo 1) { ... }
while
これはJavaと同じです
while (x > 0) {
x--
}
do {
val y = retrieveData()
} while (y != null) // y is visible here!
Returns and Jumps
Kotlinでは、それぞれのclosureにラベルのようなものを設置できます。
loop@ for (i in 1..100) {
// ...
}
どんなときに使うねん?
こんなときに使うねん。
Break and Continue Labels
loop@ for (i in 1..100) {
for (j in 1..100) {
if (...) break@loop
}
}
break
を continue
としても利用可能です
地味ですが、便利なんです、これ
Return at Labels
Returnの場合、Kotlinの特徴を知っていないと理解が難しいです。
fun foo() {
ints.forEach {
if (it == 0) return // nonlocal return from inside lambda directly to the caller of foo()
print(it)
}
print("never called if 0 in ints")
}
実はこれ、returnが foo()の returnとして働くんですね
上級編でやりますが、forEach 以外にも便利な関数がたくさん用意されていて、
しばしば「このclosureの中を脱出したいんや!」というときがあります
そんなときはこう書きましょう
fun foo() {
ints.forEach lit@ {
if (it == 0) return@lit
print(it)
}
}
Visibility Modifiers
クラスやメソッド、メンバにつけるスコープも少し変わりました
package
定義 | 意味 |
---|---|
private | その宣言を含むファイルの中でのみ見える |
internal | 同じモジュール内のどこからでも見える |
public | どこでも見える(デフォルト) |
internalは使えません
class and interface
Javaと少し異なるので注意
定義 | 意味 |
---|---|
private | そのクラス内(そのすべてのメンバーを含む)でのみ見える |
protected | private と同じ + サブクラス内でも見えます(デフォルト) |
internal | 同じモジュール内で見える |
public | どこでも見える |
Null safety
KotlinではNullがありえる型とありえない型で区別されているため
それを考慮したコーディングを行う必要があります
// Non Null
val a: String
a = null // impossible
// Nullable
var b: String? = "abc"
b = null // possible
Safe call
ではString?
のlength
を出力してみましょう
val b: String? = null
println("length is ${b.length}")
実はコンパイル通らないんですが、
仮にこれを実行するとするとNullPointerExceptionで落ちてしまいます
そんなときKotlinではNullを想定して ?
をつけると、こう書けます
val b: String? = null
println("length is ${b?.length}")
length is null
つまり b
が null の場合、 nullを返してくれるのです
もちろん重ねて書くことも可能です
bob?.department?.head?.name
Elvis Operator
ちゃうねん、nullじゃないときを書きたいねん!
if (b != null && b.length > 0) {
print("length is ${b.length}")
} else {
print("Empty string")
}
// or
val l = if (b != null) b.length else -1
print("length is $l")
もちろんこれでも書けますが、
Kotlinでは エルビス演算子 ?:
なるものを使うと、Nullケースの処理/戻り値を書けます
println("length is ${b?.length ?: -1}")
// or
val l = b?.length ?: -1
print("length is $l")
いやー、スッキリかけますね!
なによりNullPointerExceptionを未然に防げるのが良いです!
The !! Operator
複雑なコードや、Androidの標準ライブラリを利用していると
「とはいいつつも、このケースはNullありえねぇから!!」というときがあります
そんなときは無理やり使っちゃいましょう
!!
をつけるだけ、お手軽簡単です
val l = b!!.length
もちろんKotlinでは「NPE好きならやりゃええよ?」と非推奨な感じですが
致し方ないシチュエーションというのはあるものです
とはいえ、利用時はNPEに十分気をつけましょう
Safe Casts
Kotlinではキャストはas
を利用するのですが
たびたび問題になるClassCastException... これもキャッチする方法があります
as?
と、asの後ろに?をつけましょう
val aInt: Int? = a as? Int
こうすると、キャストできなかったときにnullが返ります
nullが返る、ということはもちろん エルビス演算子も使えます
val aInt: Int = a as? Int ?: 0
Collections of Nullable Type
これはどちらかといえば上級編もかみますが、
Kotlinで配列(コレクション)にはそれをフィルタリングする関数が用意されています
その中に実は、Nullを抜くというfilter関数があります
val nullableList: List<Int?> = listOf(1, 2, null, 4)
val intList: List<Int> = nullableList.filterNotNull()