Kotlinのオブジェクト指向プログラミング:クラス、継承、プロパティ、インターフェース

クラス

クラスの宣言は以下のようになります。

// クラス宣言
class User { /*...*/ }
// 空のクラス
class Empty
// プライマリコンストラクタ
class User constructor(name: String) { /*...*/ }
// キーワードを省略したプライマリコンストラクタ
class User(name: String) { /*...*/ }
// プライマリコンストラクタのロジックはinitブロックに記述します
// 複数のinitブロックを定義でき、宣言順に実行されます。
class InitializationDemo(name: String) {
    val firstProperty = "最初のプロパティ: $name".also(::println)
    init {
        println("名前 $name を表示する最初の初期化ブロック")
    }
    val secondProperty = "2番目のプロパティ: ${name.length}".also(::println)
    init {
        println("名前の長さ ${name.length} を表示する2番目の初期化ブロック")
    }
}
/*
最初のプロパティ: hello
名前 hello を表示する最初の初期化ブロック
2番目のプロパティ: 5
名前の長さ 5 を表示する2番目の初期化ブロック
*/
// プロパティ
class Client(name: String) {
    val clientKey = name.toUpperCase()
}
// プライマリコンストラクタで複数のプロパティを宣言
class User(val name: String, val surname: String, var age: Int)
// プライマリコンストラクタのプロパティにはデフォルト値を設定できます
class User(val name: String, val surname: String, var isActive: Boolean = true)
// プライマリコンストラクタのプロパティには末尾のカンマを付けることができます
class User(
    val name: String,
    val surname: String,
    var age: Int, // 末尾のカンマ
) { /*...*/ }
// アノテーションとアクセス修飾子付きのプライマリコンストラクタ
class Client public @Inject constructor(name: String) { /*...*/ }
// セカンダリコンストラクタ
class User(val pets: MutableList<Animal> = mutableListOf())
class Animal {
    constructor(owner: User) {
        owner.pets.add(this) // このペットをオーナーのペットリストに追加
    }
}
// セカンダリコンストラクタはプライマリコンストラクタをthisキーワードで呼び出す必要があります
class User(val name: String) {
    val children: MutableList<User> = mutableListOf()
    constructor(name: String, parent: User) : this(name) {
        parent.children.add(this)
    }
}
// プライマリコンストラクタのコードはセカンダリコンストラクタのコードより前に実行されます
class Constructors {
    init {
        println("Initブロック")
    }
    constructor(i: Int) {
        println("コンストラクタ $i")
    }
}
/*
Initブロック
コンストラクタ 1
*/
// プライマリコンストラクタがprivateの場合、インスタンスを作成できません
class DontCreateMe private constructor() { /*...*/ }
// クラスのインスタンス生成にはnewキーワードは不要
val bill = Bill()
val client = Client("Joe Smith")
// 抽象クラス
abstract class Figure {
    abstract fun draw()
}
class Box : Figure() {
    override fun draw() {
        // 長方形を描画
    }
}
// 抽象クラスは非抽象クラスを継承できます
open class Figure {
    open fun draw() {
        // デフォルトの図形描画メソッド
    }
}
abstract class IrregularShape : Figure() {
    // IrregularShapeを継承するクラスは、Figureのデフォルトdrawメソッドの代わりに独自のdrawメソッドを提供する必要があります
    abstract override fun draw()
}

継承

Kotlinでは、クラスとメソッドはデフォルトで継承できません。継承可能にするにはopenキーワードが必要です。一方、インターフェースとそのメソッドはデフォルトでopenです。

// Anyクラスを暗黙的に継承
class Example // 暗黙的にAnyを継承
// openは継承可能を示す
open class Parent // 継承可能なクラス
// 子クラスのプライマリコンストラクタが親クラスのプライマリコンストラクタを呼び出す
open class Parent(p: Int)
class Child(p: Int) : Parent(p)
// 子クラスのセカンダリコンストラクタが親クラスのコンストラクタを呼び出す
class MyView : View {
    constructor(ctx: Context) : super(ctx)
    constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}
