【Kotlin】レッスン5-05:アクセス修飾子とカプセル化を理解しよう

記事内に商品プロモーションを含む場合があります

この記事で学べる知識:アクセス修飾子とカプセル化

この記事の練習問題を解くために必要な知識:
基礎文法、制御構造、関数、コレクション(レッスン1~4)クラスの定義と使用プライマリコンストラクタセカンダリコンストラクタアクセス修飾子とカプセル化クラスメンバとインスタンスメンバクラスの継承メソッドのオーバーライドクラスの拡張抽象クラスインターフェースデータクラス

<<前のページ Kotlin記事一覧 次のページ>>

Kotlinの「アクセス修飾子とカプセル化」とは

この章ではKotlinにおける「アクセス修飾子とカプセル化」の意味や使い方を学習します。用語の解説が不要な方はここをクリックして練習問題へ飛びましょう。




プログラムを設計する際、クラスやそのプロパティやメソッドの公開範囲を制御することは非常に重要です。

Kotlinでは「アクセス修飾子」を使ってアクセス制御を行い、さらに「カプセル化」を利用することでデータの安全性を保つことができます。

これらの概念を理解することで、より安全でメンテナンス性の高いコードを書くことができるようになります。

アクセス修飾子とは?

アクセス修飾子はクラスやプロパティ、メソッドに対するアクセス範囲を制御するためのキーワードです。

Kotlinには以下の4つのアクセス修飾子があります。

  • public(デフォルト)
    他のどのクラスからもアクセス可能です。何も指定しない場合、デフォルトでpublicになります。
  • private
    同じクラス内でのみアクセス可能です。他のクラスからは見えません。
  • protected
    同じクラス、またはそのサブクラス(継承したクラス)内でのみアクセス可能です。
  • internal
    同じモジュール内でのみアクセス可能です。モジュールとは同じビルド単位(例えば1つのGradleプロジェクト)を指します。
class Person {
    public var name: String = "John"  // デフォルトでpublic
    private var age: Int = 30         // クラス内からのみアクセス可能
    protected var address: String = "Unknown"     // サブクラスからもアクセス可能
    internal var phoneNumber: String = "123-4567" // 同じモジュール内でアクセス可能
}

カプセル化とは?

カプセル化とは、クラスのプロパティやメソッドを適切に隠蔽し、外部から直接アクセスさせず、必要に応じて間接的に操作する仕組みです。

これによりデータの不正な変更を防ぎ、クラスの内部構造を保護することができます。

例えばprivateなプロパティは外部から直接アクセスすることができません。以下のコードではその仕組みを示します。

class Person {
    private var age: Int = 30  // 非公開プロパティ
}

fun main() {
    val person = Person()
    // エラー: 'age' has private access in 'Person'
    println(person.age) // 直接アクセスしようとするとコンパイルエラー
}

この例ではageプロパティがprivateで定義されているため、main関数から直接アクセスしようとするとコンパイルエラーになります。

このようにカプセル化によってデータの不正な操作を防ぐことができます。

ゲッターとセッターとは?

ゲッター(getter)とセッター(setter)は、クラスのプロパティに外部からアクセスするための特別なメソッドです。

ゲッターはプロパティの値を取得するために使用され、セッターはプロパティの値を設定または変更するために使用されます。

Kotlinではゲッターやセッターが内部で自動的に生成されています。また必要に応じてこれらをカスタマイズして使用することもできます。

これによりプロパティへのアクセスを制御し、データの安全性や一貫性を保つことが可能になります。

基本的な例(自動生成されるゲッターとセッター):

class Person {
    var name: String = "John" // ゲッターとセッターが自動生成される
}

fun main() {
    val person = Person()
    println(person.name) // 自動生成されたゲッターが呼び出される
    person.name = "Alice" // 自動生成されたセッターが呼び出される
    println(person.name) // Alice
}

ゲッターとセッターの役割

Kotlinではプロパティのゲッターとセッターをデフォルトで自動生成しますが、カスタマイズすることもできます。

ゲッターとセッターを活用すると、次のような役割を果たします:

  1. プロパティへのアクセスを制御する。
  2. プロパティの値を設定する際に条件を設ける。
  3. プロパティの読み取りや書き込み時に追加の処理を行う。

