objectについて

Kotlin の object は「静的(static)なクラス」ではなく、シングルトン(singleton)オブジェクトを定義する構文です。

あなたの CryptoManager の使い方はかなり典型的です。

object CryptoManager {
    fun encrypt(...) { ... }
}

呼び出しは

val encrypted = CryptoManager.encrypt(data, password)

となり、

val manager = CryptoManager()

のようなインスタンス化はできません。


objectとは?

Javaで書くとイメージ的には

public final class CryptoManager {

    private static final CryptoManager INSTANCE =
        new CryptoManager();

    private CryptoManager() {}

    public static CryptoManager getInstance() {
        return INSTANCE;
    }
}

に近いです。

Kotlinの

object CryptoManager

  • インスタンスが1つだけ
  • 遅延生成される
  • スレッドセーフ

という特徴があります。


staticとの違い

Javaの

public class Util {
    public static int add(int a, int b) {
        return a + b;
    }
}

に近い用途で

object Util {
    fun add(a: Int, b: Int): Int {
        return a + b
    }
}

と書けます。

使う側はほぼ同じです。

Util.add(1, 2)

ただし内部的には

  • Java → 本当に static
  • Kotlin object → シングルトンインスタンス

です。


Composeでよく使う?

Composeでもよく使います。

例えば

object AppColors {
    val Primary = Color(0xFF6200EE)
    val Secondary = Color(0xFF03DAC5)
}

object Routes {
    const val HOME = "home"
    const val SETTINGS = "settings"
}

のような

  • 定数管理
  • ユーティリティ
  • 状態を持たない処理

に向いています。

あなたの CryptoManager もまさに

暗号化処理をまとめたユーティリティ

なので object が適切です。


objectは状態も持てる

実は変数も持てます。

object Counter {
    var count = 0
}
Counter.count++

これは

Counter.count

がアプリ全体で共有されます。

つまり

object

  • static utility
  • singleton instance

の両方の役割を持っています。


sealed classとは?

次にこちら。

sealed class EncryptionException(message: String)
    : Exception(message)

sealed は

継承できる型を限定する

ための仕組みです。

通常の class はどこからでも継承できます。

open class Animal

class Dog : Animal()
class Cat : Animal()

しかし

sealed class Animal

にすると

同じファイル内でしか継承できません。

sealed class Animal

class Dog : Animal()
class Cat : Animal()

これはOK。

別ファイルで

class Bird : Animal()

はNGです。


なぜ使うの?

種類を限定したいからです。

例えば

sealed class Result {
    data class Success(val data: String) : Result()
    data class Error(val msg: String) : Result()
    object Loading : Result()
}

すると

when(result) {
    is Result.Success -> ...
    is Result.Error -> ...
    Result.Loading -> ...
}

else

が不要になります。

コンパイラが

Result の種類は全部知ってる

と判断できるからです。


あなたの例では

sealed class EncryptionException(message: String)
    : Exception(message)

の子クラスが

class EmptyPassword
class EmptyData
class Failed

だけであることを保証しています。

つまり

try {
    CryptoManager.encrypt(...)
} catch (e: CryptoManager.EncryptionException) {

    when (e) {
        is CryptoManager.EncryptionException.EmptyPassword -> ...
        is CryptoManager.EncryptionException.EmptyData -> ...
        is CryptoManager.EncryptionException.Failed -> ...
    }
}

のように安全に分岐できます。


object と sealed class を組み合わせることも多い

例えば

sealed class UiState {
    object Loading : UiState()
    object Empty : UiState()
    data class Success(val data: String) : UiState()
}

この

object Loading
object Empty

「値を持たない状態」

なのでインスタンスが1個で十分です。

Composeでは非常によく見るパターンです。


あなたのコードでいうと、

class EmptyPassword : EncryptionException(...)

はインスタンスごとに状態を持たないので、

実は

object EmptyPassword : EncryptionException(...)

と書くこともできます。

例えば

sealed class EncryptionException(message: String)
    : Exception(message) {

    object EmptyPassword : EncryptionException("パスワードが空です")

    object EmptyData : EncryptionException("データが空です")

    class Failed(cause: Throwable)
        : EncryptionException("暗号化に失敗しました: ${cause.message}")
}

の方が「例外の種類だけを表したい」場合は少し自然です。

ただし例外オブジェクトを毎回新規生成したい考え方もあるので、今の class でも全く問題ありません。

おまけ

sealed class DecryptionException(message: String) : Exception(message)

Java経験者だと最初は

class WrongPasswordException : Exception()
class CorruptedDataException : Exception()
class EmptyPasswordException : Exception()

みたいに別々の例外クラスを作りがちですが、Kotlinでは

sealed class DecryptionException

でまとめる設計がとてもよく使われます。

そのおかげで

catch (e: DecryptionException)

だけで関連する例外を一括で扱えるようになります。

タイトルとURLをコピーしました