LoginSignup
288

More than 1 year has passed since last update.

最初に押さえておきたいKotlin言語仕様

Last updated at Posted at 2016-04-06

Kotlinをはじめるにあたって
言語仕様でここは押さえておこう、的な記事です。

普段の開発をJavaからKotlinへ移行していく過程で
これは足りない、と気づいたら随時追記します。

何からはじめればいいですか

Kotlin https://kotlinlang.org/
ここを起点に。本記事では主にAndroid開発を想定しています。

Try Kotlin

ブラウザ上でサンプルを見ながら、その場でコードを書いて動作確認ができます。
image.png

  • コンソール↓の Open in Playground → からエディタが開きます。
  • More examples からはカテゴリごとにサンプルコードが見れます。

Kotlin Language Documentation

英語ですけどコードは読めるのでなんとなく意味はわかります。
https://kotlinlang.org/docs/kotlin-docs.pdf
或いはこちらの記事が日本語だし読みやすいかも
https://dogwood008.github.io/kotlin-web-site-ja/docs/reference/

地味なとこ

  • 文末のセミコロンが要らない
  • 拡張子 .kt

val と var の違い

  • val:読み込み専用(Getterのみ)
  • var:書き換え可能(Getter,Setter)

定数(const)

constを付けます。アンダースコア区切りの大文字で!

kotlin
const val MAX_COUNT = 8

Null-safety

null-type と non-null-type

NULLチェックしてないコードはコンパイル段階でエラー
できるだけnon-null-typeを使う。
必要な場合のみnull-typeで。

non-null-type
var a: String = "abc"	// non-null type
a = null				// 代入だめ。ぜったい。エラー
val l = a.length		// ok
null-type(?をつける)
var a: String? = "abc"	// null type
a = null				// null代入できる
val l = a.length		// nullチェックしてない。エラー

nullなら実行しない安全呼び出し

java
Thread thread = null;
if(thread != null)
    thread.start();
kotlin
val thread: Thread? = null
thread?.start()

nullの代替値

nullじゃなかったら設定、のような場合
エルビス演算子?:を使って完結にできます。

java
if(name != null){
    textView.setText(name);
}else{
    textView.setText("no name");
}
kotlin
textView.setText(name?: "no name");
kotlin
// エルビス演算子おまけ。dataがnullになるならreturnする
val data = Hoge.getData ?: return
kotlin
// 色を取得できなければ透過色を返す例
fun getColor(@ColorRes color: Int): Int {
    return activity?.resources?.getColor(color) ?: Color.TRANSPARENT
}

非null表明

null-typeをnon-null-typeに強制変換しますが使わないほうがいいかも。

kotlin
fun length(str: String?) : Int = str!!.length

〜中略〜

length("hoge") // 4を返す
length(null)   // NullPointerExcelption

どうしてもな場合はrequireNotNull関数でメッセージを残すらしいです。

kotlin
val a: Int? = null
val b: Int? = requireNotNull(a) {"ぬるぽ"}  // 第二引数の文字列は任意

文字列 string

ダブルクォーテーションで囲うのは一緒
文字列中に変数を埋め込む(String Template)場合は$マークを付けて、{}で囲う

kotlin
val name: String = "ssuzaki"
println("${name}")

型を類推できる場合は更に{}も省略可能

kotlin
println("$name")

raw string

改行を含めて書いた通りの文字列を表現する場合はダブルクォーテーションを3っつで囲う
行頭の|は目印の記号で、それが結果に含まれないようにtrimMarginしている

kotlin
fun main(args: Array<String>) {
    val text = """
        |むかしむかし
        |あるところに
        |じーさんばーさんが
        """.trimMargin()
    
	println("$text")
}

関数 function

java
int sum(int a, int b){
	return a + b;
}
kotlin(4パターン)
fun sum(a: Int, b: Int): Int{
    return a + b
}

fun sum(a: Int, b: Int) = a + b

fun sum(a: Int, b: Int): Unit {  // 何も返さないよ
    print(a + b)
}

fun sum(a: Int, b: Int) {  // Unitは省略できる
    print(a + b)
}

デフォルト引数

CustomViewを作る時なんかにオーバーロードが減るのでいいですね

java
public class HogeView extends ImageView {
	public HogeView(Context context) {
		super(context);
	}

	public HogeView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public HogeView(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
	}
}
kotlin
open class HogeView : ImageView {
    constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0)
    : super(context, attrs, defStyleAttr) {
    }
}

名前付き引数

引数名を使って関数呼び出しすると似たような関数を減らせます

kotlin
fun addData(name: String, age: Int = 0): String {
    return "$name + $age"
}

val result1 = addData(age = 20, name = "hoge")
val result2 = addData("hoge", age = 30)
val result3 = addData("hoge")