// 親クラスのメソッドをオーバーライド
open class Form {
    open fun draw() { /*...*/ }
    fun fill() { /*...*/ }
}
class Round() : Form() {
    override fun draw() { /*...*/ }
}
// 親クラスのメソッドをオーバーライドするが、子クラスでは継承不可にする
open class Box() : Form() {
    final override fun draw() { /*...*/ }
}
// 親クラスのプロパティをオーバーライド
open class Form {
    open val vertexCount: Int = 0
}
class Box : Form() {
    override val vertexCount = 4
}
// プライマリコンストラクタでインターフェースのプロパティをオーバーライド
interface Shape {
    val vertexCount: Int
}
class Box(override val vertexCount: Int = 4) : Shape // 常に4つの頂点
class Polygon : Shape {
    override var vertexCount: Int = 0  // 後で任意の数に設定可能
}
// 親クラスの初期化コードは子クラスの初期化コードより前に実行されます
open class Parent(val name: String) {
    init { println("親クラスを初期化") }
    open val size: Int = 
        name.length.also { println("親クラスでのサイズ初期化: $it") }
}
class Child(
    name: String,
    val surname: String,
) : Parent(name.replaceFirstChar { it.uppercase() }.also { println("親クラスの引数: $it") }) {
    init { println("子クラスを初期化") }
    override val size: Int =
        (super.size + surname.length).also { println("子クラスでのサイズ初期化: $it") }
}
/*
子クラスの構築("hello", "world")
親クラスの引数: Hello
親クラスを初期化
親クラスでのサイズ初期化: 5
子クラスを初期化
子クラスでのサイズ初期化: 10
*/
// superキーワードを使って親クラスのコードを呼び出す
open class Box {
    open fun draw() { println("長方形を描画") }
    val borderColor: String get() = "black"
}
class ColoredBox : Box() {
    override fun draw() {
        super.draw()
        println("長方形を塗りつぶす")
    }
    val fillColor: String get() = super.borderColor
}
// 内部クラスが外部クラスの親クラスのコードを呼び出す
class ColoredBox: Box() {
    override fun draw() {
        val filler = Filler()
        filler.drawAndFill()
    }
    inner class Filler {
        fun fill() { println("塗りつぶし") }
        fun drawAndFill() {
            super@ColoredBox.draw() // Boxクラスのdraw()を呼び出す
            fill()
            println("色付き長方形を描画。色は ${super@ColoredBox.borderColor}") // BoxクラスのborderColorのget()を使用
        }
    }
}
/*
長方形を描画
塗りつぶし
色付き長方形を描画。色は black
*/
// 特殊な構文を使ってインターフェースの多重継承問題を解決
// インターフェースのメソッドはデフォルトでopen
open class Box {
    open fun draw() { /* ... */ }
}
interface Polygon {
    fun draw() { /* ... */ } // インターフェースのメンバーはデフォルトでopen
}
class Square() : Box(), Polygon {
    // コンパイラはdraw()のオーバーライドを要求
    override fun draw() {
        super<Box>.draw() // Box.draw()を呼び出す
        super<Polygon>.draw() // Polygon.draw()を呼び出す
    }
}

プロパティ

