Kotlin概要
- KotlinのソースコードがコンパイルによってJavaバイトコードに翻訳
- javaと違って末尾のセミコロンが不要
- 変数について
- javaと違ってデータ型は後置(例:var str : String = "abc")
- var(再代入可)とval(再代入不可)両種類
- Kotlinが型推論のため、型の省略が可能
var str1:String = "abc" var str2 = "abc"
Null扱い
Null Safety(ヌルセーフティ)の考え方に基づいて扱う
Null Safety機能は、Null Pointer Exceptionを発生させることを防ぎ、
より安全で信頼性の高いコードを書くために役立つ
// 変数にnullを代入不可
val str1: String = "hello"
// 変数にnullを代入可能
val str2: String? = null
-
セーフコール演算子「?.」
セーフコール演算子を使用すると、Nullable型の変数がnullでない場合に限り、
その変数にアクセスできるvar str: String? = null // strがnullでない場合、文字列の長さを出力 println(str?.length)
-
エルビス演算子「?:」
エルビス演算子を使用すると、Nullable型の変数がnullの場合に、
代替の値を指定することができるvar str: String? = null val length = str?.length ?: 0 // strがnullでない場合は文字列の長さ、nullの場合は0を出力 println(length)
-
非nullアサーション演算子「!!」
非nullアサーション演算子を使用すると、
Nullable型の変数に対してnullチェックを行わずに直接アクセスできる
コードの安全性を低下させる可能性があるため、避けるべきです
非推奨ですval str: String? = null // コンパイルエラーが起きず、nullの場合ヌルポが発生する val length = str!!.length
文字列
- 文字列テンプレート
val num = 2
// 波括弧で囲むの場合、式利用可能
println("${num}の3倍は${num*3}") // 2の3倍は6
// 波括弧なしの場合、数値そのもの
println("$num*3") // 2*3
- raw string改行出力可能
// そのまま出力
val rawStr1 = """
a
b
c"""
println(rawStr1)
// `|`とtrimMargin組み合わせてインデントが整える出力可能(余計なスペースが含まない)
val rawStr2 = """
|a
|b
|c""".trimMargin()
println(rawStr2)
リスト
- リストの作成
// 変更不可能なリストの宣言
val readOnlyList = listOf("apple", "banana", "orange")
// 変更可能なリストの宣言
val mutableList = mutableListOf("apple", "banana", "orange")
- リストのアクセス
// リストの要素を取得する
val firstElement = list[0] // "apple"
// リストの要素を変更する
mutableList[0] = "orange"
// リストのすべての要素を反復処理する
for (element in list) {
println(element)
}
// リストのインデックスを使用して要素を反復処理する
for (i in list.indices) {
println("Index: $i, Element: ${list[i]}")
}
- リストの変更
// リストの要素を追加する
mutableList.add("date")
// リストの要素を削除する
mutableList.remove("banana")
// リストの一部を切り取る
val subList = list.subList(1, 3)
- リストの操作
// リストの要素をソートする
val sortedList = list.sorted()
// リストの要素をフィルタリングする
val filteredList = list.filter { it.startsWith("a") }
// リストの要素を変換する
val mappedList = list.map { it.toUpperCase() }
配列
- 配列の作成
// Int型の配列を作成する
val intArray = intArrayOf(1, 2, 3, 4, 5)
// String型の配列を作成する
val stringArray = arrayOf("apple", "banana", "cherry")
// 要素の型が異なる配列を作成する
val mixedArray = arrayOf(1, "two", 3.0, true)
- 配列のアクセス
// 配列の要素を取得する
val firstElement = intArray[0] // 1
// 配列の要素を変更する
intArray[0] = 10
// 配列のすべての要素を反復処理する
for (element in intArray) {
println(element)
}
// 配列のインデックスを使用して要素を反復処理する
for (i in intArray.indices) {
println("Index: $i, Element: ${intArray[i]}")
}
- 配列の変更
// 配列の要素を追加する
stringArray += "date"
// 配列の要素を削除する
stringArray -= "banana"
// 配列の一部を切り取る
val subArray = intArray.sliceArray(1..3)
マップ
- マップの作成
// 変更不可能なマップの宣言
val immutableMap = mapOf("key1" to "value1", "key2" to "value2", "key3" to "value3")
// 変更可能なマップの宣言
val mutableMap = mutableMapOf("key1" to "value1", "key2" to "value2", "key3" to "value3")
- マップのアクセス
// Mapのすべてのエントリを反復処理する
for ((key, value) in immutableMap) {
println("$key = $value")
}
// Mapのすべてのキーを反復処理する
for (key in immutableMap.keys) {
println("Key: $key")
}
// Mapのすべての値を反復処理する
for (value in immutableMap.values) {
println("Value: $value")
}
- マップの変更
// 新しいキーと値を追加する
mutableMap["key4"] = "value4"
// 既存のキーに対応する値を変更する
mutableMap["key1"] = "newValue1"
// キーに対応する値を削除する
mutableMap.remove("key2")
レンジ
数値範囲を表現する
val range = 1..10
val number = 5
if (number in range) {
println("$number is in the range")
}
型エイリアス
typealias を用いて型に別名をつけることができる
長い型の名前を短くしたり、分かりやすくするのに役立つ
class Button {
var onClickListener: ((Button) -> Unit)? = null
}
// type alias利用
typealias OnClickListener = (Button) -> Unit
class Button {
var onClickListener: OnClickListener? = null
}
キャスト(as, as?)
as は型をキャストする際に利用する
String 型ではない変数 a を String 型に変換(キャスト)して s に代入する
var s: String = a as String
// キャスト元が null 許容型の場合は as の代わりに as? を使用する
var n1: Short? = 123
var n2: Int? = n1 as? Int?
演算子オーバーロード
標準の演算子(+、-、*、/など)を、ユーザー定義のクラスに適用することができる
data class Point(val x: Int, val y: Int)
/* このクラスに、`operator`キーワードを使用し、
* `+`演算子をオーバーロードすることで、2つのPointオブジェクトを足し合わせることができる
*/
operator fun Point.plus(other: Point): Point {
return Point(this.x + other.x, this.y + other.y)
}
val p1 = Point(1, 2)
val p2 = Point(3, 4)
val sum = p1 + p2
println(sum) // Point(x=4, y=6)
条件分岐
- if文
※三項演算子がないval result = if (a>b) a else b
- when式
javaのswitchより強い、多くのマッチングが利用可能fun cases(obj: Any) { when (obj) { 1 -> println("One") 2,3 -> println("Two or Three") in 4..9 -> println("Four to Nine") "Hello" -> println("Hello") is Long -> println("Long") !is String -> println("Not a string") else -> println("Unknown") } } fun main(args: Array<String>) { cases(1) // ONE cases("Hello") // Hello val longVal: Long = 1 cases(longVal) // Long cases(2) // Two or Three cases("test") // Unknown }
ループ
- while文
// 前判断
val num = 5
while (0 < num--) {
println(num)
}
// 後判断
var maxNum = 5
do {
var targetNum = maxNum--
println(targetNum)
} while (maxNum > 0)
- for文
for (item in collection)
print(item)
関数
- 関数の基礎
//パターン1⇒ fun 関数名(引数名: 引数の型, ...): 戻り値の型 = 式
fun sum(num1: Int, num2: Int): Int = num1 + num2
//パターン2⇒ fun 関数名(引数名: 引数の型, ...): 戻り値の型 { reutn xxx}
fun sum(num1: Int, num2: Int): Int {
return num1 + num2
}
// 戻り値なしの場合、Unitを返す
fun test(str: String): Unit{
println(str)
return Unit
}
// 省略の場合、Unitをなくす
fun test(str: String){
println(str)
}
// 関数の呼び出し
sum(1, 2)
sum(num2=2, num1=2) // 名前付き引数を利用すれば、順序考慮不要
- デフォルト引数
// 関数の定義
fun defaultValue(str: String = "defalut") {
println(str)
}
// 関数の呼び出し
defaultValue("custom") // custom
defaultValue() // defalut
-
可変長引数
varargを利用して関数の可変長引数を実現できるfun foo(vararg args: String) { args.forEach { println(it) } } fun main() { foo("A", "B", "C") } /* * 可変引数関数に配列を変数として渡す際には、 * スプレッド演算子(*)を用いて配列を展開してから渡す */ fun main() { val arr = arrayOf("A", "B", "C") foo(*arr) }
-
関数オブジェクト
オブジェクトとして扱われる関数fun sum(num1:Int, num2:Int):Int { return num1 + num2 } /* *「::関数名」で関数を参照するオブジェクトを得ることができる * * 関数の型 * (引数の型)->戻り値の型 * 例: * ()->Unit ... 引数なし、戻り値なし * (Int)->Int ... 引数Int、戻り値Int * (Int,Int)->Int ... 引数1Int・引数2Int、戻り値Int * * ※型の省略が可能(val sum1 = ::sum) */ val sum1: (Int, Int)->Int = ::sum println(sum1(1,2)) // 無名関数 val sum2: (Int)->Int = fun(num: Int):Int = num + 2 /* * ラムダ式 * {引数のリスト->関数の本体} * ※return省略時の戻り値は最後に評価した式の結果 * ※戻り値の型は指定不可(型推論で行う) */ val sum3: (Int, Int)->Int = { num1: Int, num2: Int -> val sum = num1 + num2 sum + 1 // 返却結果 } println(sum3(1, 2)) // 暗黙の変数it val sum4: (Int)->Int = {it + 1}
-
高階関数
高階関数はパラメータとして関数を取るか、関数を返す関数です// 条件を渡せるメリットがある fun jobCount(jobs: List<Job>, jobKinds : (Job) -> Boolean) : Int { var jobcount = 0 for(job in job3) { if(jobKinds(job)) jobCount++ } return jobCount } fun xxxJob(Job: Job): Boolean { // xxxJobの条件 } fun yyyJob(Job: Job): Boolean { // yyyJobの条件 } fun main(args: Array<String>) { // xxxJobの件数 println(jobCount(jobs, ::xxxJob)) // yyyJobの件数 println(jobCount(jobs, ::yyyJob)) }
-
拡張関数
既存のクラスに対して機能を追加するために拡張関数を定義することができる
※既存のクラス:ライブラリに含まれるクラスなども追加できるのはkotlinの強さ// javaではStringクラスに追加関数ができない fun String.trimWhitespace(): String { return this.trim() } val str = " Hello, World! " println(str.trimWhitespace()) // "Hello, World!"
-
スコープ関数
-
also
オブジェクト自身をレシーバ(it)としてスコープ内で処理を行うが、
処理後にオブジェクト自身(つまり同じインスタンス)を返す
デバッグや、オブジェクトのプロパティをセットする前にログを取るなどに便利ですval person = Person("John").also { println("Creating person with name ${it.name}") it.name = "Jane" // プロパティ変更も可能 } println(person.name) // 出力:Jane
-
apply
オブジェクト自身をレシーバ(this)としてスコープ内で処理を行い、
処理後にオブジェクト自身を返す
thisキーワードを省略できるため、プロパティの初期化や設定に便利ですval person = Person().apply { name = "Alice" // this.name と書く必要がない age = 30 } println(person) // 出力:Person(name=Alice, age=30)
-
let
オブジェクトをレシーバ(it)としてスコープ内で処理を行い、その結果を返す
nullチェックや一時的な変数操作に便利ですval name: String? = "Kotlin" val length = name?.let { println("The name is $it") it.length // letの最後の式が返される } println(length) // 出力:6
-
run
オブジェクトをレシーバ(this)としてスコープ内で処理を行い、最後の式の結果を返す
特定のオブジェクトの設定や一時的なスコープで計算を行い、別の値を取得したい場合に便利ですval greeting = person.run { "Hello, my name is $name and I am $age years old." } println(greeting) // 出力:Hello, my name is Alice and I am 30 years old.
-
クラス
-
クラスおよびコンストラクタ
class (var name: String, var age: Int) { fun print() { println("${this.name} ${this.age}") } }
-
クラスの継承
open class Foo { open fun print() { println("Foo") } } class Baa(): Foo() { override fun print() { println("Baa") } }
-
抽象クラス(abstract)
// 抽象クラス abstract class Foo { // 抽象プロパティ abstract var name: String // 抽象メソッド abstract fun hello(): String } // 抽象クラスを実装するサブクラス class Baa : Foo() { // 抽象プロパティを実装 override var name: String = "abc" // 抽象メソッドを実行 override fun hello(): String = "Hello" }
-
インタフェース(interface)
interface Printable { fun print() } interface Writable { fun write() } class Animal(var type: String) : Printable, Writable { override fun print() = println("PRINT!!") override fun write() = println("WRITE!!") }
-
データクラス(data class)
データクラスは通常のクラスと同じですが、
.toString() がプロパティ値を含む、
プロパティ値もコピーする .copy() メソッドが自動的に生成// 通常のクラス class Foo1(var name: String) // データクラス data class Foo2(var name: String) fun main() { var a1 = Foo1("Yamada") println(a1) // Foo1@2280cdac var a2 = Foo2("Yamada") println(a2) // Foo2(name=Yamada) var a3 = a2.copy() println(a3) // Foo2(name=Yamada) }
-
シングルトンオブジェクト
objectキーワードを使用し定義する
プログラム内で唯一のインスタンスを持つオブジェクトobject MySingleton { // シングルトンオブジェクトのプロパティやメソッドを定義 } // プロパティやメソッドにアクセスする場合は、クラス名ではなくオブジェクト名を使用 MySingleton.someProperty MySingleton.someMethod()
-
コンパニオンオブジェクト
クラス内に定義されたシングルトンオブジェクト
コンパニオンオブジェクトは、クラス内で定義されることにより、
クラスのインスタンスを作成せずに直接アクセスできるため、
Javaのstaticメソッドに似た機能を提供class MyClass { companion object { fun sayHello() { println("Hello from companion object") } } } // MyClassのインスタンスを作成せずに、MyClass.sayHello()として直接アクセスできる MyClass.sayHello()
-
委譲
あるオブジェクトが他のオブジェクトに特定の処理を委譲する
委譲を実現するためには、byキーワードを使用するinterface Base { fun funA() fun funB() fun funC() } // Base インタフェースを実装する BaseImpl クラス class BaseImpl(): Base { override fun funA() { println("AAA") } override fun funB() { println("BBB") } override fun funC() { println("CCC") } } /* BaseImpl の一部だけを一時的に拡張した機能を利用したい場合、 * Base インタフェースが要求するプロパティやメソッドを全て再定義するのではなく、 * 一部の機能のみを拡張した BaseImplEx クラスを作成して一時的に利用することができる */ class BaseImplEx(b: Base): Base by b { override fun funA() { println("AAA!!!") } } fun main() { val obj = BaseImpl() obj.funA() // AAA BaseImplEx(obj).funA() // AAA!!! }
-
シールドクラス
特定の範囲内で限定されたクラスを定義するために使用される。
シールドクラスは、クラス階層を定義する場合に便利であり、
特定のクラスに対してのみ操作を許可したい場合にも使用される。/* Resultというシールドクラスを定義し、 * Resultクラスのサブクラスとして、SuccessとErrorクラスを定義する */ sealed class Result { class Success(val message: String) : Result() class Error(val errorMessage: String) : Result() } /* processResult関数は、Resultクラスのオブジェクトを受け取り、 * when式を使用しResult.SuccessとResult.Errorに対してそれぞれ異なる処理を行う */ fun processResult(result: Result) { when (result) { is Result.Success -> println("Success: ${result.message}") is Result.Error -> println("Error: ${result.errorMessage}") } } fun main() { val success = Result.Success("Operation succeeded") val error = Result.Error("Operation failed") processResult(success) processResult(error) }