Kotlin: Kotlinのクラスとオブジェクト

最終更新:2026-08-26

Kotlinでは、プライマリコンストラクタとプロパティを1行にまとめ、Javaのコンストラクタ本体をinitブロックで置き換えられます。Charlieは、Javaなら30行になるOrderクラスを、より高機能な3行のコードで定義しています。

1. 学習内容


2. ある建築家の実話

(1) 課題:Javaのコンストラクタ地獄

Javaでは、CharlieのOrderクラスには3つのコンストラクタ(引数なし、必須フィールド、全フィールド)に加え、ゲッター・セッター・検証ロジックが必要でした。たった1つのドメインクラスで80行以上ものコードが必要だったのです。

(2) Kotlinにおけるプライマリコンストラクタの解決策

KOTLIN
// Java: 80+ lines for one domain class
// Kotlin: 3 lines with validation
class Order(val id: String, var status: String, val total: BigDecimal) {
    init { require(total >= BigDecimal.ZERO) { "Total must be non-negative" } }
}

プライマリコンストラクタ+プロパティ+初期化ブロックを合わせたもの — Java 80行 → Kotlin 3行。


3. プライマリコンストラクタとプロパティ

(1) 1行で記述するプライマリコンストラクタ

KOTLIN
// Primary constructor with properties in one line
class Order(val id: String, var status: String, val total: Double)

// Equivalent Java would be 20+ lines

(2) コンストラクタのパラメータとプロパティの比較

KOTLIN
// val/var in constructor = property
class Order(val id: String,    // read-only property
            var status: String, // mutable property
            total: Double)      // just constructor param, not a property

// Usage
val order = Order("ORD-001", "PENDING", 299.99)
println(order.id)      // OK - val property
order.status = "PAID"  // OK - var property
// order.total         // ERROR - not a property

(3) プライマリコンストラクタとセカンダリコンストラクタ

KOTLIN
class Order(val id: String, var status: String, val total: Double) {
    // Secondary constructor MUST delegate to primary
    constructor(id: String) : this(id, "PENDING", 0.0)

    // Another secondary constructor
    constructor(id: String, total: Double) : this(id, "PENDING", total)
}

val order1 = Order("ORD-001")               // Secondary
val order2 = Order("ORD-002", 299.99)        // Secondary
val order3 = Order("ORD-003", "CONFIRMED", 1_500.00)  // Primary

(4) コンストラクタの比較

次元 Java Kotlin
プロパティとコンストラクタの定義 別々 1行にまとめたプライマリコンストラクタ
複数のコンストラクタ 独立した定義 二次的なものは一次的なものに委譲しなければならない
パラメータからプロパティへ 手動での割り当て val/var 自動
デフォルト値 メソッドのオーバーロード デフォルトパラメータ

4. initブロック

init ブロックは、プライマリコンストラクタの直後に実行され、検証および初期化ロジックに使用されます。

(1) 基本的な使い方

KOTLIN
class Order(val id: String, var status: String, val total: Double) {
    init {
        require(total >= 0.0) { "Total must be non-negative, got $total" }
        require(id.startsWith("ORD-")) { "Order ID must start with ORD-" }
    }

    // Multiple init blocks execute in order
    init {
        println("Order $id created with total \$$total USD")
    }
}

(2) initブロックの実行順序

KOTLIN
class Example {
    val a = println("1: property initialization")

    init {
        println("2: first init block")
    }

    val b = println("3: property initialization")

    init {
        println("4: second init block")
    }
}
// Output: 1, 2, 3, 4 (declaration order)

5. 可視性修飾子

(1) 4つの可視性レベル

KOTLIN
class OrderProcessor {
    // public (default): visible everywhere
    fun process(order: Order) { ... }

    // private: visible inside this class only
    private fun validate(order: Order) { ... }

    // protected: visible in this class and subclasses
    protected fun calculateTax(order: Order) { ... }

