static変数の宣言
companion object
を使用する。
例: シングルトンの生成
class User { lateinit var name: String private set // static変数やメソッドは`companion object`ブロックで宣言する companion object { val instance by lazy { User().apply { name = "jtonic" } } } }
プロパティ
Properties
プロパティの宣言
var
キーワードは、変更可能。val
キーワードは、読み取り専用となる。
class Address { var name: String = "Holmes, Sherlock" var street: String = "Baker" var city: String = "London" var state: String? = null var zip: String = "123456" }
プロパティを使うには、その名前を通してかんたんに参照する。
fun copyAddress(address: Address): Address { val result = Address() result.name = address.name .... return result }
GetterとSetter
プロパティのフルシンタックスは以下になる。
var プロパティ名[: 型] [= 初期化] [get()] [set(value)]
get()を使うことで、Computed property(計算プロパティ)を宣言することができる
val isEmpty: Boolean get() = this.size == 0
setter
var stringRepresentation: String get() = this.toString() set(value) { field = value }
プロパティの型は、getterから推測できる場合は、省略できる。
val isEmpty get() = this.size == 0
アクセス権(visibility)の変更もしくは、それに対するアノテートをする必要があるが、デフォルトの実装に変更は必要としない場合、その本体を宣言することなくアクセッサーを定義することができる。
// setterをprivateに変更する var setterVisibility: String = "abc" private set // setterにアノテートをつける var setterWithAnnotation:Any? = null @Inject set
Backing fields
Kotlinでは、フィールドは値を保持するためのプロパティの一部としてのみ使用される。 フィールドを直接宣言することはできない。
しかしながら、プロパティは背後のフィールドを必要とすることがある。 Kotlinは自動的にそれを提供する。
この背後のフィールドはfield
識別子を使ってアクセッサー内部で参照することができる。
var counter = 0 // 初期化は、背後のフィールドに直接値を代入する set(value) { if (value % 2 == 0) field = value else filed = value + 1 }
Backing properties
暗黙のbacking fieldを使いたくない場合は、backing propertyを使って同じことができる。
// backing property private var _table: Map<String, Int>? = null public val table: Map<String, Int> get() { if (_table == null) { _table = HashMap() } return _table ?: throw AssertionError("Set to null by another thread") }
コンパイル時定数
const
修飾子を使うことによって、コンパイル時の定数であることをマークすることができる。
ただし以下の条件がある。
- トップレベルもしくは、
object
宣言のメンバ もしくは、companion object
String
もしくは基本型による初期化- getterを持たない
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated" // 定数は、アノテーションで使用することができる @Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }
遅延初期化プロパティと変数
lateinit
修飾子をつけることで、遅延初期化することができる。
public class MyTest { // lateinitをつけると初期化コードが不要となる lateinit var subject: TestSubject @SetUp fun setup() { subject = TestSubject() } }
初期化済みかどうかは、.isInitialized
を使う
if (foo::bar.isInitialized) { println(foo.bar) }
クラス継承
継承
すべてのKotlinのクラスは、Any
を継承している。
class Example // 暗黙的に Any を継承している
Any
は3つのメソッドを持っている。
equals()
hashCode()
toString()
クラスは、デフォルトは、finalであり、継承することができない。
// デフォルトは final がつけられる class Parent // This type is final, so it cannot be inherited from class Child: Parent()
継承したい場合は、open
キーワードを付ける必要がある。
open class Base // 継承することができる
継承は、クラスヘッダー部に、コロンのあとに型を置く。
open class Base(p: Int) // Baseを継承 class Derived(p: Int) : Base(p)
派生クラスがプライマリコンストラクタを持つならば、 継承クラスもそのプライマリコンストラクタのパラメーターを使って初期化する。
派生クラスがプライマリコンストラクタを持たない場合は、
セカンダリコンストラクタが、super
キーワードを使ってbase型を初期化する必要がある。
class MyView : View { constructor(ctx: Context) : super(ctx) // セカンダリコンストラクタは異なるbaseのコンストラクタを呼ぶことができる constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs) } ## メソッドのオーバーライド
open class Shape { open fun draw() { /.../ } fun fill() { /.../ } }
class Circle() : Shape() { override fun draw() { /.../ }
// NG fill' hides member of supertype 'Shape' and needs 'override' modifier
// fun fill() { /*...*/ }
}
オーバーライドするメソッドは、`override`修飾子をつける。 親クラスのオーバーライドメソッドを子クラスに禁止する場合は、`final`をつける
open class Rectangle() : Shape() { final override fun draw() { /.../ } }
## プロパティのオーバーライド メソッドと同様にプロパティのオーバーライドもできる。 この際、型を揃える必要がある。また初期化もしくは、getメソッドを宣言する必要がある。 継承するにあたって、`val`から`var`に変更することができる。 (getメソッドに対して、setを追加的にオーバーライドすることが可能)
open class Shape { // 初期化しなかった場合 // Property must be initialized or be abstract // open val vertexCount: Int
open val vertexCount: Int = 0
}
class Rectangle : Shape() { override val vertexCount = 4 }
プライマリコンストラクタ内で、オーバーライドも可能。
interface Shape { // インターフェースのメンバはデフォルトopen val vertexCount: Int }
class Rectangle(override val vertexCount: Int = 4) : Shape
// varに変更も可能 class Rectangle(override var vertexCount: Int = 4) : Shape
## 派生クラスの初期化の順番 最初にベースクラスのコンストラクタに対する引数の評価処理され、 ベースクラスの初期化が完了後、派生クラスの初期化が実行される
open class Base(val name: String) {
init { println("Initializing a base class") }
open val size: Int =
name.length.also { println("Initializing size in the base class: $it") }
}
class Derived( name: String, val lastName: String, ) : Base(name.also { println("Argument for the base class: $it") }) {
init { println("Initializing a derived class") }
override val size: Int =
(super.size + lastName.length).also { println("Initializing size in the derived class: $it") }
}
val d = Derived("tanaka", "yoshio")
/ 実行結果 Argument for the base class: tanaka Initializing a base class Initializing size in the base class: 6 Initializing a derived class Initializing size in the derived class: 12 /
## superclassメソッドへのアクセス innerクラスからsuperクラスにアクセスするには、`super@Baseクラス`を使う
class FilledRectangle: Rectangle() { override fun draw() { val filter = Filter() filter.drawAndFill() }
inner class Filler {
fun fill() { println("Filling") }
fun drawAndFill() {
super@FilledRectangle.draw()
fill()
}
}
}
# まとめ感想 コンストラクタ周りカオスすぎ。 プライマリコンストラクタで宣言している変数がイニシャライザでも使えるというのもしっくりこない。
クラス まとめ
Classes
class
キーワードを使ってクラスを宣言する。
class Invoice { /*....*/ }
- class header: 型パラメーター, プライマリーコンストラクタ,など
- class body
headerもbodyもオプションなので、省略が可能。
class Empty
Constructors
以下のコンストラクタを持つ
プライマリーコンストラクタ
プライマリコンストラクタは、ヘッダーに宣言する。 またプライマリコンストラクタが存在しない場合は、宣言が不要。
class Person constructor(firstName: String) { /*...*/ }
constructor
キーワードは大抵の場合において、省略可能。
class Person(firstName: String) { /*...*/ }
プライマリーコンストラクタにはコードを含めることはできない。
初期化コードは、init
キーワードを付けた初期化ブロックに配置する。
初期化ブロックは、コードに配置された順番に実行される。
class InitOrderDemo(name: String) { // property initializer val firstProperty = "First property: $name".also(::println) // class initializer init { println("First initializer block that prints ${name}") } // property initializer val secoundProperty = "Secound property: ${name.length}".also(::println) // class initializer init { println("Second initializer block that prints ${name.length}") } } InitOrderDemo("hello") /* 実行結果 First property: hello First initializer block that prints hello Secound property: 5 Second initializer block that prints 5 */
プリマリーコンストラクタのパラメーターは初期化ブロック内部で使用することができる。 またプロパティ初期化でも使用できる。
class Customer(name: String) { // property initializers val customerKey = name.uppercase() }
プライマリコンストラクタ内部でプロパティの宣言と初期化が同時に行うことができる。
class Person(val firstName: String, val lastName: String, var age: Int) val tanaka = Person("tanaka", "yasushi", 27) // valプロパティは変更不可 // tanaka.firstName = "yosida" // varプロパティは変更可 tanaka.age += 1
トレーリングコンマをクラスのプロパティ宣言につけることができる。
class Person( val firstName: String, val lastName: String, var age: Int, // trailing comma
コンストラクタにアノテーションもしくは可視化修飾子(visibility modifiers)がついている場合は、constructor
キーワードが必須。
修飾子は、constructor
キーワードの前につける。
class Customer private @Inject constructor(name: String) { /*...*/ }
- 可視化修飾子: デフォルトはpublic
セカンダリコンストラクタ
constructor
キーワードを使って、複数のセカンダリコンストラクタを宣言することができる。
class Pet { // セカンダリコンストラクタ constructor(owner: Person) { owner.pets.add(this) } } // NG // val cat = Pet() // OK val tanak = Person() val cat = Pet(tanaka)
プライマリコンストラクタを持つ場合は、
セカンダリコンストラクタ内部で必ず呼び出す必要がある。
この際、this
キーワードを使用する。
class Person(val name:String) { constructor(name: String, age: Int) : this(name + age.toString()) {} } // プライマリコンストラクタのみ呼び出される val yoshida = Person("yoshida") println(yoshida.name) // yoshida // セカンダリコンストラクタが呼び出される val tanaka = Person("tanaka", 27) println(tanaka.name) // tanaka27
セカンダリコンストラクタは、初期化コードがすべて実行されたあとで実行される。
class Constructors { constructor(i: Int) { println("Constructor $i") } init { println("Init block") } } val c = Constructors(5) /* 実行結果 Init block Constructor 5 */
抽象クラスでないクラスが、プライマリ、セカンダリ両方のコンストラクタを持たない場合、 引数なしのプライマリーコンストラクタが生成される。
もし、publicなコンストラクタを持ちたくない場合は、privateをつけてプライマリーコンストラクタを実装する。
class DontCreateMe private constructor () { /*...*/ } // Cannot access '<init>': it is private in 'DontCreateMe' val c = DontCreateMe()
クラスのインスタンス生成
通常の関数と同様にコンストラクタを呼び出すことで生成することができる
val invoice = Invoice() val customer = Customer("Joe Smith")
Note: Kotlinはnew
キーワードが不要。
クラスメンバ
- コンストラクタ
- 初期化ブロック
- 関数
- プロパティ
- ネスト内部クラス
- Object宣言
継承
クラスは相互に派生させることができ、継承階層を構築することができる。
匿名関数
匿名関数
本体の指定は2種類ある
- 式の代入
- ブロックの指定
式の代入
fun(a: Int, b:Int):Int = a + b
この場合は、代入を使うことができない
// Assignments are not expressions, and only expressions are allowed in this context ints.forEach(fun(v) = sum += v)
ブロックの指定
ブロックを指定する場合は、内部で代入が可能。
var sum = 0 ints.forEach(fun(v) { sum += v }
関連
関数リテラルまとめ
関数リテラル
Kotlinは関数リテラルとして以下が用意されている
- ラムダ式
- 匿名関数
特徴
関数リテラルは、式として渡されるが、値として振る舞う事ができる 。
以下の例を考える
max(strings, { a, b -> a.length < b.length })
関数max
は、高階関数であり、第2引数に関数値を受け取る。
この第二引数は関数としての式であり、関数リテラルと呼ばれている。
これは以下の名前付き関数と同等である。
fun compare(a: String, b: String): Boolean = a.length < b.lenbth
ラムダ式構文
Lambda expression syntax
val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
- ラムダ式は、常に波括弧に囲まれる
- パラメーター宣言部 (
->
の前) - 本体 (
->
の後)
- パラメーター宣言部 (
- ラムダの返り値の型は基本
Unit
であり、最後の式が返り値の型として扱われる
パラメーターを宣言した場合は、型アノテーションを省略できる。
val sum = { x: Int, y: Int -> x + y }
トレーリングラムダを渡す
Passing trailing lambdas
※ trail A<軽い物など>を引きずる
Kotlinでは、簡易として、最後のパラメーターが関数の場合は、カッコの外側にラムダ式を配置することができる。
val product = items.fold(1) { acc, e -> acc * e }
そのような構文を trailing lambda と呼ぶ。
ラムダ式がただ一つの引数の場合は、カッコを省略することができる。
run { println("...") }
it: 暗黙のシングルパラメーター名
ラムダ式が、ただ一つのパラメーターを保つ場合は、
暗黙的にit
が宣言される。
この際、パラメーターの宣言と->
の宣言は許可されない。
ints.filter { it > 0 } // このリテラルの型は、'(it: Int) -> Boolean
ラムダ式から値を返す
Returning a value from a lambda expression
限定返り構文(the qualified return syntax)を使ってラムダからの値を明示的に返すことができるが、 最後の式の値が暗黙の返り値となる。
以下の2つのスニペットは同一である。
ints.filter { val shouldFilter = it > 0 shouldFilter }
ints.filter { val shouldFilter = it > 0 return@filter shouldFilter }
トレーリングラムダと合わせることで、LINQ-Styleコードを取ることができる
strings.filter { it.length > 5 }.sortedBy { it }.map { it.uppercase() }
未使用変数のためのアンダースコア
Underscore for unsued variables
map.forEach { _, value -> println("$value!") }
ラムダ内部の分割代入
Destructing in lambdas
https://kotlinlang.org/docs/destructuring-declarations.html#destructuring-in-lambdas
ラムダパラメーターに対して、分割宣言構文(destructing declarations syntax)を使うことができる。
Pair
タイプ(もしくは、Map.Entry
型やその他の適切なcomponentN
functionsを持つ型)をラムダのパラメーターとして持つ場合は、
複数のパラメーターに分割代入することができる。
map.mapValues { entry -> ${entry.value}" }
上記は、複数のパラメーターに分割できる。
map.mapValues { (key, value) -> "$value" }
匿名関数
匿名関数を使えば、返り値を指定することができる。 (ただし殆どの場合において、不要である。)
fun(x: Int, y: Int): Int = x + y
匿名関数は、通常の関数ととても似ているが、名前が省略されている。 その本体は、式(上記の例)もしくは、ブロックを指定することができる。
fun(x: Int, y: Int): Int = { return x + y } // 代入は、ブロックでのみ有効 var sum = 0 ints.forEach(fun(v) { sum += v } // 式内部では、代入は不可 // Assignments are not expressions, and only expressions are allowed in this context // ints.forEach(fun(v) = sum += v)
パラメーターと返り値の型は同時に宣言することができるが、推測できる場合は、排除できる。
ints.filter(fun(item) = item > 0)
NOTE: 匿名関数は、トレーリングラムダ式のように、外部に配置することはできない。
ラムダ式と匿名関数のもう一つの違いは、ローカルでない返り値の振る舞いである。
ラベルの無いreturn
文は、常にfun
キーワードを使って宣言された関数から返される。
つまり、
ラムダ式内部のreturn
は、内含された関数(the enclosing function)から返されるのに対して、
匿名関数内部のreturn
は、匿名関数から返される。
Closures
ラムダ式と匿名関数(ローカル関数かつオブジェクト式)は、そのクロージャーにアクセスすることができる。
※ クロージャー: 外側のスコープに宣言された変数を含む環境
クロージャー内部でキャプチャーされた変数は、ラムダ式内部で編集することができる。
var sum = 0 ints.filter { it > 0 }.forEach { sum += it } print(sum)
匿名関数を使った場合
var sum = 0 ints.filter(fun(v) = v > 0).forEach(fun(v) { sum += v }) print(sum)
- 匿名関数本体に式を渡した場合は、代入はできない
// NG ints.forEach(fun(v) = sum += v)