Open10

Kotlin学習 その2

Kohne62Kohne62

オブジェクト

  • classの代わりにobjectを使うことでインスタンスが必ず一つしか生成されないシングルトンなクラスを定義できる
object DataProviderManager {
    fun registerDataProvider(provider: DataProvider) {
        // ...
    }
}

// オブジェクトに定義したメソッドを呼び出すには、インスタンスの生成処理を記述せずクラス名から直接メソッドを指定する
DataProviderManager.registerDataProvider(...)
  • KotlinではJavaのようにstaticなメンバを定義することができない
  • staticのようにインスタンス生成をしなくても利用できるメンバを定義するためにはコンパニオンオンジェクトを使用する
class MyClass {
    companion object Factory {
        fun create(): MyClass = MyClass()
    }
}

val instance = MyClass.create()
Kohne62Kohne62

継承

  • open修飾子が付いているクラスは:で継承することができる
  • スーパークラスは一つしか定義することができず、多重継承はできない
open class Person(val name: String)
class Student(name: String, val id: Long): Person(name)

// PersonがスーパークラスでStudentがサブクラスになる
  • スーパークラスのopen修飾子が付いているメンバをサブクラスの同じ名前のメンバでオーバーライドできる
open class Person(val name: String) {
    open fun introduceMyself() {
        println("I am $name")
    }
}

class Student(name: String, val id: Long): Person(name) {
    override fun introduceMyself() {
        println("I am $name. (id=$id) ")
    }
}

val student: Student = Student("きの子", 728)
student.introduceMyself() // 「I am きの子. (id=728)」を出力
Kohne62Kohne62

インターフェース

  • interfaceキーワードで定義できる
interface MyInterface {
    fun bar()
    fun foo() {
        // optional body
    }
}

class Child: MyInterface {
    override fun bar() {
        // body
    }
}

val child = Child()
child.bar() // Child のオーバーライドした bar メソッドで定義されている処理が実行される
child.foo() // MyInterface の foo メソッドで定義されている処理が実行され
る
  • 継承と違ってインターフェースはカンマで区切ることで複数実装が可能
  • インターフェースにインターフェースを継承することもできる
interface A
interface B
interface C: B // インターフェース C にインターフェース B を継承する
class D: A, C // クラス D にインターフェース A とインターフェース C を実装する
  • インターフェースの匿名クラスを生成したい場合、オブジェクト式object:を用いて生成できる

無名(匿名)クラスとは、名前の無いクラスのことです。 既存のクラスを少しだけ修正したクラスを作りたい場合で敢えて名前をつけるほどでもない場合や、インターフェイスを実装したクラスを作りたい場合で、敢えて名前をつけるほどでもない場合に使用されます。

interface Greeter {
     fun greet() {
     }
}

val greeter = object : Greeter {
    override fun greet() {
        println("Hello")
    }
}
Kohne62Kohne62

パッケージ

  • KotlinではJavaと同様にパッケージによりクラスなどの要素を名前空間で区切ることができる
package sample.hoge
class Foo

// 異なるパッケージからはsample.hoge.Fooという名前でアクセスする
  • Java同様importキーワードでインポートすることで単純なクラスメイでアクセスすることができる
package sample.huga
import sample.hoge.Foo

class Bar {
    fun doSomethingGood() {
        Foo() // インポートしているため、クラス名のみでアクセスできている
    }
}
Kohne62Kohne62

アクセス制限

  • 可視性修飾子をつけることにより、パッケージのトップレベルに属する関数やクラスなどの公開範囲を設定することができる
  • トップレベルにおける可視性修飾子
    • public: 公開範囲に制限がなくどこからでもアクセス可能
    • internal: 同一モジュール内に限り、全公開
    • private: 同一ファイル内のみ、アクセス可能
  • クラスにおける可視性修飾子
    • public: 公開範囲に制限がなくどこからでもアクセス可能
    • internal: 同一モジュール内に限り、全公開
    • protected 同一クラス内と、サブクラス内からアクセス可能
    • private: 同一クラス内のみ、アクセス可能
Kohne62Kohne62

データクラス

  • 情報を保持するためだけのクラスを作成する場合、dataクラスとして定義すればgetter,setterやtoString()など標準的なメソッドを自動で生成してくれる
