Kotlin: Kotlinの継承とインターフェース

最終更新:2026-08-26

Kotlinの継承モデルはJavaよりも安全です。クラスはデフォルトでfinalであり、メソッドはデフォルトでオーバーライド不可となっています。Charlieは継承を許可するためにopenを明示的に宣言する必要があり、コンパイラがポリモーフィズムの安全性を確保します。

1. 学習内容


2. ある建築家の実話

(1) 課題:誤った上書きによる本番環境でのインシデント

BobはOrderクラスを継承してSpecialOrderを作成した際、誤ってcalculateTotalメソッドをオーバーライドしてしまいました。その結果、500件の特別注文で数量に誤りが生じ、財務チームが緊急警告を発しました。

(2) Kotlinのコンパイル時ガード

KOTLIN
// Java: override annotation is optional - silent override possible
class SpecialOrder extends Order {
    double calculateTotal() { ... }  // Silently overrides!
}

// Kotlin: override is MANDATORY - compiler catches mistakes
open class Order {
    open fun calculateTotal(): Double = ...
}
class SpecialOrder : Order() {
    override fun calculateTotal(): Double = ...  // Must declare override
}

override の必須指定とデフォルトでfinalのクラスにより、コンパイラが継承の安全性を確保します。これでBobが誤ってオーバーライドすることはありません。


3. オープン継承モデル

(1) デフォルトではクラスが最終要素となる

KOTLIN
// Default: class is final (cannot be inherited)
class Order(val id: String)  // Cannot be extended

// Open: explicitly allow inheritance
open class Order(val id: String) {
    // Methods are also final by default
    fun process() { }  // Cannot be overridden

    // Open method: explicitly allow override
    open fun calculateTotal(): Double = 0.0
}

// Abstract: must be inherited, cannot be instantiated
abstract class BaseOrder(val id: String) {
    // Abstract method: must be implemented by subclass
    abstract fun calculateTotal(): Double

    // Concrete method in abstract class
    fun getId(): String = id
}

(2) 継承修飾子の階層

修飾子 クラス メソッド インスタンス化可能 継承可能 オーバーライド可能
デフォルト(最終)
open
abstract ✅ (必須) ✅ (必須)

(3) JavaとKotlinの継承の比較

次元 Java Kotlin
クラスのデフォルト 継承可能 final(継承不可)
メソッドのデフォルト オーバーライド可能 final(オーバーライド不可)
アノテーションのオーバーライド @Override (任意) override (必須)
オープン継承 宣言不要 open 明示的な宣言
設計理念 「デフォルトでオープン」 「デフォルトでクローズ」

4. インターフェースとデフォルトの実装

(1) インターフェースの定義

KOTLIN
interface Discountable {
    // Abstract method
    fun discountRate(): Double

    // Default implementation
    fun applyDiscount(total: Double): Double {
        return total * (1.0 - discountRate())
    }
}

interface Trackable {
    val trackingCode: String?  // Abstract property

    fun track(): String = trackingCode?.let { "Tracking: $it" } ?: "Not shipped"
}

// Implement multiple interfaces
class PremiumOrder(
    val id: String,
    val total: Double,
    override val trackingCode: String?
) : Discountable, Trackable {
    override fun discountRate() = 0.15  // 15% discount
}

(2) インターフェースと抽象クラス

次元 インターフェース 抽象クラス
コンストラクタ なし あり
状態 なし(抽象的なプロパティのみ) あり(具体的なプロパティ)
多重継承 サポートされている(複数の実装) サポートされていない(単一継承)
デフォルトの実装 Kotlin 対応 対応
ユースケース 動作契約 共有状態 + 動作

5. 必須アノテーションのオーバーライド

(1) 基本的な使い方

KOTLIN
open class Order(val id: String, var status: String) {
    open fun summary(): String = "Order $id: $status"
}

class BulkOrder(id: String, status: String, val minQuantity: Int) : Order(id, status) {
    // MUST use override keyword
    override fun summary(): String = "Bulk $id: $status (min: $minQuantity)"
}

(2) さらなる上書きの防止

KOTLIN
open class Order {
    open fun process() { }
}

class SpecialOrder : Order() {
    // Prevent further override in subclasses
    final override fun process() { }
}

6. 多重継承における競合の解決

(1) ダイヤモンドの相続問題

KOTLIN
interface A {
    fun hello() = "A"
}

interface B {
    fun hello() = "B"
}

// Diamond: C inherits hello() from both A and B
class C : A, B {
    // MUST override to resolve conflict
    override fun hello(): String {
        // Explicitly choose which parent's implementation
        return super<A>.hello()  // Choose A's implementation
    }
}

(2) 紛争解決の戦略

KOTLIN
class D : A, B {
    override fun hello(): String {
        // Strategy 1: Pick one parent
        return super<B>.hello()

        // Strategy 2: Combine both
        // return "${super<A>.hello()} + ${super<B>.hello()}"

        // Strategy 3: Custom implementation
        // return "D"
    }
}

(3) 紛争解決手法の比較

戦略 構文 ユースケース
親Aを選択 super<A>.hello() Aの実装の方が適切である
親Bを選択 super<B>.hello() Bの実装の方が適切である
両方を組み合わせる super<A> + super<B> 両方の挙動が必要
独自の実装 独自コード どちらも適切ではない

7. 継承階層図

100%
classDiagram
    class Order {
        +val id: String
        +var status: String
        +open fun summary(): String
        +open fun calculateTotal(): Double
    }
    class BulkOrder {
        +val minQuantity: Int
        +override fun calculateTotal(): Double
    }
    class PriorityOrder {
        +val priority: String
        +override fun summary(): String
    }
    class Discountable {
        <<interface>>
        +fun discountRate(): Double
        +fun applyDiscount(total: Double): Double
    }
    class Trackable {
        <<interface>>
        +val trackingCode: String?
        +fun track(): String
    }
    Order <|-- BulkOrder
    Order <|-- PriorityOrder
    BulkOrder ..|> Discountable
    PriorityOrder ..|> Trackable
    PriorityOrder ..|> Discountable