例1:カスタムゲッターとセッター

class Person {
    private var _age: Int = 0  // 非公開のプロパティ

    var age: Int               // 外部からアクセス可能なプロパティ
        get() = _age           // ゲッター(プロパティの値を取得する)
        set(value) {           // セッター(プロパティの値を設定する)
            if (value >= 0) {  // 年齢は0以上である必要がある
                _age = value
            } else {
                println("Invalid age")
            }
        }
}

この例ではゲッターが_ageの値を返し、セッターが新しい値を設定する際に条件をチェックしています。

アクセス修飾子とカプセル化の使用例

以下はアクセス修飾子とカプセル化を利用したクラス設計の例です。

class BankAccount {
    private var balance: Double = 0.0  // 非公開プロパティ

    // 残高を取得するゲッター
    fun getBalance(): Double {
        return balance
    }

    // 残高を更新するメソッド
    fun deposit(amount: Double) {
        if (amount > 0) {
            balance += amount
        } else {
            println("Invalid deposit amount")
        }
    }

    fun withdraw(amount: Double) {
        if (amount > 0 && amount <= balance) {
            balance -= amount
        } else {
            println("Invalid withdraw amount")
        }
    }
}

fun main() {
    val account = BankAccount()
    account.deposit(100.0) // 100を預金
    println(account.getBalance()) // 残高を表示: 100.0
    account.withdraw(30.0) // 30を引き出し
    println(account.getBalance()) // 残高を表示: 70.0
}

まとめ

アクセス修飾子を正しく利用することで、クラスやプロパティのアクセス範囲を制御し、カプセル化によって安全性と保守性を向上させることができます。

この仕組みを活用することで、プログラムの品質を大きく向上させることが可能です。

アクセス修飾子とカプセル化の練習問題

銀行口座を管理するプログラムを作成しましょう。

このプログラムでは「残高」をプライベートプロパティとしてカプセル化し、安全に管理します。

残高を確認したり、入金や引き出しを行う機能を実装してください。

また不正な操作(負の金額の入金や残高を超える引き出し)に対しては適切なエラーメッセージを表示するようにします。

この問題の要件

以下の要件に従ってコードを完成させてください。

  1. クラスの作成
    クラス名はBankAccountとすること。
  2. プライベートプロパティ
    プロパティbalanceprivateとして定義し、初期値を設定すること。
  3. コンストラクタ
    クラスを初期化する際、残高の初期値を設定できるようにすること。
  4. メソッドの実装
    • getBalance()
      現在の残高を返すメソッドを作成すること。
    • deposit(amount: Double)
      入金額amountを受け取り、残高に加算するメソッドを作成すること。ただし、負の金額の場合はエラーメッセージを表示すること。
    • withdraw(amount: Double)
      引き出し額amountを受け取り、残高から減算するメソッドを作成すること。ただし、残高が足りない場合や負の金額の場合はエラーメッセージを表示すること。
  5. メイン関数の作成
    • BankAccountクラスのインスタンスを作成し、初期残高を設定すること。
    • 作成したインスタンスを利用して以下の操作を行う:
      1. 現在の残高を表示する。
      2. 入金を行い、結果を表示する。
      3. 引き出しを行い、結果を表示する。
      4. 不正な入金や引き出し操作を行い、エラーメッセージを確認する。

ただし、以下のような実行結果となるコードを書くこと。

*****↓↓正解コードの実行結果の例↓↓*****

新しい銀行口座を作成しました。現在の残高: ¥1000.0
¥500.0 を入金しました。現在の残高: ¥1500.0
¥200.0 を引き出しました。現在の残高: ¥1300.0
エラー: 残高不足です。現在の残高: ¥1300.0
エラー: 入金額は正の数である必要があります。

この問題を解くヒント

1からコードを組み立てることが難しい場合は、以下のヒントを開いて参考にしましょう。

ヒント1【コードの構成を見る】

正解のコードは上から順に以下のような構成となっています。
(※下記の□はコード内のインデントを表しています)

