SJ blog
beginner
S

信頼度ランク

S 公式ソース確認済み
A 成功実績多数・失敗例少数
B 賛否両論
C 動作未確認・セキュリティリスク高
Z 個人所感

型変換とキャストの仕組み — byte+byteがintになる理由と情報損失の正体

Javaの暗黙的型変換(widening)と明示的キャスト(narrowing)の仕組み。なぜbyte+byteはintになるのか、long→intでビットが切り捨てられる様子、リテラルの型を根本から解説。

一言結論

Javaは情報が失われない変換は自動(widening)、失われる可能性がある変換はキャスト必須(narrowing)。byte+byteがintになるのはCPUが32bitの整数演算をするから。

型変換には2種類ある

Javaの変数には型がある。型が違う値を代入したり演算したりするとき、型変換が起きる。

型変換には自動で行われるものと、明示的に指示が必要なものがある。

int x = 10;
long y = x;    // ✅ 自動変換(int → long)

long a = 10L;
int b = a;     // ❌ コンパイルエラー(long → int は明示的なキャストが必要)
int c = (int) a;  // ✅ キャストを明示

なぜ一方は自動で、もう一方は明示が必要なのか。

Widening:情報が増える方向は自動

数値型の「大きさ」の順番がある:

byte(8bit) → short(16bit) → int(32bit) → long(64bit) → float(32bit) → double(64bit)
                  char(16bit) ┘

小さい型から大きい型への変換を Widening Conversion(拡大変換) という。「情報が増える方向」なので自動でできる。

byte  b = 100;
short s = b;      // byte → short(自動)
int   i = s;      // short → int(自動)
long  l = i;      // int → long(自動)
float f = l;      // long → float(自動)
double d = f;     // float → double(自動)

直接の一気変換も可能:

byte b = 42;
double d = b;   // byte → double(自動)

注意点:long→floatは精度が落ちることがある

long big = 1_000_000_000_000_000L;  // 1000兆
float f = big;  // 自動変換されるが...

System.out.println(big);  // 1000000000000000
System.out.println(f);    // 1.0E15(精度が落ちている)

longは64bit整数で正確な値を持てるが、floatは32bitの浮動小数点なので大きな整数を正確に表現できない。「情報が失われる可能性がある」にもかかわらずWideningとして自動変換されるのがJavaのここだけの例外的な挙動だ。

Narrowing:情報が減る方向はキャスト必須

大きい型から小さい型への変換を Narrowing Conversion(縮小変換) という。情報が失われる可能性があるので、キャストを明示的に書かなければならない:

int i = 300;
byte b = (byte) i;   // ← キャストを明示
System.out.println(b);  // 44(!)

なぜ300が44になるのか。ビットレベルで見ると:

int 300:  00000000 00000000 00000001 00101100
                                    ↓(下位8bitだけ取り出す)
byte:                               00101100  = 44

intの下位8ビットだけが残る。上位24ビットは切り捨てられる。情報が「欠ける」のだ。

double→intの場合は小数点以下が切り捨てられる(四捨五入ではない):

double d = 9.99;
int i = (int) d;
System.out.println(i);  // 9(小数点以下は切り捨て)

double d2 = -9.99;
int i2 = (int) d2;
System.out.println(i2);  // -9(「ゼロ方向」に切り捨て)

マイナスの場合は「ゼロ方向」への切り捨てだ。Math.floor(-9.99) = -10 とは異なる。

リテラルの型

コード上に書いた数値リテラルにも型がある:

リテラル
100int
100L または 100llong
3.14double
3.14f または 3.14Ffloat
'A'char
long l = 10000000000;    // ❌ エラー:10000000000 はintリテラルだが、intの範囲を超えている
long l = 10000000000L;   // ✅ longリテラル

float f = 3.14;    // ❌ エラー:3.14 はdoubleリテラル。double→floatはnarrowing
float f = 3.14f;   // ✅ floatリテラル

byteやshortへの代入は特例がある:

byte b = 127;   // ✅ リテラル 127 はintだが、byteの範囲内ならコンパイラが許可する
byte b = 128;   // ❌ エラー:128 はbyteの範囲(-128〜127)を超える

コンパイル時に値が確定しているリテラルの場合、コンパイラが範囲チェックをしてOKなら暗黙変換してくれる特例だ。

byte + byte がintになる理由

これが試験でよく問われる:

byte b1 = 10;
byte b2 = 20;
byte b3 = b1 + b2;   // ❌ コンパイルエラー!
int  i3 = b1 + b2;   // ✅

b1b2byteなのに、足した結果がintになる。なぜか?

CPUのアーキテクチャが理由だ。

現代のCPUは「32bitや64bitのレジスタ」で演算する。byte(8bit)をそのまま演算しようとすると、内部でCPUは32bitのレジスタに載せて計算する。これをJVM仕様が「整数の格上げ(Integer Promotion)」として明示的に定義している。

byte b1 = 10, b2 = 20;

演算前の格上げ:
  b1 → int(10)
  b2 → int(20)

演算:
  int(10) + int(20) = int(30)

結果の型: int

byte, short, charの演算は、すべて演算前にintに格上げされる。演算結果はint型だ。

byte b1 = 10, b2 = 20;
byte b3 = (byte)(b1 + b2);  // ✅ 明示的にbyteにキャスト

混合演算の型の決まり方

異なる型が混在した演算では、「より大きい型」に揃えられる:

// ルール: byte/short/char → int → long → float → double の順で格上げ
int   i = 10;
long  l = 20L;
int   x = i + l;   // ❌ int + long = long → intに代入不可
long  y = i + l;   // ✅ long

long  a = 10L;
float f = 2.0f;
long  b = a + f;   // ❌ long + float = float → longに代入不可
float g = a + f;   // ✅ float

float  f2 = 1.0f;
double d  = 2.0;
float  h  = f2 + d;  // ❌ float + double = double
double dd = f2 + d;  // ✅ double

混合演算では 左辺でも右辺でもなく「より大きい型」に統一されてから演算する

よくある試験問題パターン

// これはOKか?
byte b = 10;
b = b + 1;    // ❌ b + 1 は int(bがintに格上げされる)→ intをbyteに代入できない

b += 1;       // ✅ 複合代入演算子(+=)は暗黙的にキャストする

b++;          // ✅ インクリメント演算子も暗黙的にキャストする

b = b + 1 がエラーで b += 1 がOKというのは混乱しやすい。+=演算子は内部で b = (byte)(b + 1) と同等の処理をする。

まとめ

Widening(自動変換)  = 小さい型 → 大きい型。情報が増える方向。(byte→short→int→long→float→double)
Narrowing(キャスト)= 大きい型 → 小さい型。情報が失われる可能性。(int)value と明示する
リテラルの型          = 整数はint、Lつきはlong、小数はdouble、fつきはfloat
整数の格上げ          = byte/short/char同士の演算 → 演算前にintに格上げされる(結果はint)
混合演算の型          = より大きい型に揃えてから演算(int+long=long、long+double=double)
+= の特例            = 複合代入演算子は暗黙的にキャストを含む(b += 1 は b = (byte)(b+1))