8. 完全な例:OrderProcessor の継承階層

▶ サンプル:OrderProcessorの継承階層

KOTLIN
// ============================================
// OrderProcessor - Inheritance Hierarchy
// Feature: Order / BulkOrder / PriorityOrder
// ============================================

interface Discountable {
    fun discountRate(): Double
    fun applyDiscount(total: Double): Double = total * (1.0 - discountRate())
}

interface Trackable {
    val trackingCode: String?
    fun track(): String = trackingCode?.let { "Tracking: $it" } ?: "Not shipped yet"
}

open class Order(val id: String, var status: String, val total: Double) {
    init {
        require(total >= 0) { "Total must be non-negative" }
    }

    open fun summary(): String = "Order $id | \$$total USD | $status"
    open fun calculateFinal(): Double = total
}

class BulkOrder(
    id: String,
    status: String,
    total: Double,
    val minQuantity: Int
) : Order(id, status, total), Discountable {

    override fun discountRate() = 0.10  // 10% bulk discount

    override fun calculateFinal(): Double = applyDiscount(total)

    override fun summary(): String =
        "${super.summary()} | Bulk (min: $minQuantity) | Final: \$${calculateFinal()} USD"
}

class PriorityOrder(
    id: String,
    status: String,
    total: Double,
    val priority: String,
    override val trackingCode: String?
) : Order(id, status, total), Discountable, Trackable {

    override fun discountRate() = when (priority) {
        "VIP" -> 0.20
        "GOLD" -> 0.15
        else -> 0.05
    }

    override fun calculateFinal(): Double = applyDiscount(total)

    override fun summary(): String =
        "${super.summary()} | Priority: $priority | ${track()} | Final: \$${calculateFinal()} USD"
}

fun main() {
    val orders = listOf(
        Order("ORD-001", "CONFIRMED", 299.99),
        BulkOrder("ORD-002", "CONFIRMED", 5_000.00, 100),
        PriorityOrder("ORD-003", "SHIPPED", 15_000.00, "VIP", "TRK-XYZ789"),
        PriorityOrder("ORD-004", "PENDING", 2_500.00, "GOLD", null)
    )

    println("=== Order Summary ===")
    orders.forEach { println(it.summary()) }

    val totalSavings = orders
        .filterIsInstance<Discountable>()
        .sumOf { it.applyDiscount((it as Order).total) - (it as Order).calculateFinal() }

    println("\nTotal savings from discounts: \$$totalSavings USD")
}

出力:

TEXT 📖 参照専用
=== Order Summary ===
Order ORD-001 | $299.99 USD | CONFIRMED
Order ORD-002 | $5000.0 USD | CONFIRMED | Bulk (min: 100) | Final: $4500.0 USD
Order ORD-003 | $15000.0 USD | SHIPPED | Priority: VIP | Tracking: TRK-XYZ789 | Final: $12000.0 USD
Order ORD-004 | $2500.0 USD | PENDING | Priority: GOLD | Not shipped yet | Final: $2125.0 USD

Total savings from discounts: $3475.0 USD

❓ よくある質問

Q なぜKotlinのクラスはデフォルトでfinalなのでしょうか?
A 『Effective Java』では、「継承を想定して設計・文書化するか、あるいは継承を禁止する」ことが推奨されています。Kotlinはこの助言を採用しており、デフォルトでは継承を禁止していますが、openを使用することで明示的に継承を許可することができます。
Q インターフェースにコンストラクタは設定できますか?
A いいえ。インターフェースにはコンストラクタがなく、状態を保持することはできません。共有状態には抽象クラスを使用してください。
Q クラスは、デフォルトメソッドを持つ複数のインターフェースを実装する際、競合を解決できますか?
A はい。2つのインターフェースに同じ名前とシグネチャを持つデフォルトメソッドがある場合、サブクラスはそのメソッドをオーバーライドし、super<InterfaceName> を使用して実装を選択する必要があります。
Q override は省略できますか?
A いいえ。Kotlin では override キーワードの使用が必須となっており、これを省略するとコンパイルエラーが発生します。これは、意図しないオーバーライドを防ぐための重要な安全対策です。
Q 抽象クラスのメソッドには open が必要ですか?
A いいえ。抽象クラス内の抽象メソッドは暗黙的にオーバーライド可能です(オーバーライドする必要があります)。非抽象のオープンメソッドには、引き続き open 修飾子が必要です。
Q Kotlinでは、Javaと同様にsuperを呼び出すことはできますか?
A はい。 サブクラスのコンストラクタでは super(...) を使用して親クラスのコンストラクタを呼び出し、メソッドでは super.foo() を使用して親クラスの実装を呼び出し、インターフェースでは super<Interface>.foo() を使用します。

📖 まとめ


📝 練習問題

  1. 初心者 (⭐): Order と、それを継承し、summary メソッドをオーバーライドする BulkOrder を定義してください。ヒント: open class Orderclass BulkOrder : Order()
  2. 中級 (⭐⭐): Discountable および Trackable インターフェースを作成し、PriorityOrder にその両方を実装させて、名前の競合を解決してください。ヒント: super<Discountable>
  3. 課題 (⭐⭐⭐): 完全な注文継承階層を設計してください:BaseOrderBulkOrder / PriorityOrder。それぞれが異なる割引および配送戦略を実装するようにしてください。ヒント:インターフェースと抽象クラスを組み合わせてください。

← 前へ | 次へ →

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%