可変長引数

可変長にしたい引数をvarargで修飾すると配列として扱われる
1つの関数で1つまでしか定義できない

kotlin
fun main(args: Array<String>) {
    println( sum(1,2,3) )
    println( sum(*intArrayOf(1,2,3)) )  // 配列を渡す場合は*が必要
}

fun sum(vararg items: Int): Int {
    var total = 0
    for(item in items) {
        total += item
    }
    return total
}

再帰

参考

kotlin
// これが
fun sum(num: Int): Int {
    if (num != 0)
        return num + sum(num - 1)
    else
        return num
}

// こう書ける。tailrecで修飾すると再帰が最適化されます。詳しくは参考リンク↑
tailrec fun sum(num: Int, total:Int = 0) : Int
    = if(num != 0) sum(num - 1, total + num) else total

ローカル関数 local function

関数内関数です。スコープがその関数内に限定されます。

kotlin
fun main(args: Array<String>) {
    println( unko(true) )
}

fun unko(isHoge: Boolean): String {
    fun hoge(): String = "hoge"
    if(isHoge) return hoge() else return "unko"
}

値を返さない関数

javaでいうvoid? Unit型の関数は値を返さなくてもいいそうです

kotlin
class Hoge() {
	private var cnt = 0
    fun count(): Unit {
        cnt ++
    }    
    fun count2() {	// Unitは省略するのが普通ぽい
        cnt ++
    }
}

関数オブジェクト

関数名の頭に::を付けると関数オブジェクトが取得でき、関数として呼び出せます

kotlin
fun main(args: Array<String>) {
    // 型を類推
	val function = ::unko
    println( function() )
    
    // 型を明示
    val function2: () -> String = ::unko
    println( function2() )
}

fun unko(): String {
    return "unko"
}

ラムダ式

関数を定義せず、直接関数オブジェクトを生成します

kotlin
fun main(args: Array<String>) {
    val square: (Int) -> Int = { i: Int -> i * i }  // ラムダ式
    val square2: (Int) -> Int = { i -> i * i }      // 型推論
    val square3 = { i:Int -> i * i }                // 型推論
    val square4: (Int) -> Int = { it * it }         // 引数が1つの場合はitの名で変数が使える
}

制御文 Control Flow

if

ifは式で値を返します。なので三項演算子はありません。

kotlin
// 普通に書けます。
var max = a
if (a < b)
    max = b

// elseも書けます。
var max: Int
if (a > b)
    max = a
else
    max = b

// 式
val max = if (a > b) a else b

// 分岐はブロックにできます。ブロックの最後の式が値になります。
val max = if (a > b) {
	print("Choose a")
	a
} else {
	print("Choose b")
	b
}

when

switchみたいなやつの強化版。とてもお世話になりそう。
引数ありなしどっちでもいける。分岐条件に定数じゃないものも書ける。

  • when(引数あり) { ... }
  • when {...} 引数なし
  • ==, ||, && も使える

ので、if-else,else,else...よりも見やすいからifよりこっちがいいかも

kotlin(引数あり版)
fun cases(obj: Any) {
    when (obj) {
        1          -> println("One")
        2,3 ->     -> println("Two or Three")
        in 4..9    -> println("Four to  Nine")
        "Hello"    -> println("Greeting")
        is Long    -> println("Long")
        !is String -> println("Not a string")
        else       -> println("Unknown")
    }
}

fun main(args: Array<String>) {
    cases(1)
    
    cases("Hello")
    
    val a: Long = 1;
    cases(a)	// whenの順番的にOneと表示されると思ったらLong
    
    cases(2)
    
    cases("test")
}
結果
One
Greeting
Long
Not a string
Unknown
kotlin(引数なし版)
when {
    hoge.isOld() -> println("old")
    hoge.isNew() -> println("new")
}

for

イテレータが提供されます。

kotlin
// くるくる
for (item in collection)
	print(item)

// ブロックくるくる
for (item: Int in ints) {
	// ...
}

// インデックスでくるくる
for (i in array.indices)
	print(array[i])

// インデックスと値でくるくる
for ((index, value) in array.withIndex()) {
	println("the element at $index is $value")
}

// 範囲でくるくる
for (i in 1..10) {
    println(hoge(i))
}

// downTo, step の指定
for (i in 100 downTo 1 step 2) {
    println(hoge(i))
}

// listがnullのケースも考慮したパターン1。listがnullだとforに入らない
for (i in list.orEmpty())  {
}

// listがnullのケースも考慮したパターン2。forの前に判断
list?.let {
    for (i in it) {
    }
}

イテレータの自作

規定のメソッドが実装されていればイテレータを備えるクラスを自作できる

