Kotlin: Kotlinの継承とインターフェース
最終更新:2026-08-26
Kotlinの継承モデルはJavaよりも安全です。クラスはデフォルトでfinalであり、メソッドはデフォルトでオーバーライド不可となっています。Charlieは継承を許可するためにopenを明示的に宣言する必要があり、コンパイラがポリモーフィズムの安全性を確保します。
1. 学習内容
open/abstract: クラスはデフォルトで final であり、明示的に宣言する必要があります- インターフェースのデフォルト実装
override必須の注釈- 多重継承の競合解決:
super<ParentA> - Charlieの実践:BulkOrder/PriorityOrder の継承階層
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. 継承階層図
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() を使用します。📖 まとめ
- Kotlinのクラスはデフォルトでfinalです。継承するには
openである必要があります。— デフォルトではクローズドですが、明示的にオープンにすることができます。 abstractクラスはインスタンス化できません。抽象メソッドはサブクラスで実装する必要があります。- インターフェースは、デフォルトの実装および抽象プロパティをサポートしており、複数の実装が許可されています
override必須の注釈により、意図しない上書きが防止される — コンパイラがポリモーフィズムの安全性を確保する- 多重継承による競合は、明示的な
super<Interface>の選択によって解決される - 動作の契約(ステートレス)にはインターフェースを、共有状態と動作には抽象クラスを使用する
📝 練習問題
- 初心者 (⭐):
Orderと、それを継承し、summaryメソッドをオーバーライドするBulkOrderを定義してください。ヒント:open class Order、class BulkOrder : Order() - 中級 (⭐⭐):
DiscountableおよびTrackableインターフェースを作成し、PriorityOrderにその両方を実装させて、名前の競合を解決してください。ヒント:super<Discountable> - 課題 (⭐⭐⭐): 完全な注文継承階層を設計してください:
BaseOrder→BulkOrder/PriorityOrder。それぞれが異なる割引および配送戦略を実装するようにしてください。ヒント:インターフェースと抽象クラスを組み合わせてください。