// プロパティ宣言
class ContactInfo {
    var name: String = "Holmes, Sherlock"
    var street: String = "Baker"
    var city: String = "London"
    var state: String? = null
    var zip: String = "123456"
}
// プロパティへのアクセス
fun duplicateContact(info: ContactInfo): ContactInfo {
    val result = ContactInfo() // Kotlinには'new'キーワードがない
    result.name = info.name // アクセサが呼び出される
    result.street = info.street
    // ...
    return result
}
// 読み書き可能なプロパティ
var initialized = 1 // 型はInt、デフォルトのgetterとsetterを持つ
// var allByDefault // ERROR: 明示的な初期化子が必要
// 読み取り専用プロパティ
val simple: Int? // 型はInt、デフォルトのgetter、コンストラクタで初期化が必要
val inferredType = 1 // 型はInt、デフォルトのgetter
// 読み取り専用プロパティ、カスタムgetter
class Box(val width: Int, val height: Int) {
    val area: Int // プロパティの型はgetterの戻り値から推論可能なので省略可能
        get() = this.width * this.height
}
/*
幅=3, 高さ=4, 面積=12
*/
// 読み取り専用プロパティ、型の自動推論
val area get() = this.width * this.height
// 読み書き可能なプロパティ、カスタムgetterとsetter
var stringRepresentation: String
    get() = this.toString()
    set(value) {
        setDataFromString(value) // 文字列を解析して他のプロパティに値を設定
    }
// setterはpublicだがgetterはprivate
var setterVisibility: String = "abc"
    private set // setterはprivateでデフォルトの実装を持つ
// アノテーション付きsetter
var setterWithAnnotation: Any? = null
    @Inject set // setterにInjectアノテーションを付与
// field識別子を使ってプロパティのバッキングフィールドにアクセス
var count = 0
    set(value) {
        if (value >= 0) field = value
    }
// バッキングフィールドが不要な場合
val isEmpty: Boolean
    get() = this.size == 0
// 公開プロパティとその背後のプライベートプロパティ
private var _dataMap: Map? = null
public val dataMap: Map
    get() {
        if (_dataMap == null) {
            _dataMap = HashMap() // 型パラメータは推論される
        }
        return _dataMap ?: throw AssertionError("別のスレッドによってnullに設定されました")
    }
// コンパイル時定数
const val SUBSYSTEM_DEPRECATED: String = "このサブシステムは非推奨です"
@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }
// 遅延初期化が必要なプロパティはlateinit修飾子でマーク
public class MyComponent {
    lateinit var component: TestComponent
    @SetUp fun setup() {
        component = TestComponent()
    }
    @Test fun test() {
        component.method()
    }
}
// 遅延初期化されたプロパティが初期化されているか確認
if (foo::bar.isInitialized) {
    println(foo.bar)
}

インターフェース

// インターフェースのメソッドはデフォルト実装を持つことができる
interface IDisplay {
    fun show()
    fun display() {
      // オプションの本体
    }
}
// クラスでインターフェースのメソッドを実装
class Child : IDisplay {
    override fun show() {
        // 本体
    }
}
// クラスでインターフェースのプロパティを実装
interface IDisplay {
    val prop: Int // 抽象
    val propertyWithImplementation: String
        get() = "表示"
    fun display() {
        print(prop)
    }
}
class Child : IDisplay {
    override val prop: Int = 29
}
// インターフェースの継承
interface Identifiable {
    val id: String
}
interface Person : Identifiable {
    val name: String
    val surname: String
    override val id: String get() = "$name $surname"
}
data class Employee(
    // 'id'の実装は不要
    override val name: String,
    override val surname: String,
    val position: Position
) : Person
// 特殊な構文を使ってインターフェースの多重継承問題を解決
interface A {
    fun foo() { print("A") }
    fun bar()
}
interface B {
    fun foo() { print("B") }
    fun bar() { print("bar") }
}
class C : A {
    override fun bar() { print("bar") }
}
class D : A, B {
    override fun foo() {
        super<A>.foo()
        super<B>.foo()
    }
    override fun bar() {
        super<B>.bar()
    }
}

関数型インターフェース

関数型インターフェースは、SAMインターフェース(Single Abstract Method interfaces、単一抽象メソッドインターフェース)とも呼ばれます。

