Kotlinをはじめるにあたって
言語仕様でここは押さえておこう、的な記事です。
普段の開発をJavaからKotlinへ移行していく過程で
これは足りない、と気づいたら随時追記します。
何からはじめればいいですか
Kotlin https://kotlinlang.org/
ここを起点に。本記事では主にAndroid開発を想定しています。
Try Kotlin
ブラウザ上でサンプルを見ながら、その場でコードを書いて動作確認ができます。
- コンソール↓の 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を付けます。アンダースコア区切りの大文字で!
const val MAX_COUNT = 8
Null-safety
null-type と non-null-type
NULLチェックしてないコードはコンパイル段階でエラー
できるだけnon-null-typeを使う。
必要な場合のみnull-typeで。
var a: String = "abc" // non-null type
a = null // 代入だめ。ぜったい。エラー
val l = a.length // ok
var a: String? = "abc" // null type
a = null // null代入できる
val l = a.length // nullチェックしてない。エラー
nullなら実行しない安全呼び出し
Thread thread = null;
if(thread != null)
thread.start();
val thread: Thread? = null
thread?.start()
nullの代替値
nullじゃなかったら設定、のような場合
エルビス演算子?:を使って完結にできます。
if(name != null){
textView.setText(name);
}else{
textView.setText("no name");
}
textView.setText(name?: "no name");
// エルビス演算子おまけ。dataがnullになるならreturnする
val data = Hoge.getData ?: return
// 色を取得できなければ透過色を返す例
fun getColor(@ColorRes color: Int): Int {
return activity?.resources?.getColor(color) ?: Color.TRANSPARENT
}
非null表明
null-typeをnon-null-typeに強制変換しますが使わないほうがいいかも。
fun length(str: String?) : Int = str!!.length
〜中略〜
length("hoge") // 4を返す
length(null) // NullPointerExcelption
どうしてもな場合はrequireNotNull関数でメッセージを残すらしいです。
val a: Int? = null
val b: Int? = requireNotNull(a) {"ぬるぽ"} // 第二引数の文字列は任意
文字列 string
ダブルクォーテーションで囲うのは一緒
文字列中に変数を埋め込む(String Template)場合は$マークを付けて、{}で囲う
val name: String = "ssuzaki"
println("${name}")
型を類推できる場合は更に{}も省略可能
println("$name")
raw string
改行を含めて書いた通りの文字列を表現する場合はダブルクォーテーションを3っつで囲う
行頭の|は目印の記号で、それが結果に含まれないようにtrimMarginしている
fun main(args: Array<String>) {
val text = """
|むかしむかし
|あるところに
|じーさんばーさんが
""".trimMargin()
println("$text")
}
関数 function
int sum(int a, int b){
return a + b;
}
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を作る時なんかにオーバーロードが減るのでいいですね
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);
}
}
open class HogeView : ImageView {
constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0)
: super(context, attrs, defStyleAttr) {
}
}
名前付き引数
引数名を使って関数呼び出しすると似たような関数を減らせます
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つまでしか定義できない
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
}
再帰
// これが
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
関数内関数です。スコープがその関数内に限定されます。
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型の関数は値を返さなくてもいいそうです
class Hoge() {
private var cnt = 0
fun count(): Unit {
cnt ++
}
fun count2() { // Unitは省略するのが普通ぽい
cnt ++
}
}
関数オブジェクト
関数名の頭に::を付けると関数オブジェクトが取得でき、関数として呼び出せます
fun main(args: Array<String>) {
// 型を類推
val function = ::unko
println( function() )
// 型を明示
val function2: () -> String = ::unko
println( function2() )
}
fun unko(): String {
return "unko"
}
ラムダ式
関数を定義せず、直接関数オブジェクトを生成します
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は式で値を返します。なので三項演算子はありません。
// 普通に書けます。
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よりこっちがいいかも
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
when {
hoge.isOld() -> println("old")
hoge.isNew() -> println("new")
}
for
イテレータが提供されます。
// くるくる
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
お馴染みの前判断、後判断、どっちもいけますが
後判断のスコープが便利になってます。
// 前判断
while (x > 0) {
}
// 後判断
do {
val y = retrieveData()
} while (y != null) // yが見える!
範囲指定 range
// 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
// お好みで
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(重複要素あり)
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(重複要素なし)
val list = setOf(1, 2, 2, 3) // immutable(変更不可)変更する場合はmutableSetOf
for(num in list)
print(num) // 123
map(キーと値のペア)
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を付けます。
data class User(val name: String = "", val age: Int = 0)
使い方
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用の関数が作られるみたい
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クラス
enum class Direction {
NORTH, SOUTH, WEST, EAST
}
定義と初期化
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
ちょっと使いやすく
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
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の代わりみたいなの
class MyClass {
companion object { }
}
fun MyClass.Companion.foo() {
println("hoge")
}
fun main(args: Array<String>) {
MyClass.foo()
}
同一 equality
同じ参照先か===で比較。違う参照先は!==
val taro = User("taro", 10)
val jiro = User("jiro", 11)
if(taro === jiro)
println("taro equal jiro")
例外 Exceptions
例外はすべて Throwable の子孫でメッセージ、スタックトレースを持ち
オプションで原因を持ちます。
例外を投げる throw
throw MyException("Hi There!")
例外を捕捉する try...catch...finally
try {
// some code
} catch(e: SomeException) {
// handler
} finally {
// optional finally block
}
tryは式で値を返せる
val a: Int? = try { parseInt(input) } catch (e: NumberFormatException) { null }
クラス class
class Hoge {
// プロパティの定義。getterとsetterが定義される。valならgetterのみ
var name: String = ""
// コンストラクタ
constructor(name: String) {
this.name = name
}
// メソッドの定義
fun changeLanguage(language: String) {
}
}
プライマリコンストラクタ
class Hoge(n: String) {
val name = n
}
// プライマリコンストラクタでプロパティも一緒に定義するケース↓
class Hoge(val name: String) {
}
クラスのネスト
class Hoge {
val num: Int
// 外側のクラスのメンバを参照するときはinner classにしないと参照できないよ
inner class Fugo {
val total = num + 10
// おまけ:inner class から外側のクラスのthisを指したいときは@で指定
// this@Hoge
}
}
継承
// openを付けないと継承できない
open class Base {
}
class Hoge : Base() {
}
プロパティ property properties
引数のない戻り値だけの関数はプロパティとして書いたりするといいかも?
書き方も何パターンかあってgetter or setter またはその両方で用意できます。
// 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
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 で
Hoge hoge = (Hoge) obj;
val hoge = obj as Hoge
スコープ関数 Scope Functions
使い分けが難しく感じるので使いそうな例だけ
let (nullじゃなかったら○○したい)
null-typeの変数と一緒に、nullじゃなかったら○○したいシチュエーションで
// これが
if (hoge != null) {
textView.text = hoge.name
}
// こう書けます
hoge?.let {
textView.text = it.name
}
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元に対する何かを行う
// before
binding.textView1.text = "aaa"
binding.textView2.text = "bbb"
// after
binding.apply {
textView1.text = "aaa"
textView2.text = "bbb"
}
also (編集中)
遅延初期化
lateinit
- 後から初期化するので許してください!みたいな
- isInitialized で初期化されたか判断はできるっぽい
- あまり多様すると初期化される前に触られてクラッシュするので利用は控えめがいいかも
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されたプロパティーはインスタンス作成時はまだ初期化されてません
class Hoge {
// hogeインスタンス作ったときはまだ初期化されておらず
// はじめてnameプロパティにアクセスしたときに委譲先のgetName()で初期化される
// 2度目以降は↑の値を返すので、何度もプロパティーの値を作るのに時間がかからない
val name: String by lazy { getName() }
private fun getName(): String {
// ここでなにかとてつもない重い処理があったりして
return "hoge"
}
}