1:クラスBankAccountの定義
  □ プライマリコンストラクタの定義(private var balance: Double)
  □ ゲッター関数getBalanceの定義
  □ □ balanceの値を返す
  □ 入金処理関数depositの定義
  □ □ if文による分岐処理(amount > 0 の場合)
  □ □ □ balanceに入金額を加算
  □ □ □ 現在の残高をprintlnで出力
  □ □ else文
  □ □ □ エラーをprintlnで出力
  □ 引き出し処理関数withdrawの定義
  □ □ if文による分岐処理(amount > 0 && amount <= balanceの場合)
  □ □ □ balanceから引き出し額を減算
  □ □ □ 現在の残高をprintlnで出力
  □ □ else if文(amount > balance の場合)
  □ □ □ エラー(残高不足)をprintlnで出力
  □ □ else文
  □ □ □ エラー(引き出し額が正の数でない)をprintlnで出力
2:main関数の定義
  □ BankAccountクラスのインスタンスmyAccountを生成(初期残高: 1000.0)
  □ 新しい口座の残高をprintlnで出力
  □ deposit関数を呼び出し(入金額: 500.0)
  □ withdraw関数を呼び出し(引き出し額: 200.0)
  □ withdraw関数を呼び出し(引き出し額: 1500.0)
  □ deposit関数を呼び出し(入金額: -100.0)

ヒント2【穴埋め問題にする】

以下のコードをコピーし、コメントに従ってコードを完成させて下さい。

// クラスBankAccountは銀行口座を表現します。このクラスを通じてアクセス修飾子とカプセル化を学びます。
class BankAccount(private var balance: Double) {
    // 残高を取得するためのゲッター関数
    fun getBalance(): Double {
        /*【穴埋め問題1】
        ここでbalanceを返すコードを書いてください。
        */
    }

    // 入金処理
    fun deposit(amount: Double) {
        /*【穴埋め問題2】
        ここに入金処理を実装してください。amountが0より大きい場合はbalanceに加算し、printlnで残高を表示します。それ以外の場合はエラーメッセージを出力します。
        */
    }

    // 引き出し処理
    fun withdraw(amount: Double) {
        /*【穴埋め問題3】
        ここに引き出し処理を実装してください。amountが0より大きくbalance以下の場合は引き出しを実行し、printlnで残高を表示します。それ以外の場合は適切なエラーメッセージを出力します。
        */
    }
}

// メイン関数でクラスを使用して実行例を示します。
fun main() {
    // 銀行口座を初期化します。
    val myAccount = BankAccount(1000.0) // 初期残高を設定
    println("新しい銀行口座を作成しました。現在の残高: ¥${myAccount.getBalance()}")

    // 入金処理
    myAccount.deposit(500.0)

    // 引き出し処理
    myAccount.withdraw(200.0)

    // 無効な引き出し例
    myAccount.withdraw(1500.0)

    // 無効な入金例
    myAccount.deposit(-100.0)
}

以上がこの問題の穴埋めコードです。

このヒントを見てもまだ回答を導き出すのが難しいと感じる場合は、先に正解のコードと解説を見て内容を理解するようにしましょう。



練習問題の解答と解説

この問題の一つの正解例とそのコードの解説を以下に示します。

正解コードの例

例えば以下のようなプログラムが考えられます。

// クラスBankAccountは銀行口座を表現します。このクラスを通じてアクセス修飾子とカプセル化を学びます。
class BankAccount(private var balance: Double) {
    // 残高を取得するためのゲッター関数
    fun getBalance(): Double {
        return balance // 現在の残高を返します。
    }

    // 入金処理
    fun deposit(amount: Double) {
        if (amount > 0) { // 入金額が正の場合のみ処理を実行
            balance += amount
            println("¥$amount を入金しました。現在の残高: ¥$balance")
        } else {
            println("エラー: 入金額は正の数である必要があります。")
        }
    }

    // 引き出し処理
    fun withdraw(amount: Double) {
        if (amount > 0 && amount <= balance) { // 正しい引き出し額の場合のみ処理を実行
            balance -= amount
            println("¥$amount を引き出しました。現在の残高: ¥$balance")
        } else if (amount > balance) {
            println("エラー: 残高不足です。現在の残高: ¥$balance")
        } else {
            println("エラー: 引き出し額は正の数である必要があります。")
        }
    }
}

