slides



slides

0 0


slides

スライドすらすら

On Github serorigundam / slides

Kotlin

kotlinとは

2011年に発表されたばかりのJVM言語です。

読み方は「ことりん」、 意味はやかん、 ロゴもやかん。

語感がかわいいですね。

ロシア サンクトペテルブルグのJetBrains社研究所生まれ。

ロシア生まれとかどう考えてもかわいい。

生まれた地に近いコトリン島から命名されました。

つまり

かわいい(大事)

主な特徴や機能

  • 静的型付け
  • クロージャ
  • Null安全
  • 拡張関数
  • インライン関数
  • 単一式関数
  • ローカル関数
  • ジェネリクス
  • フィールドがない(プロパティがある)
  • 演算子オーバーロード
  • ミックスインと第一級デリゲーション
  • スマートキャスト
  • javaScriptへコンパイル可
  • Android上での動作
  • オープンソース(Apache License Version 2.0による)

Kotlinを始めよう

  • 必要なもの
  • InteliJ IDEA
    • 学生ならタダで有料版が使えます。使おう。
  • Kotlin プラグイン
  • ある程度のJavaの知識
  • 多少のバグを許せる心

ここでブラウザ上でKotlin試せるよ!

http://try.kotlinlang.org/

Hello World

Javaの場合

public class Main {
    public static void main(String args[]){
        System.out.println("ほげらりおん");
    }
}

Kotlinの場合

fun main(args: Array<String>){
    println("ほげらりおん")
}

HelloWorldから分かるJavaとは違うところ。

  • 関数の宣言にはfunctionの略であるfunを用いる。
  • 変数の書き方は「変数名: 型名」
  • 関数はクラスに属さなくても良い。
  • セミコロンがいらない

基本的な事について

可視性について

Kotlinの可視性のアノテーションは4種類あります。

  • public

    • すべてのクラスに公開
  • internal

    • 同じパッケージ内のクラスに公開
    • 間違えてました
    • 同一コンパイル単位内に公開
  • protected

    • 自身と自身を継承したクラスに公開
  • private

    • 公開しない

これらアノテーションはクラス、コンストラクタ、プロパティと関数等に付与することができます。

特にこれらを指定しない場合、デフォルトの可視性はpublicです。

Kotlinで扱う基本的な型

Any

KotlinのすべてオブジェクトはAnyクラスを継承しています。

Any is not java.lang.Object

Anyクラスはメンバとして以下のメソッドが定義されています。

  • equals(other: Any?): Boolean

    • 構造の等価性を判断する。
  • hashCode(): Int

    • ハッシュコードを得る
  • toString(): String

    • 文字列で表す

equalsメソッドは == に置き換えることができます。

Unit型

Javaで言うvoidのような物関数等が特に決まって返す値がない場合に返すオブジェクト

数値型

型 ビット幅 Byte 8 Short 16 Int 32 Long 64 Float 32 Double 64

注意点

KotlinにはJavaのようにプリミティブな型は存在せず、すべてがオブジェクトです。

そして数値型の暗黙的な型変換は存在しません。

val a: Int = 1
val b: Double = a //コンパイルエラー

明示的な型変換を行う必要がある。

val a: Int = 1
val b: Double = a.toDouble() //Double型に明示的に変換

真偽値型

Boolean

組み込み演算子としてJavaと同じく || と && が提供されています。

文字型

Char

Javaのchar型とほぼ一緒

val c: Char = 'あ' //Javaのchar型と同じく ' で囲って表現
val a: Int = c.toInt() //明示的にInt型に変換可

文字列型

String

String型には二種類の文字列リテラルがあります。

テンプレート式を使用できます。

エスケープ文字列

javaと一緒のやつ

val str: String = "ほげらりおん\n" //エスケープシーケンスもJavaと一緒

生文字列