    // internal: visible within the same module
    internal fun report() { ... }
}

(2) 可視性の比較表

修飾子 クラス内部 サブクラス 同一モジュール グローバル
public
internal
protected
private

(3) JavaとKotlinの可視性の違い

次元 Java Kotlin
デフォルトの可視性 パッケージプライベート public
モジュールの表示設定 なし internal
パッケージの可視性 パッケージ-private なし(代わりに internal を使用してください)
最上位の宣言 public のみ public / internal / private

6. 遅延初期化

(1) lateinit 変数

KOTLIN
class OrderService {
    // lateinit: promise to initialize before use
    lateinit var repository: OrderRepository

    fun init(repo: OrderRepository) {
        repository = repo
    }

    fun process(order: Order) {
        // Access before init throws UninitializedPropertyAccessException
        repository.save(order)
    }
}

(2) 投稿者:lazy

KOTLIN
class OrderProcessor {
    // lazy: thread-safe, initialized on first access
    val cache: OrderCache by lazy {
        println("Initializing cache...")
        OrderCache(maxSize = 10_000)
    }

    // lazy with custom lock mode
    val heavyResource by lazy(LazyThreadSafetyMode.PUBLICATION) {
        loadHeavyResource()
    }
}

(3) lateinit と by lazy の比較

寸法 lateinit by lazy
タイプ var (変更可能) val (読み取り専用)
初期化のタイミング 手動による割り当て 最初のアクセス時
スレッドセーフ いいえ はい(デフォルト)
null許容性 null不可と宣言 null不可と宣言
初期化前のアクセス 実行時例外 発生しない
ユースケース DIフレームワークによる注入 計算コストの高い計算プロパティ

7. クラス関係図

100%
classDiagram
    class Order {
        +val id: String
        +var status: String
        +val total: Double
        +val items: List~OrderItem~
        +fun addItem(item: OrderItem)
    }
    class OrderItem {
        +val sku: String
        +val quantity: Int
        +val unitPrice: Double
        +fun subtotal: Double
    }
    class Customer {
        +val id: String
        +val name: String
        +val email: String?
        +val address: Address?
    }
    class Address {
        +val street: String
        +val city: String
        +val country: String
    }
    Order --> OrderItem : contains
    Order --> Customer : belongs to
    Customer --> Address : has

8. 完全な例:OrderProcessor ドメインモデル

▶ サンプル:OrderProcessorドメインモデル

KOTLIN
// ============================================
// OrderProcessor - Domain Model
// Feature: Order, OrderItem, Customer with init validation
// ============================================

import java.math.BigDecimal
import java.math.RoundingMode

class Address(val street: String, val city: String, val country: String) {
    override fun toString(): String = "$street, $city, $country"
}

class Customer(val id: String, val name: String, val email: String?) {
    var address: Address? = null

    fun getDisplayEmail(): String = email ?: "no-email"

    override fun toString(): String = "Customer($id, $name, ${getDisplayEmail()})"
}

class OrderItem(val sku: String, val quantity: Int, val unitPrice: BigDecimal) {
    init {
        require(quantity > 0) { "Quantity must be positive, got $quantity" }
        require(unitPrice >= BigDecimal.ZERO) { "Price must be non-negative" }
    }

    val subtotal: BigDecimal
        get() = unitPrice.multiply(BigDecimal(quantity)).setScale(2, RoundingMode.HALF_UP)
}

class Order(
    val id: String,
    var status: String,
    private val _items: MutableList<OrderItem> = mutableListOf()
) {
    init {
        require(id.startsWith("ORD-")) { "Order ID must start with ORD-" }
    }

    val items: List<OrderItem> get() = _items.toList()

    val total: BigDecimal
        get() = _items.fold(BigDecimal.ZERO) { acc, item -> acc.add(item.subtotal) }

    fun addItem(item: OrderItem) {
        _items.add(item)
    }

    val itemCount: Int get() = _items.size

    // Lazy computed tax
    val tax by lazy {
        total.multiply(BigDecimal("0.08")).setScale(2, RoundingMode.HALF_UP)
    }

    override fun toString(): String = "Order($id, $status, ${itemCount} items, \$$total USD)"
}