// fun修飾子付きの関数型インターフェース
fun interface Task {
   fun execute()
}
// 関数型インターフェースは内部クラスで実装することも、より簡潔なlambda式で実装することもできます
fun interface NumberChecker {
   fun check(num: Int): Boolean
}
// クラスのインスタンスを作成
val isOdd = object : NumberChecker {
   override fun check(num: Int): Boolean {
       return num % 2 != 0
   }
}
// lambda式を使ったインスタンス作成
val isOdd = NumberChecker { it % 2 != 0 }
// 伝統的なインターフェースを関数型インターフェースに改良
// 伝統的なインターフェース
interface OutputDevice {
    fun print()
}
fun OutputDevice(action: () -> Unit): OutputDevice = object : OutputDevice { override fun print() = action() }
// 関数型インターフェース
fun interface OutputDevice {
    fun print()
}
documentsStorage.addPrinter(::OutputDevice)
@Deprecated(message = "この機能は非推奨です", level = DeprecationLevel.HIDDEN)
fun OutputDevice(...) {...}
// 型エイリアスを使って関数型インターフェースと似た機能を実現
typealias NumberChecker = (num: Int) -> Boolean
val isOdd: NumberChecker = { it % 2 != 0 }
fun main() {
   println("7は奇数? - ${isOdd(7)}")
}

可視性修飾子

可視性修飾子は、他の言語のアクセス修飾子に相当します。4種類の可視性(アクセスレベル)があります。

  • private: クラス内(クラス内で宣言時)またはファイル内(パッケージ内で宣言時)で可視
  • protected: クラスとそのサブクラス内(クラス内でのみ宣言可能)で可視
  • internal: モジュール内で可視
  • public: どこからでも可視

ローカル変数、クラス、関数には可視性はありません。モジュールとは、物理的なモジュール(IntelliJ IDEAのモジュール、MavenやGradleのプロジェクトなど)を指します。

// パッケージ内でトップレベルの関数、プロパティ、クラス、オブジェクトを宣言
// ファイル名: example.kt
package foo
fun baz() { ... }
class Bar { ... }
// パッケージ内での宣言
// ファイル名: example.kt
package foo
private fun foo() {} // example.kt内でのみ可視
public var bar: Int = 5 // プロパティはどこからでも可視
    private set         // setterはexample.kt内でのみ可視
internal val baz = 6    // 同じモジュール内でのみ可視
// クラス内での宣言
open class Container {
    private val a = 1
    protected open val b = 2
    internal val c = 3
    val d = 4  // デフォルトはpublic
    protected class Nested {
        public val e: Int = 5
    }
}
class SubContainer : Container() {
    // aは可視不可
    // b, c, dは可視可能
    // Nestedとeは可視可能
    override val b = 5   // 'b'はprotected
}
class Unrelated(c: Container) {
    // c.a, c.bは可視不可
    // c.cとc.dは可視可能(同じモジュール内)
    // Container.Nestedは可視不可、Nested::eも可視不可
}
// プライマリコンストラクタの可視性
class C private constructor(a: Int) { ... }

拡張

拡張には、拡張関数と拡張プロパティがあります。拡張関数はグローバルに定義することも、特定のクラスやコンパニオンオブジェクト内に定義することもできます。

// 拡張関数(extension functions)
// 関数名の前にレシーバータイプを付与
// 内部ではthisが呼び出し元のオブジェクトを指す
fun MutableList<String>.exchange(index1: Int, index2: Int) {
    val tmp = this[index1] // 'this'はリストを指す
    this[index1] = this[index2]
    this[index2] = tmp
}
val list = mutableListOf("one", "two", "three")
list.exchange(0, 2) // 'exchange()'内の'this'は'list'の値を保持
// ジェネリック拡張関数
fun <T> MutableList<T>.exchange(index1: Int, index2: Int) {
    val tmp = this[index1] // 'this'はリストを指す
    this[index1] = this[index2]
    this[index2] = tmp
}
// 静的ディスパッチ
open class Form
class Square: Form()
fun Form.getName() = "図形"
fun Square.getName() = "正方形"
fun printClassName(s: Form) {
    println(s.getName())
}
printClassName(Square()) // "図形"を出力
// 同名の場合、メンバ関数が優先
class Example {
    fun printFunctionType() { println("クラスメソッド") }
}
fun Example.printFunctionType() { println("拡張関数") }
Example().printFunctionType() // "クラスメソッド"を出力
// メンバ関数をオーバーロード可能
class Example {
    fun printFunctionType() { println("クラスメソッド") }
}
fun Example.printFunctionType(i: Int) { println("拡張関数 #$i") }
Example().printFunctionType(1) //  "拡張関数 #1"を出力
// null許容型への拡張
fun Any?.toString(): String {
    if (this == null) return "null"
    // nullチェック後、'this'は非null型にキャストされるため、以下のtoString()はAnyクラスのメンバ関数に解決
    return toString()
}
// 拡張プロパティ(extension properties)
val <T> List<T>.lastIndex: Int
    get() = size - 1
