記事内にはプロモーションが含まれています

【Java】ビット演算子の使い方!仕組みから実用的なフラグ管理まで

【Java】ビット演算子の使い方!仕組みから実用的なフラグ管理まで Java

Javaプログラミングにおいて、普段あまり意識することのない「ビット演算」。

「1と0の世界の話でしょ?」
「普通のアプリ開発では使わないのでは?」

このように思われがちですが、実はデータベースのフラグ管理や、パフォーマンスが要求される計算処理、画像処理などの分野では必須の技術です。

また、&&& の違いや、>>>>> の違いなど、Java特有の細かいルールを理解していないと、思わぬバグを引き起こす原因にもなります。

この記事では、Javaにおけるビット演算子の基本的な使い方から、論理演算子との違い、そして実務で役立つ「ビットフラグ」の活用テクニックまで、サンプルコード付きで徹底解説します。

【本記事の信頼性】
プロフィール
執筆者:マヒロ
  • 執筆者は元エンジニア
  • SES⇒大手の社内SE⇒独立
  • 現在はこじんまりとしたプログラミングスクールを運営
  • モットーは「利他の精神」

ビット演算とは?論理演算子との違い

ビット演算とは、整数データを「2進数(0と1の並び)」として扱い、ビット単位で計算を行う処理のことです。

コンピュータの内部構造に直接働きかけるため、非常に高速に処理できるという特徴があります。

ビット演算子と論理演算子の違い

Javaには &| といった演算子がありますが、これらは条件式(if文)で使う &&|| とは明確に異なります。

  • 論理演算子 (&&, ||)boolean 型(true/false)に対して使い、条件判定を行う。「短絡評価(左側だけで結果が決まれば右側を評価しない)」が行われる。
  • ビット演算子 (&, |, ^ など):整数型(int, longなど)に対して使い、ビットごとの演算を行う。短絡評価は行われない。

Javaで使えるビット演算子の一覧と使い方

Javaで利用可能な主要なビット演算子を紹介します。
それぞれの動作を理解するために、具体的なコードと実行結果を見ていきましょう。

1. AND演算子(&):論理積

両方のビットが「1」のときだけ「1」になります。
「特定のビットだけを取り出す(マスク処理)」際によく使われます。

public class BitwiseAndExample {
    public static void main(String[] args) {
        int a = 0b1010; // 10進数で10
        int b = 0b1100; // 10進数で12

        // 1010
        // 1100
        // ---- (&)
        // 1000 -> 10進数で8
        int result = a & b;

        System.out.println("10 & 12 = " + result);
        System.out.println("2進数表記: " + Integer.toBinaryString(result));
    }
}

実行結果

10 & 12 = 8
2進数表記: 1000

0b という接頭辞をつけることで、2進数リテラルとして記述できます。

ab の各ビットを比較し、両方が 1 の場所(最上位ビット)だけが 1 になっています。

2. OR演算子(|):論理和

どちらか一方でも「1」であれば「1」になります。
「特定のビットをONにする(フラグを立てる)」際によく使われます。

public class BitwiseOrExample {
    public static void main(String[] args) {
        int a = 0b1010; // 10
        int b = 0b1100; // 12

        // 1010
        // 1100
        // ---- (|)
        // 1110 -> 10進数で14
        int result = a | b;

        System.out.println("10 | 12 = " + result);
        System.out.println("2進数表記: " + Integer.toBinaryString(result));
    }
}

実行結果

10 | 12 = 14
2進数表記: 1110

3. XOR演算子(^):排他的論理和

ビットが異なるときに「1」、同じときに「0」になります。
「特定ビットの反転」や「暗号化の基礎」として利用されます。

public class BitwiseXorExample {
    public static void main(String[] args) {
        int a = 0b1010; // 10
        int b = 0b1100; // 12

        // 1010
        // 1100
        // ---- (^)
        // 0110 -> 10進数で6
        int result = a ^ b;

        System.out.println("10 ^ 12 = " + result);
        System.out.println("2進数表記: " + Integer.toBinaryString(result));
    }
}

実行結果

10 ^ 12 = 6
2進数表記: 110

4. NOT演算子(~):ビット反転

すべてのビットを反転させます(0を1に、1を0に)。これは単項演算子です。

public class BitwiseNotExample {
    public static void main(String[] args) {
        int a = 10; // 00...001010
        
        // ビット反転(補数表現になるため、値は -11 になる)
        int result = ~a;

        System.out.println("~10 = " + result);
        // 上位ビットも含めて表示される
        System.out.println("2進数表記: " + Integer.toBinaryString(result));
    }
}

実行結果

~10 = -11
2進数表記: 11111111111111111111111111110101

5. シフト演算子(<<, >>, >>>)