data class Person(val id: Long, var name: String)
  • dataクラスを定義するとコンパイラーは次のメソッドを自動生成する
    • equals()/hashCode()
    • toString()
      • Person(id=100, name=Tarou)形式
    • componentN()
      • Nは1から開始されるメンバー変数の順番。例えばPersonクラスのcomponent2()の場合、nameの値が戻り値となる
    • copy()
      • 特定のメンバ変数が同じのインスタンスを作成したいときに便利なメソッド
// 実装イメージ
fun copy(id: Long = this.id, name: String = this.name): Person {
    return Person(id, name)
}
  • dataクラスにはいくつか制限がある
    • プライマリーコンストラクターは少なくとも一つの引数を持つ必要がある
    • プライマリーコンストラクターの全ての引数はvalかvarで定義する必要がある
    • dataクラスはabstract, open, sealed, innerはつけられない
    • 継承できるのはsealedクラスのみ。Interfaceの実装もできる

sealedクラス:一言で言うと、クラスの継承を制限するための修飾子です。 sealedが付いたクラスを継承するにはある条件を満たす必要があるということです。

  • dataクラスは分解宣言(Destructuring Declarations)をサポートしている
    • 分解宣言:インスタンスを分解して一度に幾つかの変数に代入できる機能
val person = Person(1, "二郎")
val (id, name) = person
println("id: $id - name: $name") // "id: 1 - name: 二郎"と表示される
Kohne62Kohne62

拡張関数

  • 任意の型(クラスやインターフェースなど)に対して外部から関数を追加できる機能
  • 関数を追加したい型を関数名の手前に付け加えるだけで実装できる
fun String.appendBeer(): String = "${this}beer!"
println("I like".appendBeer()) // => I like beer!

拡張プロパティ

  • 拡張関数と考え方はほぼ一緒。任意のクラスに対して外部からプロパティを追加できる
// Kotlinに標準搭載されている拡張プロパティ
val <T> List<T>.lastIndex: Int
    get() = size -1

val arr = listOf(1,2,3)
println(arr.lastIndex) // => 2
  • 拡張関数、拡張プロパティを別パッケージから呼び出す場合はimportが必要
  • 同一パッケージ内であればそのまま呼び出せる
package a
fun String.extentionFun() = "it's cool operation"
val String.extentionProperty : String
get() = "something special"
package b
import a.extentionFun
import a.extentionProperty
fun main(args : Array<String>) {
"hoge".extentionFun()
"fuga".extentionProperty
}
  • 使い方:拡張関数のみ集めたktファイルを作成して拡張関数用のpackageを作成→これを明示的にimportさせることでスコープを分けられ管理がしやすくなる...etc.
Kohne62Kohne62

演算子オーバーロード

  • プログラミング言語で使用できる演算子を新しい型にも適用できるようにする仕組み
  • Kotlinでは使用できる演算子の種類はあらかじめ決められており、各演算子は決まったシグネチャーのメソッド、拡張関数に対応する
class MyInt(val value: Int) {
    // operatorをつけることで対応する演算子のオーバーロードが行える
    // plus => +
    operator fun plus(that: MyInt): MyInt =
        MyInt(value + that.value)
}

fun main(args: Array<String>) {
    val sum: MyInt = MyInt(5) + MyInt(7)
    println(sum.value)
}

演算子と対応するメソッド(公式)

Kohne62Kohne62

イコール

  • 参照型における比較はKotlinでは===で比較する
  • オブジェクトの中身が同じかどうかは==で調べることができる
  • ==も演算子オーバーロードの一つでありシンタックスシュガーとなっている
a == b // => a?.equals(b)?: (b === null)
Kohne62Kohne62

型エイリアス(type alias)

  • 既存の型に別名を与えることができる
  • 用途がわかりやすくなったり、コードの可読性が上がる
typealias Name = String

fun hello(name: Name) {
    println("Hello, $name!")
}

fun main(args: Array<String>) {
    hello("Kotlin")
}
  • 関数型に対して適切に別名をつける使い方がおすすめ
typealias Predicate<T> = (T) -> Boolean