生の文字列をそのまま認識します。 """ で囲って表現

val str: String = """ 
ほげほげほげ
ほげほげ
ほげほげ
\n
""" //エスケープシーケンスも無視してそのまま認識されます。

テンプレート式

文字列の文字列への組み込み

${式} または $変数名 を使用する

val str1: String = "ほげらりをん"
val str2: String = "つよい"
val str3: String = "${str1 + 'は'}$str2" //ほげらりをんはつよい

Range

範囲を表す型

  • CharRange
    • Char型の範囲を表す
  • IntRange
    • Int型の範囲を表す
  • LongRange
    • Long型の範囲を表す

他にもありましたが今では非推奨となっているため紹介しません。

1.rangeTo(4) //1~4の範囲を表すIntRangeを得る
1L.rangeTo(4L) //1~4の範囲を表すLongRangeを得る
'a'.rangeTo('z') //a~zを表すCharRangeを得る
4.downTo(1) // 4~1の範囲を表すIntRangeを得る

rangeTo() は .. downTo() はdownTo に置き換えられる

1..4 //1~4の範囲を表すIntRange
1L..4L //1~4の範囲を表すLongRange
'a'..'z' //a~zを表すCharRange
4 downTo 1 // 4~1の範囲を表すIntRange

なお、rangeTo() downTo()で得られるオブジェクトのクラスは実際には各クラスのRange系クラスのスーパークラスであるProgression系のクラスです。

rangeTo() downTo() 以外でRangeを得たい場合は各クラスのProgression系クラスのfromClosedRange関数を使用します。

IntProgression.fromClosedRange(1, 4, 1)

第一引数から第二引数の範囲のレンジを得る、第三引数は後述するfor文などで用いる際の増数です。

Rangeはオブジェクト (in or !in) rangeと書くことでBooleanを返す式にすることができます。

println(3 in 1..3) //true   3は1~3の範囲に含まれている?
println(3 !in 1..3) //false 3は1~3の範囲に含まれない?

配列

Array

  • Array型を用いる

  • 配列操作のためのプロパティやメンバ関数を提供。

  • ジェネリクスを用いるため不変です。(Javaの配列は共変)

val intArray: Array<Int> = arrayOf(1, 2, 3, 4)
intArray.set(3,5) //indexが3の要素に5を代入
intArray.forEach {//関数リテラルを用いてリストを順走査するメソッド
    println(it)  //要素を順番に出力
}

さっきの共変不変のくだり

役に立つはずだから興味があれば聞いてね

共変とは?

広い型から狭い型への変換を行うこと

またはそれが可能であること

この説明では、子クラスから親クラスへの変換を行うこと、 またはそれが可能であることを指します。

それらを踏まえて以下のコードを見てみましょう。

Javaの例

//IntegerクラスはNumberクラスを継承しています。
val Integer[] intArray = {1, 2 ,3 ,4}  
val Number[] numArray = intArray //危険な操作だけどコンパイル通っちゃう
numArray[0] = 1.2 //numArrayの要素は実体はIntegerなのでランタイムエラー

Kotlinの例

//IntクラスはNumberクラスを継承しています。
val intArray: Array<Int> = arrayOf(1, 2, 3, 4)
val numArray: Array<Number> = intArray //ここでコンパイルエラー

kotlinの配列は無論共変としても扱える

val intArray: Array<Int> = arrayOf(1, 2, 3, 4)
val numArray: Array<out Number> = intArray //共変は out 型 と記述する

ではKotlinの配列を共変にするとJavaの配列と同じようにランタイムエラーが起こり得るのか?

答えはNo

配列を共変にした時点でsetメソッドは見えなくなります

なぜ?

Javaの例から分かる通り、共変なオブジェクトに値の入力が行えるのは問題がります。

つまり

共変なオブジェクトへは値の入力が行えるべきではない

それを示すのがoutというキーワードであり、それが共変を表す所以です。

共変の逆は反変で in となります。

構文

変数宣言

Kotlinの変数には二種類あります。

読み取り専用と、読み書きできるやつ。

宣言に用いるワードは前者がval後者がvar

val valHoge: String = "hoge" //読み取り専用
var varHoge: String = "hoge" //読み取り書き込みできる

宣言の構文は (var or val) (変数名): (型名) = (初期化式)

いちいち型名書くのがだるい?

もちろん省略できます!

省略しました。

val hoge = "hoge"

初期化時に入ってくる型が明確(型推論が可能)な場合に省略が可能です。

var hoge = "hoge"
hoge = 1

始めに言った通り、Kotlinは静的型付けです。これは許容されません。

インスタンスの生成

Javaと同じくコンストラクタを使用しますが

new は不要です。

val hoge = Hoge() //Hogeクラスのインスタンスを生成

宣言時の型の省略で代入式の両辺に型名を書く必要がなく

とてもスマート

しかし

型の省略は使い方次第でコードの可読性を上げも下げもするので注意!!!

if式

if文では無くif式

つまりどういうこと?

式なので値を返します。

Javaで例えるならば、三項演算子のような振る舞いをします。

まずは普通のif文らしい例から

val hoge = 3
if (hoge != 3) {
    println("うぇい")
} else { 
    println("そいや") //そいや
}

もちろんif-else内での処理が一つだけなら{}は省略できます

val hoge = 3
if (hoge != 3) 
    println("うぇい")
else 
    println("そいや") //そいや

値を返す。

val hoge = 3
val fuga = if(hoge==3) hoge else 0

hogeが3ならばhoge そうでなければ0をfugaに代入

if式は一つしか分岐がない

もしくは一つでもUnit型を返す枝があると値として変数に代入することはできません。

switch文

when式

Kotlinにはswitch文は存在せず、代わりにwhen式が存在します。

if式同様にwhen式は値を返しますが同様に、elseの枝がない、もしくは一つでもUnitを返す枝があると値として変数に代入することはできません。

//hogeはInt型とする
 val hogehoge = when (hoge) {
     1 -> {
         "1"
     }
     2 -> "2" //処理が一つの場合{}を省略できる
     3, 4, 5 -> "3 or 4 or 5" //複数の値を指定できる
     in 6..10 -> "6 to 10" //レンジを条件として使用可能
     else -> "undefined" //else枝
    }

枝ごとにbreakしなくてもいいの?

when式はフォールスルーしません。

For文

他の言語のようにカウンタをインクリメントしながら繰り返しを制御したりするようなfor文ではありません。Javaのfor-eachの様に、ある条件を満たしたオブジェクトから要素を順番に取り出して繰り返し処理を行います。

条件とは

関数、もしくは拡張関数にIteratorを返すiterator()を持っている 関数、もしくは拡張関数に要素を返すnext()とBooleanを返すhasNext()を持っている これら3つの関数がoperatorとしてマークされていること

1もしくは2の条件と3の条件を満たしたオブジェクトは

for (変数: 型名 in リストオブジェクト) {//左辺の型名は省略可
   //処理
} //処理が一つの場合は{}を省略できます。

この形でfor文を使用できます。

RangeはIteratorを継承したProgressionIterator系のオブジェクトをiterator()で返すのでfor文に用いることが可能です。

for(i in 1..5) print(i) // 12345
for(i in 5 downTo 1) print(i)//54321
for(i in 10 downTo 1 step 2) print(i)//108642
for(i in 1..10 step 2) print(i)//13579

while文

Javaと一緒 条件が真の間ループを続ける構文についても特に説明することがないので省きます。

do-while文

Javaと微妙に違います。

果たして!!その違いとは!?

Javaと違ってdoブロック内で宣言をした変数にwhile()の条件式の部分からアクセスできます。

do{
    val hoge = 1
} while(hoge > 1)

こんな感じ

地味にありがたい...

構造ジャンプ演算子

kotlinの構造ジャンプ演算子

  • return
    • 最も内側の関数をリターンする
  • continue
    • 最も近い外側のループの次のステップに進む
  • break
    • 最も近い外側のループを終了する

Kotlinではラベルを用いてこれらの演算子を使用したループ(関数)が属するスコープの任意のループ(returnならば関数)に対して 演算子の効果を発揮させることができます。(にほんごむずかしい)

例を見るのが早いですよね!!!

例 forループの場合

hoge@ //ラベル
for (i in 1..10) {//ループ1
    print("i = $i\n")
    for (j in 1..10) {//ループ2
        print("$j")
        if (j == 5 && i == 3) {
            break@hoge //hogeラベルの付いたループに対してbreak
        }
    }
}

実行結果

i = 1
12345678910
i = 2
12345678910
i = 3
12345

ラベルを付与する場合は ラベル名@ラベルを指定して演算子を用いる場合は 演算子@ラベル名

ラベルという目印のおかげでどのループに対して実行しているのかわかりやすい!!

関数のリターンは後述する関数のところで

クラスとオブジェクトについて

クラスと継承

クラス

Kotlinのクラスは以下の要素で構成されます。

  • 初期化ブロック

  • コンストラクタ

  • プロパティ

  • 関数

  • 内部クラス

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

Kotlinではクラス宣言にはclassキーワードを用います。

class Empty{
}

更にクラスのbodyがない場合{}を省略できます。

class Empty  //最も短いクラス宣言

コンストラクタ

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

Kotlinではコンストラクタを定義する際にはconstructorというキーワードを用います。

クラス名の直後に定義されるコンストラクタを特にプライマリコンストラクタと呼びます。

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

class Person constructor(name: String) {

}

constructor()の後の{}はクラスのボディです。

コンストラクタの処理ブロックではありません。

プライマリコンストラクタ内での処理を行うにはinitというキーワードを用います。

class Person constructor(name: String) {
     init { //コンストラクタに渡されるパラメータを出力
         println(name)
     }
}

プライマリコンストラクタに可視性を指定しない場合constructorキーワードは省略できます。

class Person(name: String){
    init {
       println(name)
    }
}

簡潔になりました。

更にプライマリコンストラクタ渡されたパラメータは初期化ブロックでそのまま使用することができます。

class Person(firstName: String, lastName: String){
    val fullName = "$lastName $firstName" // プロパティ fullNameを初期化
}

更に更に!プライマリコンストラクタに渡された値をそのままクラスのプロパティとして持つことができます。

class Person(val firstName: String, 
             val lastName: String, 
             var age: Int)
//読み取り専用のプロパティとしてfirstNameとlastNameを持つ
//書き換え可能プロパティとしてageを持つ

ただ渡されたデータを保持するだけのようなクラスの定義の際に便利

プライマリコンストラクタに引数を与えない場合は記述を省略できます。

セカンダリコンストラクタ

プライマリコンストラクタ以外のコンストラクタです。

セカンダリコンストラクタはプライマリコンストラクタに 直接または間接的に処理を委任しなければなりません。

処理の委任は

constructor(仮引数...): this(実引数...){ }

と書きます

セカンダリコンストラクタを定義する場合クラスのボディ内にコンストラクタを定義します。

class Person(firstName: String){
    init {
        println(firstName)
    }

    constructor(firstName: String, lastName: String):
    this(firstName){
        println(lastName)
    }
}

継承

Kotlinでの継承に関してのルール

子クラスは必ず親クラスのコンストラクタのいずれかを実行しなけばなりません。

親クラスには必ずopenアノテーションが付与されている必要があります。

親クラスのコンストラクタに引数が与えられないものが存在する場合に限り、 子クラスが実行する親クラスのコンストラクタの指定を省略することが可能です。

省略した場合は自動的に親クラスの引数なしコンストラクタが実行されます。

class クラス名 : 継承元クラス名この形で継承を実現できます。

open class Base(s: String)

class Derived : Base {
    constructor(s: String): super(s) {

    }
}

子クラスのプライマリコンストラクタで親のコンストラクタを実行したい場合は

class クラス名(引数...) : 継承元クラス名(実引数...)とします。

open class Base(s: String)

class Derived(s:String):Base(s)

プロパティ

前述したとおり、Kotlinのクラスはフィールドを持たず、代わりにプロパティを持ちます。

プロパティって?

Javaで例えるならば、フィールドとアクセサ(getter/setter)を組合わせたような物です。

プロパティを定義するにはプライマリコンストラクタの節で示したようにするかクラスのボディ内で定義する必要があります。

プロパティの定義は

(可視性) (var or val) (プロパティ名) :(型) = (初期化式)

と書きます。

変数と同様に型推論が可能な場合に型名を省略できます。

//ほぼ使い回し
class Person(firstName: String, lastName: String){
   //プロパティ fullNameを初期化
   private val fullName = "$lastName $firstName" 
}

もちろん、Javaのフィールドの様にコンストラクタ内での初期化も可能です。

アクセサ

プロパティにはgetter/setterを定義可能です。

そして、このアクセサにも可視性のアノテーションを付与することが可能です。

アクセサはプロパティの定義の下に。

(可視性) get(){処理}(可視性) set(仮引数){処理}

このような形で定義します。

getterの処理は最後にそのプロパティの型のオブジェクトをsetterは最後にUnitを返す必要があります。

アクセサ内で実行する処理が一つの場合は

(可視性) get() = 処理(可視性) set(仮引数) = 処理

と書くことができます。

アクセサの可視性だけ指定したくて処理は特にいらない場合は

プロパティの定義(可視性) get(可視性) set

このように書けます。

もちろんvalで定義されたプロパティはsetterを持つことができません。

val hoge: String //fugaを取得する読み取り専用プロパティ
    get() {
        return "hoge"
    }

var fuga = "fuga"
    set(value) = print(value)

アクセサからプロパティにアクセスするためにはバッキングフィールドを用います。

アクセサ内から単にプロパティ名を指定してプロパティにアクセスしようとすると再度アクセサを経由することになり再帰によるオーバーフローが起こりえます。

//マイナスの値を受け付けないInt型プロパティ
var fuga = 1
    set(value){
        if(value >= 0){
            field = value
        }
    }

バッキングフィールドによりアクセサないから直接プロパティにアクセスすることができます。

バッキングフィールドにアクセスするにはfieldを用います。

関数

kotlinの関数は必ず値を返さなければなりません。

目立って返す値がない場合にはUnitを返すようにします。

関数を定義するにはfunctionの略のfunキーワードを用います。

fun hoge(): Unit{

}

(可視性) fun (関数名)(仮引数....): 返り値の型 {処理}の形で宣言をします。

Unitを返す場合に限り、戻り値の型は省略することができます。

これは最もシンプルな関数定義の例です。

fun simple(){
}

序盤に言ったように、関数は必ずしもクラスに属する必要はありません。

トップレベルに宣言をしてそれをグローバルに用いることができます。

また、トップレベルに関数を定義した際にはprotected以外の可視性を指定できます。

例えばprivateを指定すれば、関数を定義したファイル以外からその関数を視ることができなくなります。

関数をクラス内で定義した場合、それはメンバ関数(いわゆるメソッド)となります。

メンバ関数へはそのクラスのインスタンスからアクセスします。

class Hoge{
    fun hoge(){
        println("ほげらりをん")
    }
}

使用時

val hoge = Hoge()
hoge.hoge()

単一式関数

Kotlinには関数を宣言的に書くための仕組みとして、単一式関数と言う糖衣構文があります。

簡単に言えば人間にとって、より直感的でより理解しやすい構文です。

これにより関数を宣言的に書くことができます。

通常の関数定義

fun hoge(a: Boolean, b: Boolean): Boolean {
    return a && b
}

上記関数を単一式関数に変換

fun hoge(a:Boolean, b: Boolean): Boolean = a && b

(可視性) fun (関数名)(仮引数...): (返り値の型) = (単一の処理)のように書くことで定義できます。

また、単一式関数は返り値の型を推論可能なのでそれを省略できます。

fun hoge(a:Boolean, b: Boolean) = a && b

更に簡潔になりました。

高階関数

kotlinは高階関数を持てます。

高階関数って何...?

値として受け渡したりできる関数のことを指します。

ざっくり言ってしまえばオブジェクトとしての関数です。

ここでは前述した関数を宣言された関数、高階関数は関数オブジェクトと呼ぶことにします。

宣言された関数はそれをそのままオブジェクトとして扱うことはできません。

関数オブジェクトを作るには

val function = fun(s: String) {
    println(s)
}

このように通常の関数宣言から関数名を抜いたものを変数に代入するように記述します。

ちなみにこの変数の型は (String) -> Unit 型です。String型の引数を一つ受け取りUnitを返す関数型という事になります。

もう一つ、書き方があります。

val function: (String) -> Int = {str ->
   str.toInt()
}

この{}の部分を関数リテラルと呼びます。

{ 仮引数... -> 処理 }という形で書きます。

この例ではfunctionは引数にString型を受け取りIntを返す関数型のオブジェクトです。基本的にreturnは使用できず、一番最後の処理で返る値が評価され、そのまま返り値になります。

関数リテラルは引数が一つの場合にそれを省略し、itで参照することができます。

 val function: (String) -> Int = {
       it.toInt()
    }

関数オブジェクトを実行

関数オブジェクトを実行するにはinvoke()メソッドを使用します。

val function: (String) -> Int = {
           it.length
        }

function.invoke("ほげ")

またinvoke()は省略することが可能です。

val function: (String) -> Int = {
           it.length
        }

function("ほげ")

これにより直感的に関数オブジェクトを実行できる。

関数オブジェクトを関数に渡す。

fun hoge(function: (String) -> Unit) {
    function("hoge")
}

//使用
hoge( {
    println(it)
})

関数に関数リテラルを一つだけ渡す場合、()を省略できます。

fun hoge(function: (String) -> Unit) {
    function("hoge")
}

hoge {
    println(it)
}

ここで突然、構造ジャンプ演算子 returnについて

関数に関数リテラルを渡すとき、その関数はネストしていますよね?

なので、ラベルを用いて終了する関数を指定することができます。

ここではKotlinのArrayクラスのforEachを用いて例を示します。

//適当な値の入ったArray<Int>なオブジェクト
val intArray = arrayOf(1, 2, 3, 4, 5, 6, 7, 8)

intArray.forEach each@ {
        if (it == 5) {
            return@each //関数 forEachを終了
        }
    }

この場合ラベル名の宣言は省略可能です。その場合のラベル名は関数名になります。

intArray.forEach {
    if (it == 5) {
        return@forEach
    }
}

強い

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

Kotlinにはstaticなメンバは存在しません

その代替手段としてクラス共通のオブジェクトとなるコンパニオンオブジェクトを使用します。

コンパニオンオブジェクトはクラスのボディ内で宣言します。

class Hoge {

   companion object {
        val HOGE = "HOGE"

        fun hoge(){
        }
   }

}

コンパニオンオブジェクトには全ての可視性のアノテーションを付与することができます。

ですがコンパニオンオブジェクト内のメンバにはprotectedのみ付与することができません。

Null安全

KotlinではNull許容型と非Null許容型は明確に区別されます。

Null許容型には 末尾に? を付与します。

var hoge: Strung = null //これはダメ
var fuga: String? = null //行ける

関数やコンストラクタの引数、関数の返り値にも指定可能

fun hoge(s:String?):Int?{
    //省略
}

null許容型オブジェクトのメンバは安全に呼び出すことができます。これを安全呼び出しといいます。 安全呼び出しには . の代わりに ?. や !!. を使用します。

   val hoge = Hoge()

   hoge?.hoge //hogeがNullだった場合Nullが返る
   hoge!!.hoge //hogeがNullだった場合ぬるぽ!

Null合体演算子

かきかけ説明不足なところもあるので随時修正、追記していきます。

Kotlin