2011年に発表されたばかりのJVM言語です。
読み方は「ことりん」、 意味はやかん、 ロゴもやかん。
語感がかわいいですね。
ロシア サンクトペテルブルグのJetBrains社研究所生まれ。
ロシア生まれとかどう考えてもかわいい。
生まれた地に近いコトリン島から命名されました。
つまり
ここでブラウザ上でKotlin試せるよ!
Javaの場合
public class Main {
public static void main(String args[]){
System.out.println("ほげらりおん");
}
}
Kotlinの場合
fun main(args: Array<String>){
println("ほげらりおん")
}
Kotlinの可視性のアノテーションは4種類あります。
public
internal
protected
private
これらアノテーションはクラス、コンストラクタ、プロパティと関数等に付与することができます。
特にこれらを指定しない場合、デフォルトの可視性はpublicです。
KotlinのすべてオブジェクトはAnyクラスを継承しています。
Any is not java.lang.Object
Anyクラスはメンバとして以下のメソッドが定義されています。
equals(other: Any?): Boolean
hashCode(): Int
toString(): String
equalsメソッドは == に置き換えることができます。
Javaで言うvoidのような物関数等が特に決まって返す値がない場合に返すオブジェクト
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" //ほげらりをんはつよい
範囲を表す型
他にもありましたが今では非推奨となっているため紹介しません。
例
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式
つまりどういうこと?
式なので値を返します。
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型を返す枝があると値として変数に代入することはできません。
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文ではありません。Javaのfor-eachの様に、ある条件を満たしたオブジェクトから要素を順番に取り出して繰り返し処理を行います。
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
Javaと一緒 条件が真の間ループを続ける構文についても特に説明することがないので省きます。
Javaと微妙に違います。
果たして!!その違いとは!?
Javaと違ってdoブロック内で宣言をした変数にwhile()の条件式の部分からアクセスできます。
例
do{
val hoge = 1
} while(hoge > 1)
こんな感じ
地味にありがたい...
kotlinの構造ジャンプ演算子
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のみ付与することができません。
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だった場合ぬるぽ!
かきかけ説明不足なところもあるので随時修正、追記していきます。