ビット列を左右にずらす演算です。掛け算や割り算の高速化に使われます。

  • 左シフト (<<): ビットを左にずらし、右側の空きを0で埋めます。2倍、4倍、8倍…にするのと同じ効果があります。
  • 右シフト (>>): ビットを右にずらします。符号付きシフトとも呼ばれ、最上位ビット(符号ビット)の値で空きを埋めます(正なら0、負なら1)。
  • 符号なし右シフト (>>>): ビットを右にずらしますが、符号を無視して必ず0で埋めます。Java特有の演算子です。
public class ShiftExample {
    public static void main(String[] args) {
        int a = 8;  // 00...01000
        int b = -8; // 11...11000 (2の補数)

        System.out.println("8 << 1  = " + (a << 1));  // 16 (2倍)
        System.out.println("8 >> 1  = " + (a >> 1));  // 4 (1/2倍)
        
        System.out.println("-8 >> 1 = " + (b >> 1));  // -4 (符号維持)
        System.out.println("-8 >>> 1 = " + (b >>> 1)); // 巨大な正の整数 (0埋めされるため)
    }
}

実行結果

8 << 1  = 16
8 >> 1  = 4
-8 >> 1 = -4
-8 >>> 1 = 2147483644

ビット演算の実践的な使い道:ビットフラグ

ビット演算が最も輝くのは、複数の状態(ステータス)を1つの整数変数で管理する「ビットフラグ」です。
例えば、「読取可」「書込可」「実行可」といった権限管理を考えてみましょう。

フラグ管理のサンプルコード

public class BitFlagExample {
    // 各フラグを2のべき乗で定義する
    public static final int READ    = 1; // 0001
    public static final int WRITE   = 2; // 0010
    public static final int EXECUTE = 4; // 0100
    public static final int DELETE  = 8; // 1000

    public static void main(String[] args) {
        // 権限の設定:READ と WRITE を付与
        // OR演算子 (|) でフラグをまとめる
        int permission = READ | WRITE; // 0001 | 0010 = 0011 (3)

        System.out.println("現在の権限値: " + permission);

        // 権限の確認:READ 権限を持っているか?
        // AND演算子 (&) で特定のビットが立っているか確認
        if ((permission & READ) != 0) {
            System.out.println("読取権限があります。");
        }

        // 権限の確認:EXECUTE 権限を持っているか?
        if ((permission & EXECUTE) != 0) {
            System.out.println("実行権限があります。");
        } else {
            System.out.println("実行権限はありません。");
        }

        // 権限の追加:EXECUTE を追加
        permission |= EXECUTE; // 0011 | 0100 = 0111 (7)
        System.out.println("実行権限を追加しました。権限値: " + permission);

        // 権限の削除:WRITE を削除
        // NOT (~) と AND (&) を組み合わせる
        permission &= ~WRITE; // 0111 & 1101 = 0101 (5)
        System.out.println("書込権限を削除しました。権限値: " + permission);
    }
}

実行結果

現在の権限値: 3
読取権限があります。
実行権限はありません。
実行権限を追加しました。権限値: 7
書込権限を削除しました。権限値: 5

コードの解説

  • | で合成: 複数のフラグを足し合わせます。
  • & で判定: 特定のフラグが含まれているかチェックします。(val & FLAG) != 0 は定型句です。
  • &= ~ で削除: 特定のビットだけを0(OFF)にします。

この手法を使うと、データベースのカラムを節約できたり、引数の数を減らしたりすることができます。

高速化テクニック!偶数・奇数の判定

ビット演算を使うと、偶数か奇数かの判定を高速に行うことができます。
通常は num % 2 != 0 と書きますが、ビット演算では (num & 1) == 1 と書けます。

int num = 5;
if ((num & 1) == 1) {
    System.out.println("奇数です");
} else {
    System.out.println("偶数です");
}

最下位ビットが 1 なら奇数、0 なら偶数という性質を利用しています。
大量のデータを処理するループ内などで、わずかながらパフォーマンス向上が期待できます。

Javaのスキルを活かして年収を上げる方法

以上、Javaにおけるビット演算子の使い方について解説してきました。

なお、Javaのスキルがある場合には、「転職して年収をアップさせる」「副業で稼ぐ」といった方法を検討するのがおすすめです。

Javaエンジニアの需要は非常に高いため、転職によって数十万円の年収アップはザラで、100万円以上年収が上がることも珍しくありません。

なお、転職によって年収を上げたい場合は、エンジニア専門の転職エージェントサービスを利用するのが最適です。

今すぐ転職する気がなくとも、とりあえず転職エージェントに無料登録しておくだけで、スカウトが届いたり、思わぬ好待遇の求人情報が送られてきたりするというメリットがあります。

併せて、副業案件を獲得できるエージェントにも登録しておくと、空いている時間を活かして稼げるようなJavaの案件を探しやすくなります。

転職エージェントも副業エージェントも、登録・利用は完全無料なので、どんな求人や副業案件があるのか気になる方は、気軽に利用してみるとよいでしょう。