LoginSignup
6
4

More than 5 years have passed since last update.

型安全なビットフラグ集合の実装例

Last updated at Posted at 2018-08-17

Kotlin での、型安全で不変なビットフラグ集合の実装例。
(他の言語でも概ね同様にして型安全なビットフラグ集合を実現できるだろう。)

class Flags private constructor(
        private val bits: Int
) {
    companion object {
        val EMPTY = Flags(0b0000)

        val FLAG_A = Flags(0b0001)
        val FLAG_B = Flags(0b0010)
        val FLAG_C = Flags(0b0100)
        val FLAG_D = Flags(0b1000)
    }

    operator fun plus(flags: Flags): Flags =
            Flags(this.bits or flags.bits)

    operator fun minus(flags: Flags): Flags =
            Flags(this.bits and flags.bits.inv())

    operator fun times(flags: Flags): Flags =
            Flags(this.bits and flags.bits)

    operator fun contains(flags: Flags): Boolean =
            this * flags == flags

    override fun equals(other: Any?) = when {
        this === other -> true
        other !is Flags -> false
        else -> this.bits == other.bits
    }

    override fun hashCode() = bits.hashCode()

    override fun toString() =
            "${Flags::class.java.simpleName}(${::bits.name}=0b${bits.toString(2)})"
}

使用方法

    // plus
    val ab = Flags.FLAG_A + Flags.FLAG_B // -> 0b0011
    val abc = ab + Flags.FLAG_C // -> 0b0111
    ab + Flags.FLAG_A  // -> 0b0011 既に立っているフラグをさらに立てても影響なし

    // minus
    val bc = abc - Flags.FLAG_A // -> 0b0110
    bc - Flags.FLAG_A // -> 0b0110 既に倒れているフラグをさらに倒しても影響なし

    // times
    ab * bc // -> 0b0010

    // contains
    Flags.FLAG_A in ab // -> true
    Flags.FLAG_C in ab // -> false
    Flags.EMPTY in ab // -> true
    ab in abc // -> true 複数のフラグの場合、
    ab in bc // -> false 全てが含まれているかどうかを返す

    // equals
    ab + Flags.FLAG_C == abc // -> true
    ab == bc // -> false

解説

上から順に、簡単に解説する。

class

単なる class にする。
これにより、派生クラスは実装できない。

data class にはしない。
不正なプロパティ値のインスタンスが他所で生成されないようにする必要があり、そのため後述するようにコンストラクターを private にするのだが、
data class にしてしまうと、メンバ関数 copy が自動的に定義・実装され、それを用いると任意のプロパティ値を持つインスタンスを生成できるため。→https://kotlinlang.org/docs/reference/data-classes.html

private constructor

不正なプロパティ値のインスタンスが他所で生成されないよう、コンストラクターを private にする。

private val bits: Int

各ビットフラグを保持するビットフィールド。

  • 他所から直接参照されないよう private にする。
  • 不変にするため val にする。
  • ここでは Int を使用しているが、ビットフラグの数が多い場合は Longjava.util.BitSetを使うとよい。1
    companion object {
        val EMPTY = Flags(0b0000)

        val FLAG_A = Flags(0b0001)
        val FLAG_B = Flags(0b0010)
        val FLAG_C = Flags(0b0100)
        val FLAG_D = Flags(0b1000)
    }

各ビットフラグの定義を companion object のメンバ(他の言語でのクラスメンバに当たる)として行う。
いずれのフラグも立っていない状態を表す EMPTY も忘れず定義しよう。(全てのフラグが立っている状態を表す FULL もあると便利かもしれない。)

2進数リテラルは接頭辞 0b をつけることで記述できる。
また、(今回は使用していないが)桁区切りに _ を使用できるので、フラグ数が多い場合は4桁ごとにいれるとよい。例: val FLAG_E = Flags(0b0001_0000)

    operator fun plus(flags: Flags): Flags =
            Flags(this.bits or flags.bits)

+ 演算でフラグを立てられるよう、演算子をオーバーロードする。

    operator fun minus(flags: Flags): Flags =
            Flags(this.bits and flags.bits.inv())

- 演算でフラグを倒せるよう、演算子をオーバーロードする。

    operator fun times(flags: Flags): Flags =
            Flags(this.bits and flags.bits)

* 演算で共通のフラグを得られるよう、演算子をオーバーロードする。

    operator fun contains(flags: Flags): Boolean =
            this * flags == flags

in 演算でフラグが含まれているかどうかを得られるよう、演算子をオーバーロードする。

    override fun equals(other: Any?) = when {
        this === other -> true
        other !is Flags -> false
        else -> this.bits == other.bits
    }

    override fun hashCode() = bits.hashCode()

data class ではないため、メンバ関数 equals および hashCode をオーバーライドする必要がある。

    override fun toString() =
            "${Flags::class.java.simpleName}(${::bits.name}=0b${bits.toString(2)})"

toString 関数をオーバーライドして、Flags(bits=0x1111) の形式の文字列に変換できるようにする。 bits プロパティ値の出力を固定桁数にするのも良いだろう。

/以上


  1. クラスを分割することを検討した方が良いとは思うが。 Int でも32ビットもあるので。 

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4