// メイン関数でクラスを使用して実行例を示します。
fun main() {
    // 銀行口座を初期化します。
    val myAccount = BankAccount(1000.0) // 初期残高を設定
    println("新しい銀行口座を作成しました。現在の残高: ¥${myAccount.getBalance()}")

    // 入金処理
    myAccount.deposit(500.0)

    // 引き出し処理
    myAccount.withdraw(200.0)

    // 無効な引き出し例
    myAccount.withdraw(1500.0)

    // 無効な入金例
    myAccount.deposit(-100.0)
}

正解コードの解説

今回作成したコードは銀行口座を管理するプログラムで、「アクセス修飾子とカプセル化」を学ぶことを目的としています。

このコードをブロックごとに分解し、それぞれの部分を初心者向けに解説します。

クラスの定義とプロパティのカプセル化

class BankAccount(private var balance: Double) {
  • class BankAccount
    クラスBankAccountは銀行口座を表現します。クラスはオブジェクト指向プログラミングの基本単位で、関連するデータと処理をまとめたものです。
  • private var balance: Double
    balanceは残高を表すプロパティでprivateアクセス修飾子によってカプセル化されています。
    これにより外部のコードから直接balanceにアクセスすることができません。

ゲッター関数:残高の取得

fun getBalance(): Double {
    return balance
}

fun getBalance()この関数は現在の残高を取得するためのものです。
balanceprivateであるため外部から直接アクセスできない代わりに、この関数を通じて残高を取得します。

入金処理

fun deposit(amount: Double) {
    if (amount > 0) {
        balance += amount
        println("¥$amount を入金しました。現在の残高: ¥$balance")
    } else {
        println("エラー: 入金額は正の数である必要があります。")
    }
}

この関数は指定された金額を残高に加算するためのものです。

  • 条件if (amount > 0)によって、負の金額を入金しようとする不正な操作を防いでいます。
  • 入金額が正の場合、balance += amountで残高を更新し、その結果を表示します。

引き出し処理

fun withdraw(amount: Double) {
    if (amount > 0 && amount <= balance) {
        balance -= amount
        println("¥$amount を引き出しました。現在の残高: ¥$balance")
    } else if (amount > balance) {
        println("エラー: 残高不足です。現在の残高: ¥$balance")
    } else {
        println("エラー: 引き出し額は正の数である必要があります。")
    }
}

この関数は指定された金額を残高から引き出すためのものです。

  • 条件if (amount > 0 && amount <= balance)によって、引き出し可能な金額かどうかを確認します。
  • 引き出し額が残高を超えている場合や負の金額の場合にはエラーメッセージを表示します。

メイン関数:プログラムの実行

fun main() {
    val myAccount = BankAccount(1000.0)
    println("新しい銀行口座を作成しました。現在の残高: ¥${myAccount.getBalance()}")

    myAccount.deposit(500.0)
    myAccount.withdraw(200.0)
    myAccount.withdraw(1500.0)
    myAccount.deposit(-100.0)
}

メイン関数はプログラムのエントリーポイントです。この中でクラスBankAccountを使用し、各種操作を実行します。

  • 銀行口座を初期残高1000.0で作成します。
  • 入金や引き出しを実行し、その結果を画面に表示します。
  • 無効な操作も試みて、適切なエラーメッセージが表示されることを確認します。

まとめ

このプログラムでは「アクセス修飾子とカプセル化」を通じて、データの安全な管理と不正操作の防止方法を学びました。

特にprivateアクセス修飾子を使ってプロパティを隠蔽し、ゲッターやメソッドを活用することで、外部からの不正なアクセスを防ぐ重要性を理解できます。

初心者の方は、この仕組みを他のプログラムにも応用し、より安全で管理しやすいコードを書く練習をしてみてください!

<<前のページ Kotlin記事一覧 次のページ>>

この記事への質問・コメント

この記事を作成するにあたりAIを活用しています。

問題ないことは確認していますが、もし間違いや表現の違和感などありましたら、ご指摘頂けると大変助かります。






    Kotlinのテキスト&問題集トップへ戻る
    トップページへ戻る