LoginSignup
159
166

More than 3 years have passed since last update.

はじめよう、Kotlin(基本編)

Last updated at Posted at 2017-07-22

導入編
基本編 ★イマココ
上級編

はじめに

Kotlinの基本を流します。
すべては やりません

また、let, applyなどのスコープ関数やfilter関数など
Kotlinの真髄を見たい方は基本編ではなく上級編を見てください

Kotlinの魅力

個人的な意見ですが、以下が挙げられます。

  • Nullセーフ
  • 型推論
  • Scope関数が便利すぎ(上級編
  • Extensionが使える(上級編
  • 高階関数が使える(上級編
  • 可読性の高さ
  • Java(既存プロジェクト)との共存
  • Javaコーダーから見たときの慣れの速さ = 学習コストが低い
  • コーディングスピード
  • 何より書いてて楽しい

結果、バグを未然に防げることが多いのではないかと思います。

また、javascriptにもできるそうなので、
ReactNativeなどにも使えるのかもしれませんね。(適当)

Kotlin to Javascript (公式)

サポートツール

Kotlinを勉強、感覚をつかむ上で
便利な情報をまとめておきます。

Try Kotlin

お試しで書いてみたいなー、という方は
Try KotlinからWebブラウザ上で簡単に確認できます。

リファレンス

English(公式)
Japanese(非公式)

offlineで読みたい、という方はpdfもあります。
また、非公式ではありますがAndroid版もあります。
移動中にふんわり見てみるのもいいでしょう。

Kotlin助走読本

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に加えてabを足してみましょう

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
    }
}

breakcontinue としても利用可能です
地味ですが、便利なんです、これ

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()
159
166
5

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
159
166