// 拡張プロパティにはデフォルト値を設定できない
val House.number = 1 // エラー: 拡張プロパティの初期化子は許可されない
// 拡張コンパニオンオブジェクト(companion object)
class MyClass {
    companion object { }  // "Companion"と呼ばれる
}
fun MyClass.Companion.printCompanion() { println("コンパニオン") }
fun main() {
    MyClass.printCompanion() //  "コンパニオン"を出力
}
// 拡張とパッケージの関係
// 定義側
package org.example.declarations
fun List<String>.getShortestString() { /*...*/}
// 使用側
package org.example.usage
import org.example.declarations.getShortestString
fun main() {
    val list = listOf("red", "green", "blue")
    list.getShortestString()
}
// あるクラスで別のクラスの拡張関数を定義できる
// この場合、前者はディスパッチレシーバー、後者は拡張レシーバーと呼ばれる
class Server(val hostname: String) {
    fun printHostname() { print(hostname) }
}
class Session(val server: Server, val port: Int) {
    fun printPort() { print(port) }
    fun Server.printConnectionString() {
        printHostname()   // Server.printHostname()を呼び出す
        print(":")
        printPort()   // Session.printPort()を呼び出す
    }
    fun connect() {
        /*...*/
        server.printConnectionString()   // 拡張関数を呼び出す
    }
}
fun main() {
    Session(Server("kotl.in"), 443).connect() // "kotl.in:443"を出力
    //Server("kotl.in").printConnectionString()  // エラー、この拡張関数はSessionの外では使用不可
}
// 拡張レシーバーはディスパッチレシーバーより優先
class Session {
    fun Server.getConnectionString() {
        toString()         // Server.toString()を呼び出す
        this@Session.toString()  // Session.toString()を呼び出す
    }
}
// ディスパッチレシーバーはポリモーフィズムを持つが、拡張レシーバーは持たない
open class Base { }
class Derived : Base() { }
open class BaseCaller {
    open fun Base.printFunctionInfo() {
        println("BaseCaller内のBase拡張関数")
    }
    open fun Derived.printFunctionInfo() {
        println("BaseCaller内のDerived拡張関数")
    }
    fun call(b: Base) {
        b.printFunctionInfo()   // 拡張関数を呼び出す
    }
}
class DerivedCaller: BaseCaller() {
    override fun Base.printFunctionInfo() {
        println("DerivedCaller内のBase拡張関数")
    }
    override fun Derived.printFunctionInfo() {
        println("DerivedCaller内のDerived拡張関数")
    }
}
fun main() {
    BaseCaller().call(Base())   // "BaseCaller内のBase拡張関数"
    DerivedCaller().call(Base())  // "DerivedCaller内のBase拡張関数" - ディスパッチレシーバーは仮想解決
    DerivedCaller().call(Derived())  // "DerivedCaller内のBase拡張関数" - 拡張レシーバーは静的解決
}
/*
BaseCaller内のBase拡張関数
DerivedCaller内のBase拡張関数
DerivedCaller内のBase拡張関数
*/

タグ: Kotlin オブジェクト指向 クラス 継承 プロパティ

7月8日 23:57 投稿