変数の宣言
- 変数の宣言するには、var命令を用いる
var 変数: 変数の型 = 初期値
※ var はvariable(変数)の略
var n: Int = 10
Kotlinの基本のデータ型
型 | 概要 |
---|---|
Double | 64bit浮動小数点数型 |
Float | 32bit浮動小数点数型 |
Long | 64bit整数型 |
Int | 32bit整数型 |
Short | 16bit整数型 |
Byte | 8bit整数型 |
Boolean | 真偽値 |
Char | 文字型 |
String | 文字列型 |
初期値があればデータ型を省略できる
以下のように書いてもnはInt型の変数として定義される
浮動小数店はDouble型とみなされる
var n = 10
var m = 1.5
このように初期値から型を類推する仕組みを型推定・型推論と呼ぶ
初期値とデータ型の両方を省略することはできない
変数に定義したデータ型以外の型の変数を代入することはできない
var n = 10
変数に任意の型を持たせたい場合はAny型を指定する
var n: Any
n = 10
n = "foo"
※ Kotlinでは全てのデータがオブジェクトである
そして全てのデータ型はAny型を継承している
リテラル表現
-
リテラルとは? ⇨ ソースコードに書かれた値のこと
-
リテラル表現とは? ⇨ ソースコードの中の値の書き方・表現方法
ここではよく使う数値リテラルと文字列リテラルについて扱う
※主なリテラル数値
val a = 100 // 10進数
val b = 0x0F // 16進数
val c = 0b101010 // 2進数
val d = 1.23 // 浮動小数点数
val e = 1.23e+5 // 浮動小数点数(指数)
桁区切り文字として『_』を利用することもできる
val a = 123_456_789
リテラル表現(文字列)
kotlinは2種類の文字列リテラルを持つ
①エスケープ文字を含む文字列(Escaped String)
②改行やタブなど任意の文字列を含む文字列(Raw String)
- 文字列テンプレートとは? ⇨ 文字列に任意の式を埋め込むことの出来る機能のこと
var data = arrayOf(1,2,3)
println("配列dataの先頭の値は${data[0]}で、要素数は${data.size}です。")
println("1+1は、${1+1}です。")
nullとnull許容型
-
nullとは? ⇨ 値(オブジェクトの参照)を持たないことを意味する値
-
オブジェクトの参照とは? ⇨ 値(オブジェクト)はPCのメモリに格納されることで状態を保持出来るオブジェクトごとにメモリ上の住所(メモリアドレス)が与えられる。一つの住所に一つのオブジェクトが入っている状態
-
変数はメモリアドレスを参照して格納されている値を引き出すための仕組み
-
変数にオブジェクトの参照が設定されていない状態が『null』である
-
Kotlinでは原則『nullを許容しない』ように作られている
var foo: String = "foo"
foo = null //エラー,nullを代入できない
あえてnullを許容したいときは、型名の後ろに『?』を付ける
var foo: String? = "foo"
foo = null //ok
このような型をNullable型(null許容型)と呼ぶ
Stringに限らずNullable型として定義することができる
※ Nullable型はnullが入っている可能性があるので取り扱いには注意が必要
①非null型をNullable型に代入すると暗黙的な型変換(ボクシング)が発生する
var a: Int = 10000
var b: Int = a
var c: Int? = a
println(a == b) //結果: true
println(a === b) //結果: true
println(a == c) //結果: true
println(a === c) //結果: false
変数aを非null型の変数bとNullable型の変数cにそれぞれ代入している
同じ値を持つことを同値、同じオブジェクトであることを同一という
同値性の比較には『==』、同一性の比較には『===』という演算子を用いる
aとbは同じオブジェクトなので同値かつ同一
一方で、aとcは下2行からもわかるように同値だが同一ではない
⇨ 暗黙的な型変換(ボクシング)が発生して別のオブジェクトになっている
②Nullable型を非null型に代入することはできない
以下のコードはエラーになる
var foot1: String? = "foo"
var foot2: String = foo1 //エラー
では以下のコードはどうなるか?
var foot1: String? = "a"
var foot2: Any = a1
実行してみるとエラーになる
Any型もnullを許容しないことに注意
Nullable型である『Any?』に変更するとエラーが解消される
Any型を指定すると任意の型を代入できるという話をしたが、
厳密には『Any型を指定すると任意の非null型を代入できる』かつ
『Any型を指定するとnull型を含む任意の型を代入できる』と言える
③Nullable型のメンバーにアクセスする際には『?.(セーフコール演算子)』を使う
var a: String? = "foo"
printIn(a?.length) //結果: 3
var a: String? = null
printIn(b?.length) //結果: null
nullの場合に返す既定値を設定するには「?:」演算子を使う
var b: String? = null
printIn(b?.length ?:0) //結果: 0
Nullable型を非null型に変換するには「!!」演算子を使う
var c: String? = "foo"
printIn(c!!.length) //結果: 3
※上記の例でaがnullだった場合にはNullPointerExceptionが発生するので、
Nullable型の値がnullでないことが保証されている際にしか利用してはいけない
型変換
- ワイドニングとは? ⇨ 互換性がある型において、値範囲の狭い型から広い型への変換のこと
var a: Float = 1.2f
var b: Double = 10.0
もしくはtoデータ型()メソッドを用いて変換する
var a = 10
var b: Long = a.toLong()
配列とコレクション
配列
var a = arrayOf(1, 2, 3)
var b = intArrayOf(1, 2, 3) //[1, 2, 3] 要素はInt型
var c: Array<String?> = arrayOfNulls(3) //[null, null, null]
var d = Array(3, {i -> i * 2}) //[0, 2, 4]
println(a[1]) //結果: 2
コレクション
val list = listOf("あ", "い", "う")
val set = setOf("A", "B", "A", "C", "D", "B")
val map = listOf("First" to 1, "Second" to 2, "Third" to 3)
println(list) //結果: [あ, い, う]
コレクションを作成すると読み取り専用になるので、変更可能な高レクションを作成するには、
mutableコレクション名Of()関数を使う
var list = mutavleListOf(1, 2, 3)
list[2] = 4
println(list) //結果: [1, 2, 4]
※通常のListがimutable(変更不可能)なわけではないことに注意
以下のような記述をすれば、Listも変更される
var list1 = mutavleListOf(1, 2, 3)
var list2: List<Int> = list1
list[2] = 4
println(list2) //結果: [1, 2, 4]
定数
変数のように値の名前を与えるが、一度定義したら値が変わらないもの
定数を宣言するには、val命令を用いる(変数はval命令)
定数を変更しようとするとエラーになる
var a = 10
a = 11 //エラー
配列の場合は以下のような振る舞いになる
var a = arrayOf(1, 2, 3)
a = arrayOf(1, 2, 3) //エラー
a[2] = 4 //OK
※varとvalの使い分けは?⇨ valを利用するのが基本
演算子
比較演算子
null許容型のところで登場した『==』『===』など
範囲演算子
『m..n』というように書くとm~nの範囲を表現することができる
var i = 10
println(i in 1..20) //結果: true
制御構文
if式
条件分岐は『if式』を使って表現する
val a = 10
if(a <= 5) {
println("aは5以下です")
} else if (a <= 10) {
println("aは10以下です")
} else {
println("aは10より大きいです")
}
kotlinにおいて『if』は『式』であるので、値を返すことができる
val a = 10
var msg = if(a <= 5) {
"aは5以下です"
} else if (a <= 10) {
"aは10以下です"
} else {
"aは10より大きいです"
}
println(msg) //結果: aは10以下です
ifを式として用いた場合は何らかの値を返す必要があるので、elseは省略できない
{}の中には複数業の記述の可能、その場合最後に書いた値を戻り値とみなす
処理を一文しか書かないならば、{}は省略可能なので以下のようにも書ける
val a = 10
var msg = if(a <= 5) "aは5以下です" else if (a <= 10) "aは10以下です"
else "aは10より大きいです"
when式
式の値に応じて処理を分岐させるには『when式』を使う
val x = 1
when(x) {
1 -> println("xは1です")
2 -> println("xは2です")
else -> { //ブロックとして書くと複数行処理を書ける
println("xは1でも2でもないです")
}
}
スマートキャストとは?
型チェックが行われた後のブロックでは、該当する変数をその型として扱うことができる
機能のこと、暗黙的にキャスト(型変換)を行う機能とも言える
val obj: Any = "あいうえお"
when (obj) {
is String -> println(”文字数は${obj.length}です”)
else -> println(”String型ではない型です”)
} //結果: 文字数はSです
引数を取らずに宣言すれば、if式の代替としても使える
val a = 10
when {
a <= 5 -> println("aは5以下です")
a <= 10 -> println("aは10以下です")
else -> println("aは10より大きいです")
}
}
forループ
反復処理を行うにはforループを用いる
val arr = arrayOf(1, 2, 3)
for (item in arr) {
println(item)
}
Mapにも使用できる
val map = mapOf("First" to 1, "Second" to 2, "Third" to 3)
for ((key, value) in map) {
println("${key} : ${value}")
}
配列でもwithIndex()メソッドを用いて、インデックスと値をセットで取得できる
val list = listOf("あ", "い", "う")
for ((index, value) in list.withIndex()) {
println("${index} : ${value}")
}
範囲演算子を用いれば指定した回数を繰り返すことも可能
for (i in 1..3){
println(i)
} // 結果: 1,2,3
範囲演算子を使うと3が含まれる(1 <= i <= 3)が、3を含めたくない場合(1 <= i <= 3)は、
untilを用いる
for (i in 1 until 3){
println(i)
} // 結果: 1,2
範囲演算子やuntilを使うとインクリメント(1ずつ増加)されるが、デクリメント(1ずつ減少)させる
にはdownToを用いる
for (i in 1 downTo 3){
println(i)
} // 結果: 3,2,1
デフォルトでは1ずつ増加・減少するが、Stepで増分・減分を指定することもできる
for (i in 1..10 step 3){
println(i)
} // 結果: 1, 4, 7, 10
ループを中断するにはbreak, 周回をスキップするにはcontinueを用いる
for (i in 1..10){
if(i % 3 == 0) continue
println(i)
if(i == 9) break
} // 結果: 1, 2, 4, 5, 7, 8
breakとcontinueは現在のループと周回を脱出する
for (i in 1..3) {
for(j in 1..3) {
if(i * j > 5 ) break
print("${i * j}")
}
println()
}
『i * j』が5より大きくなった時点で、内側のループだけでなく、外側のループも終了させるには
ラベル構文を用いる
outer@ for (i in 1..3) {
for(j in 1..3) {
if(i * j > 5 ) break@outer
print("${i * j}")
}
println()
}
while
var i = 1
while (i < 2){
println(i)
i ++
}
do whileループ
do whileループは条件式の判定が処理の後になる
var j = 2
do {
println(j)
j ++
} while (i < 2)
do whileループはパスワードの入力などに使える
do {
パスワードの入力をしてもらう
} while (パスワードのチェック)
関数
関数の基本
関数を定義するにはfunを使用する
//関数の定義
fun 関数名 (仮引数: 引数の型, ...): 戻りの値の型{処理}
//関数の呼び出し
関数名(実引数, ...)
仮引数と戻り値の型宣言は必須
戻り値がない場合は戻り値の型としてUnitを指定する
fun sayHello(name: String): Unit {
printLn("Hello, ${name}.")
}
sayHello("Masao")
Unit型だけは例外的に省略が可能
fun sayHello(name: String){...}
引数と戻り値の表現方法
引数のデフォルト値
引数が省略されたときに使用されるデフォルト値を設定することもできる
デフォルト値を設定するには『仮引数=デフォルト値』の形で記述する
fun getTriangleArea(width:Double=1.0, height:Double=1.5): Double {
retrun widrh * height / 2
}
printIn(getTriangleArea()) //結果: 0.75(= 1.0 * 1.5 / 2)
printIn(getTriangleArea(3.0)) //結果: 2.25(= 3.0 * 1.5 / 2)
名前付き引数
名前付き引数を使えば、どの引数にどの値を渡すか明示的に指定できる
fun getTriangleArea(width:Double=1.0, height:Double=1.5): Double {
retrun widrh * height / 2
}
// 引数の順番を入れ替え
printIn(getTriangleArea(height = 3.0, width = 2.0 )) //結果: 3.0
// 2個目の引数にだけ値を渡す
printIn(getTriangleArea(height = 3.0)) //結果: 1.5
名前付き引数には以下のメリットがある
- 引数の数が増えてもどの引数に何を渡しているかがわかりやすい
- 定義時の引数の順番による縛りがないので、呼び出しやすいし使いやすい
可変長引数を表現するにはvarargキーワードを用いる
可変長引数は内部的には配列とみなされるのでforループによる処理が可能
fun sum(vararg numbers: Int): Int {
var total = 0
for (number in numbers) {
total += number
}
return total
}
println(allSum(2, 3, 1, 7)) // 出力: 13
スプレッド演算子(*)を使えば可変長引数に配列を渡すこともできる
fun sum(vararg numbers: Int): Int {
var total = 0
for (number in numbers) {
total += number
}
return total
}
val arr = intArrayOf(2, 3, 1, 7)
//スプレッド演算子を使えば配列を可変長引数に配列を渡せる
println(allSum(* arr)) // 結果: 13
//配列をそのまま渡すことはできないのは以下のように書くとエラー
println(arr)
// 可変長引数の一部として配列を渡すことも可能
println(allSum(4, *arr, 3)) // 結果: 20
関数から複数の戻り値を返すことも可能
KotlinではPair(2値)とTriple(3値)を利用することで実現できる
//Pair型の戻り値を定義した関数
①戻り値の型を Pair<Int, Double>としている
2つある戻り値の1つ目の値がInt型、2つ目の値がDouble型であることを表している
fun getSumAverage(vararg values: Int): Pair<Int, Double> {
val result = 0
val count = 0.0
for (value in values) {
result += value
count ++ //インクリメント
}
②戻り値をPair(result, result / count)としている
Pairクラスのコンストラクタに戻り値とする2値を渡している
return Pair(result, result / count)
//Pairクラスのコンストラクタに2値を渡す
}
③getSumAverage関数の戻り値を(sum, average)という形で受け取っている
Pairの2値をsum, averageにそれぞれ代入・定義している(分解宣言)
fun main(args: Array<String>) {
val (sum, average) = getSumAverage(3, 4, 8, 1)
println(sum)
println(average)
}
高階関数
引数として関数を取ったり、戻り値として関数を返す関数を高階関数と呼ぶ
高階関数の一例としてforEachメソッドなどがある
配列の要素を1ずつ取り出して引数の関数に渡していくメソッドである
fun hyoji(n: Int) {
println(n)
}
val arr = arrayOf(1, 2, 3, 4)
arr.forEach(::hyoji) //結果 : 1, 2, 3, 4
関数(メソッド)の引数に関数を渡すには、:: 演算子を用いて ::関数名というように渡す
上記の例をラムダ式で書き換える
val arr = arrayOf(1, 2, 3, 4)
arr.forEach({ n: Int -> printIn(n) }) //結果 : 1, 2, 3, 4
ラムダ式の基本的な書き方
{ 引数 → 関数の本体(処理) }
オブジェクト指向
クラスの基本
class Human {
var name = "名無し"
var age = 20
fun intro(){
printIn(”私の名前は${name}です。${age}歳です。”)
}
}
val human = Human()
human.Intro() //結果: 私の名前は名無しです。20歳です。
アクセス修飾子を利用することもできる
⇨ 変数やメソッドのスコープを定義する機能
※ javaのアクセス修飾子とは似て非なるものなので注意すること!
- public: 全てのクラスからアクセス可能
- protected: 定義されたクラスとそのサブクラスからのみアクセス可能
- intermal: 同じモジュール内のクラスからのみアクセス可能
- private: 定義されたクラスからのみアクセス可能
class Human {
private var name = "名無し"
var age = 20
protected fun intro(){
printIn(”私の名前は${name}です。${age}歳です。”)
}
}
val human = Human()
human.Intro() //結果: 私の名前は名無しです。20歳です。
モジュールとは?
⇨ Kotlinのファイルが一体としてコンパイルされるまとまりのこと
クラスファイルとは?
⇨ ソースコードを実行する過程で生成される中間コード、OSに依存しない
JVMとは?
⇨ クラスファイルをOSごとに実行可能なコードに変換・実行するソフトウェア
プロパティ
kotlinでは他の言語のようなフィールド(クラス内で定義される変数)が使えないので、フィールドの代わりにプロパティという機能がある。プロパティはただ単にクラスが持つ変数ではなく、それ自体がアクセサーを持つ
アクセサーとは?
⇨ 値の取得・設定を行うシンプルなメソッドのこと。getter(取得) setter(設定)
プロパティの基本的な書き方
※getter(取得) setter(設定)はいずれも省略可能
var プロパティ名: データ型 = 初期値
getter関数
setter関数
アクセサーを定義する
class Human {
var name = "名無し"
var age = 20
set(value){
if(value < 0){
printIn("年齢が不正です。")
} else {
field = value
}
}
fun intro(){
printIn(”私の名前は${name}です。${age}歳です。”)
}
}
val human = Human()
human.Intro() //結果: 私の名前は名無しです。20歳です。
human.age = -1 //年齢が不正です
//human.age = -1と書くとageプロパティのsetterが呼び出される
printIn(human.name) //結果: 名無し
//human.nameと書くとnameプロパティのgetterが呼び出される
⇨ 変数にアクセスしているようだが、アクセサー(メソッド)を呼び出している
⇨ デフォルトの実装によりフィールドにアクセスするような振る舞いになる
アクセサーの引数は慣例的にvalueを用いる
fieldはバッキングフィールドと呼ばれる
⇨ プロパティの値を格納するために裏側で自動生成されるフィールド
⇨ アクセサー内部でのみ使用可能なフィールド
valを使ってプロパティを宣言すると読み取り専用のプロパティになる
⇨ setterを使用することはできないgetterは可能
private setとするとクラス外では読み取り専用、クラス内からは変更可能というように、
アクセサのスコープを変更することもできる(setterのみ可能)
コンストラクタ
kotlinのクラスは、プライマリコンストラクタと0個以上のセカンダリコンストラクタを持つことができる
プライマリコンストラクタ
クラスに一つだけ記述できるコンストラクタ
class クラス名 constructor(引数: データ型) {}
Humanクラスにプライマリコンストラクタを使う
class Human constructor(name: String, age: Int){
var name = String
var age = Int
//initブロックに具体的な初期化処理を記述する。またプライマリコンストラクタの引数にアクセスできる
//プロパティの名前と引数の名前が同じ場合、プロパティの名前に『this.』をつけてプロパティを指定していることを明確にする必要がある
init {
this.name = name
this.age = age
}
fun intro(){
printIn(”私の名前は${this.name}です。${this.age}歳です。”)
}
}
val human = Human("太郎", 10)
human.Intro() //結果: 私の名前は名無しです。10歳です。
coustructorキーワードは、プライマリコンストラクタがアノテーションやアクセス修飾子を持たない場合は以下のように省略可能
class Human(name: String, age: Int){}
プロパティの宣言と初期化を同時に行うこともできる
class Human constructor(name: String, age: Int){
fun intro(){
printIn(”私の名前は${this.name}です。${this.age}歳です。”)
}
}
下記の場合アクセス修飾子も付与できる
class Human(private var name: String, private var age: Int){}
セカンダリコンストラクタ
一つのクラスに2つ以上コンストラクタを定義する際、ただ一つのプライマリコンストラクタ以降は
セカンダリコンストラクタとして定義
class Human(val name: String, var age: Int) {
constructor(name: String): this(name, 10) {) //一つ目
constructor(): this("太郎"){} //二つ目
fun intro() {
printIn(”私の名前は${this.name}です。${this.age}歳です。”)
}
}
val person1 = Human("三郎", s)
val person2 = Human("次郎")
val person3 = Human()
human1,intro() //結果: 私の名前は三郎です。5歳です。
human2,intro() //結果: 私の名前は次郎です。10歳です。
human3,intro() //結果: 私の名前は太郎です。10歳です。
}
継承
継承とメソッドのオーバーライドを使ったコード
open class Human(var name: String) {
open fun intro() {
println("私の名前は${this.name}です。")
}
}
class PerfectHuman(name: String, var place: String) : Human(name) {
override fun intro() {
super.intro()
println("${this.name}! ${this.name}! I'm a perfect human.")
super.intro()
}
fun liveIn() {
printIn("We live in ${this.place}.")
}
}
val nakata = PerfectHuman("Nakata", "Tokyo")
nakata.LiveIn()
nakata.intro()
参考サイト
はじめての Kotlin【Java 知らなくてOK!丁寧な解説で Android に必要な Kotlin の基本を学習】