fun main(args: Array<String>) {
    for(item in Iterator()) {
        println(item)
    }
}

class IteratorFunctions {
    operator fun hasNext(): Boolean = Math.random() < 0.8
    operator fun next(): String = "unko"
}

class Iterator {
    operator fun iterator() = IteratorFunctions()
}

while

お馴染みの前判断、後判断、どっちもいけますが
後判断のスコープが便利になってます。

kotlin
// 前判断
while (x > 0) {
	
}

// 後判断
do {
	val y = retrieveData()
} while (y != null) // yが見える!

範囲指定 range

kotlin
// xが1〜10
if(x in 1..10)
	println("ok")

// xが1〜10じゃない
if(x !in 1..10)
	println("ng")

// リスト
println((1..5).toList())               // [1, 2, 3, 4, 5]
println((1..5).reversed().toList())    // [5, 4, 3, 2, 1]
println((5 downTo 1).toList())         // [5, 4, 3, 2, 1]
println((1..5 step 2).toList())        // [1, 3, 5]

配列 array

kotlin
// お好みで
val list = arrayOf(1, 2, 3)               // 型は類推される
val list = intArrayOf(1, 2, 3)            // intで
val list: Array<Int?> = arrayOfNulls(3)   // nullな要素を3っつ

list[0] = list[1] + list[2]       // 各要素はお馴染みのカッコで

for(item in list){                // forループくるくる
    println(item)
}

コレクション collection

list(重複要素あり)

kotlin
val list = listOf(1, 2, 3)
list[0] = list[1] + list[2]    // immutable(変更不可)エラー

val list = mutableListOf(1, 2, 3)
list[0] = list[1] + list[2]    // mutalbe(変更可能)

val list = mutableListOf<Hoge>() // 空っぽでinitialize

set(重複要素なし)

kotlin
val list = setOf(1, 2, 2, 3)  // immutable(変更不可)変更する場合はmutableSetOf
for(num in list)
    print(num)  // 123

map(キーと値のペア)

kotlin
val map = mapOf(    // immutalbe(変更不可)。変更する場合はmutableMapOf
    1 to "one",
    2 to "two",
    3 to "three"
)
//for(item in map){
//    println("${item.key} to ${item.value}")
//}
for((key, value) in map)
    println("${key} to ${value}")

println(list[1])
結果
1 to one
2 to two
3 to three
one

データクラス data class

値を保持するだけのクラスならこれで。クラス名の前にdataを付けます。

kotlin
data class User(val name: String = "", val age: Int = 0)

使い方

kotlin
val jack = User(name = "Jack", age = 1)  // jackさん1歳
val olderJack = jack.copy(age = 2)       // jackさんの名前のまま2歳のコピーを

val jane = User("Jane", 35)  // janeさん35歳
val (name, age) = jane       // janeさんの名前、年齢をGET!

copy用の関数が作られるみたい

kotlin
fun copy(name: String = this.name, age: Int = this.age) = User(name, age)

constructor を追加して使いやすく

    data class User(val name: String = "", val age: Int = 0) {
        constructor(name: String, age: Int, email: String) : this(name, age)
    }

enumクラス

kotlin
enum class Direction {
    NORTH, SOUTH, WEST, EAST
}

定義と初期化

kotlin
enum class Color(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF)
}

ちょっと使いやすく

kotlin
enum class HogeType constructor(val value: Int) {
    AAA(0),
    BBB(1),
    CCC(2);

    companion object {
        fun fromInt(value: Int): HogeType {
            return values().firstOrNull { it.value == value } ?: AAA
        }
    }

    val isAAA: Boolean
    get() = this == AAA

    val isBBB: Boolean
    get() = this == BBB
}

シングルトン singleton

kotlin
object SingletonUser {
    private var name: String = "ssuzaki"
    
    fun init(data: String) {
        name = data
    }
    
    fun put() {
        println("${name}")
    }
}

fun main(args: Array<String>) {
    SingletonUser.put()
}

コンパニオンオブジェクト companion

staticの代わりみたいなの

kotlin
class MyClass {
    companion object { }
}

fun MyClass.Companion.foo() {
    println("hoge")
}

fun main(args: Array<String>) {
	MyClass.foo()
}

同一 equality

同じ参照先か===で比較。違う参照先は!==

kotlin
val taro = User("taro", 10)
val jiro = User("jiro", 11)

if(taro === jiro)
	println("taro equal jiro")

例外 Exceptions

例外はすべて Throwable の子孫でメッセージ、スタックトレースを持ち
オプションで原因を持ちます。

例外を投げる throw

kotlin
throw MyException("Hi There!")

例外を捕捉する try...catch...finally