fun main() {
    val customer = Customer("CUST-001", "Alice", "alice@example.com").also {
        it.address = Address("123 Main St", "New York", "US")
    }
    println(customer)
    println("Address: ${customer.address}")

    val order = Order("ORD-001", "PENDING")
    order.addItem(OrderItem("SKU-WIDGET", 3, BigDecimal("9.99")))
    order.addItem(OrderItem("SKU-GADGET", 1, BigDecimal("149.99")))
    order.addItem(OrderItem("SKU-DOOHICKEY", 5, BigDecimal("4.50")))

    println(order)
    println("Subtotal: \$$${order.total} USD")
    println("Tax: \$$${order.tax} USD")
    println("Grand Total: \$$${order.total.add(order.tax)} USD")

    order.status = "CONFIRMED"
    println("Status updated: ${order.status}")
}

出力:

TEXT 📖 参照専用
Customer(CUST-001, Alice, alice@example.com)
Address: 123 Main St, New York, US
Order(ORD-001, PENDING, 3 items, $199.42 USD)
Subtotal: $$199.42 USD
Tax: $$15.95 USD
Grand Total: $$215.37 USD
Status updated: CONFIRMED

❓ よくある質問

Q プライマリコンストラクタとセカンダリコンストラクタは共存できますか?
A はい。ただし、セカンダリコンストラクタは this(...) を使用してプライマリコンストラクタに委譲する必要があります。セカンダリコンストラクタよりもデフォルトパラメータの使用が推奨されます。
Q initブロックを複数設定できますか?
A はい、複数のinitブロックは宣言順に実行されます。混乱を避けるため、1つにまとめることをお勧めします。
Q lateinit はプリミティブ型でも使用できますか?
A いいえ。プリミティブ型にはデフォルト値があるため、lateinit は非プリミティブ型(オブジェクト型)でのみ機能します。Int や Double などについては、by lazy またはnull許容型とデフォルト値を使用してください。
Q 遅延初期化はスレッドセーフですか?
A デフォルトではスレッドセーフです(SYNCHRONIZEDモード)。シングルスレッドでのアクセスが確実な場合は、パフォーマンス向上のためにLazyThreadSafetyMode.NONEを使用してください。
Q Maven/Gradleのマルチモジュールプロジェクトにおいて、内部可視性はどのように機能しますか?
A 「internal」は、同じGradleモジュールまたはMavenモジュール内でのみ可視性を制限します。異なるモジュールは、たとえ同じプロジェクト内にあっても、内部メンバーにアクセスすることはできません。
Q なぜKotlinにはJavaの「パッケージプライベート」がないのですか?
A Kotlinでは、パッケージプライベートの代わりにinternal(モジュールレベルの可視性)を採用しています。Javaではパッケージレベルの可視性が誤用されることが多いため、モジュールレベルの方が現代的なプロジェクト構造に適しているからです。

📖 まとめ


📝 練習問題

  1. 初心者 (⭐): Product クラス (id: String, name: String, price: Double) を定義し、price >= 0 を検証する初期化ブロックを設定してください。ヒント: require(price >= 0)
  2. 中級 (⭐⭐): lateinit を使用して Repository を注入し、by lazy を使用してキャッシュを初期化する Service クラスを設計してください。 ヒント:lateinit + by lazy
  3. 課題 (⭐⭐⭐): Order/OrderItem/Customer を含む完全な Order ドメインモデルを実装し、すべてのバリデーションを init ブロック内で行い、アイテムに対してディフェンシブコピーを適用してください。ヒント:第 8 節の完全な例を参照してください。

← 前へ | 次へ →

Web-Tutorial.com

Web-Tutorial 技術チーム

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

100%