kotlin
try {
    // some code
} catch(e: SomeException) {
    // handler
} finally {
    // optional finally block
}

tryは式で値を返せる

kotlin
val a: Int? = try { parseInt(input) } catch (e: NumberFormatException) { null }

クラス class

kotlin
class Hoge {
    // プロパティの定義。getterとsetterが定義される。valならgetterのみ
    var name: String = ""

    // コンストラクタ
    constructor(name: String) {
        this.name = name
    }

    // メソッドの定義
    fun changeLanguage(language: String) {
    }
}

プライマリコンストラクタ

kotlin
class Hoge(n: String) {
    val name = n
}

// プライマリコンストラクタでプロパティも一緒に定義するケース↓
class Hoge(val name: String) {
}

クラスのネスト

kotlin
class Hoge {
    val num: Int

    // 外側のクラスのメンバを参照するときはinner classにしないと参照できないよ
    inner class Fugo {
        val total = num + 10

        // おまけ:inner class から外側のクラスのthisを指したいときは@で指定
        // this@Hoge
    }
}

継承

kotlin
// openを付けないと継承できない
open class Base {
}

class Hoge : Base() {
}

プロパティ property properties

引数のない戻り値だけの関数はプロパティとして書いたりするといいかも?
書き方も何パターンかあってgetter or setter またはその両方で用意できます。

kotlin
// getterだけ書くパターンいろいろ
val isEnable: Boolean = hoge()
val isEnable: Boolean
    get() = hoge()
val isEnable: Boolean
    get() {
        if (hoge())
            return true
        else
            return false
    }

// setterも用意する場合(varで定義してね)
var isEnable: Boolean = false
    set(value) {
        field = value  // fieldはプロパティのこと。この例では単純に代入
        Hogehoge()     // 代入をトリガーに何か処理をしてもいいし
    }

// setterをprivateにする場合
var isEnable: Boolean = false
    private set

インターフェイス interface

kotlin
interface Hoge {
    fun hoge()
}

// javaでのimplementsは:で
class Sample: Hoge {
    override fun hoge() {
        println("gorua-")
    }
}

// 無名クラスで実装↓
class Sample {
    private val h = object: Hoge {
        override fun hoge() {
            println("gorua-")
        }
    }
}

キャスト cast

as で

java
Hoge hoge = (Hoge) obj;
kotlin
val hoge = obj as Hoge

スコープ関数 Scope Functions

使い分けが難しく感じるので使いそうな例だけ

let (nullじゃなかったら○○したい)

null-typeの変数と一緒に、nullじゃなかったら○○したいシチュエーションで

kotlin
// これが
if (hoge != null) {
    textView.text = hoge.name
}

// こう書けます
hoge?.let {
    textView.text = it.name
}
kotlin
var list: MutableList<Int>? = null
list?.let {
    println(it)  // nullなので出力されない。クラッシュしないよ。
}

list = mutableListOf (1, 2, 3)
list?.let {
    println(it)  // nullじゃないので出力される。
}

with (編集中)

run (編集中)

apply

  • apply元を何度も書かずに済ませたい
  • apply { } 中はapply元に対する何かを行う
kotlin
// before
binding.textView1.text = "aaa"
binding.textView2.text = "bbb"

// after
binding.apply {
    textView1.text = "aaa"
    textView2.text = "bbb"
}

also (編集中)

遅延初期化

lateinit

  • 後から初期化するので許してください!みたいな
  • isInitialized で初期化されたか判断はできるっぽい
  • あまり多様すると初期化される前に触られてクラッシュするので利用は控えめがいいかも
kotlin
class HogeView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0): ConstraintLayout(context, attrs, defStyleAttr) {

    // あとで必ず!必ず初期化しますから!今はご勘弁を!
    lateinit var binding: HogeViewBinding

    init {
        if (isInEditMode) {
            // レイアウトエディターとかでプレビュー中はこっちなのでbindingしないよ
            inflate(context, R.layout.hoge, this)
        } else {
            val inflater: LayoutInflater = LayoutInflater.from(context)
            binding = DataBindingUtil.inflate(inflate, R.layout.hoge, this, true)
        }
    }
}

by lazy

  • プロパティーとかではじめてアクセスしたときに初期化されるようお願い
  • by lazyされたプロパティーはインスタンス作成時はまだ初期化されてません
kotlin
class Hoge {
    // hogeインスタンス作ったときはまだ初期化されておらず
    // はじめてnameプロパティにアクセスしたときに委譲先のgetName()で初期化される
    // 2度目以降は↑の値を返すので、何度もプロパティーの値を作るのに時間がかからない
    val name: String by lazy { getName() }

    private fun getName(): String {
        // ここでなにかとてつもない重い処理があったりして
        return "hoge"
    }
}

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
288