【決定版】Javaのキャスト(型変換)完全ガイド|数値型・参照型・アップ/ダウンキャスト・例外まで

目次

1. キャスト(型変換)とは?まず結論

Javaの「キャスト(cast)」は、ある値やオブジェクトを、別の型として扱うための操作です。
たとえば doubleint に変換して小数点以下を捨てたいときや、親クラス型で受け取ったオブジェクトを子クラス型として扱いたいときに登場します。

ただし、キャストは便利な反面、情報が失われたり、実行時エラーの原因になったりします。この記事では「使い方」だけでなく、「どこで危険になるのか」まで含めて整理していきます。

1.1 この記事で分かること(最短の理解ルート)

キャストで迷うポイントは、最終的に次の2つに分かれます。

  • 数値型のキャスト(プリミティブ型)
    int / long / double などの数値同士の変換。
    ここで重要なのは 「暗黙変換できるか」「情報落ちするか」 です。
  • 参照型のキャスト(クラス/インタフェース)
    継承関係があるオブジェクトを、別の型として扱う変換。
    ここで重要なのは 「実体の型は何か」「ダウンキャストの安全性」 です。

この2種類を混ぜて考えると一気にややこしくなるので、記事の序盤で分けて理解するのが最短です。

1.2 キャストが必要になる典型場面

初心者が最初に出会いやすいのは、次のようなケースです。

数値型の例:小数を整数にしたい

double price = 19.99;
int yen = (int) price; // 19 (小数点以下が消える)

このように 小数点以下を捨てる(=情報落ちする)変換では、(int) のような明示キャストが必要になります。

参照型の例:親型で受け取ったものを子型として使いたい

Animal a = new Dog();   // 親型で受け取る(アップキャスト)
Dog d = (Dog) a;        // 子型に戻して扱う(ダウンキャスト)

この例は動きますが、もし a の実体が Dog ではなかったら実行時に失敗します。
つまり参照型のキャストは、**「できるときはできるが、間違えると実行時に落ちる」**性質があります。

1.3 キャストの“危険ポイント”はここだけ押さえればOK

キャストが危険と言われる理由は、大きく次の2つです。

1) 数値型:値が変わる(情報落ち・丸め・オーバーフロー)

  • double → int で小数が消える(意図した丸めではないことがある)
  • 範囲の狭い型に入れると、値が想定外になる可能性がある(オーバーフロー)

2) 参照型:実行時エラー(ClassCastException)

  • 「親型の変数」だから安全ではない
  • 実体がその子型である保証がないままダウンキャストすると落ちる

この2点を先に理解しておくと、キャスト関連のトラブルの大半は回避できます。

1.4 “キャスト”と混同されやすい言葉(最初に整理)

キャストの学習で混乱しやすいのが、似た言葉が多いことです。ここで最小限だけ整理します。

  • 暗黙の型変換(暗黙変換)
    自動的に型が広がる方向の変換(例:int → double)など。コードに (型) を書かなくても通ります。
  • 明示的キャスト(明示キャスト)
    (int) のように、開発者が意図して型を変えるもの。情報落ちや危険がある方向の変換で必要になりやすいです。
  • ボクシング/アンボクシング
    intInteger の自動変換のこと。キャストとは別物ですが、実際のバグ原因になりやすいので後半で扱います。

2. Javaのキャストは2種類ある(数値型/参照型)

Javaのキャスト(型変換)を正しく理解するコツは、最初に 「数値型のキャスト」「参照型のキャスト」 を完全に分けて考えることです。
この2つは同じ「キャスト」という言葉でも、目的も危険ポイントもまったく違うからです。

2.1 数値型キャスト(プリミティブ型)とは?

数値型キャストは、intdouble のような プリミティブ型(基本データ型)同士を変換することです。

代表的な数値型は次の通りです。

  • 整数:byte / short / int / long
  • 小数:float / double
  • 文字:char(内部的には数値として扱える)

数値型キャストが必要になる例

たとえば次のように、計算結果を別の型に入れたい場面です。

double d = 10.5;
int i = (int) d; // 10

この変換は「小数 → 整数」なので情報が減ります。
そのため、Javaは勝手にやらず、明示的にキャストを書けというルールになっています。

数値型キャストの重要ポイント:拡大変換と縮小変換

数値型の変換は、大きく2つに分かれます。

  • 拡大変換(widening conversion)
    表現できる範囲が広い型へ移す変換
    例:int → longint → double
    → 基本的に安全なので 暗黙変換(自動) でOK
  • 縮小変換(narrowing conversion)
    表現できる範囲が狭い型へ移す変換
    例:double → intlong → short
    → 危険なので 明示キャスト(手動) が必要

この区別を理解すると、「なぜここはキャストが必要なのか?」が一気に分かるようになります。

2.2 参照型キャスト(クラス/インタフェース)とは?

参照型キャストは、クラスやインタフェースなどの オブジェクトの型を変換することです。

ここで重要なのは、数値型と違って「値を変換する」というより、**“扱い方(見え方)を変える”**という感覚に近い点です。

参照型キャストが出てくる典型

たとえば、次のような継承関係を想像してください。

  • Animal(親クラス)
  • Dog(子クラス)
Animal a = new Dog(); // DogをAnimalとして扱う(アップキャスト)

この時点で aAnimal 型です。
しかし実体は Dog なので、状況によっては Dog の機能を使いたくなります。

Dog d = (Dog) a; // Dogとして扱う(ダウンキャスト)

このように参照型キャストは、多態性(ポリモーフィズム)とセットで登場します。

参照型キャストの重要ポイント:アップキャストとダウンキャスト

参照型キャストも2種類に分かれます。

  • アップキャスト(子 → 親)
    例:Dog → Animal
    → 基本的に安全で、暗黙変換できる
  • ダウンキャスト(親 → 子)
    例:Animal → Dog
    → 危険で、明示キャストが必要

2.3 数値型と参照型で「危険」の意味が違う

初心者が混乱しやすいのは、両方とも「危険」と言われるけど、危険の中身が違う点です。

数値型キャストが危険な理由

  • 情報落ちする(小数点が消える、桁が落ちる)
  • 範囲外になると値が壊れる(オーバーフロー)

つまり、コンパイルは通るが値が想定外になるタイプの危険です。

参照型キャストが危険な理由

  • 実体の型が違うと、実行時に落ちる(ClassCastException

つまり、コンパイルは通るが実行時に例外で停止するタイプの危険です。

2.4 まず覚えるべき“最重要の結論”

キャストの理解で迷ったら、まずこの結論に戻ってください。

  • 数値型キャスト
    → 「変換できるが、値が変わる可能性がある」
  • 参照型キャスト
    → 「変換できるが、実体が違うと落ちる」

この2本柱を押さえておけば、次のセクション以降の理解がスムーズになります。

3. 数値型のキャスト:暗黙変換と明示キャストの基本

ここからは 数値型(プリミティブ型)のキャストを、実務で困らないレベルまで整理します。
数値型キャストは、初心者が最初に「コンパイルエラー」「計算結果がおかしい」「小数が消えた」などでつまずきやすい分野です。

このセクションを読み終えると、次の疑問が解決します。

  • なぜ intdouble に代入できるのか?
  • なぜ doubleint に代入できないのか?
  • (int) を付けたら何が起きるのか?
  • いつキャストが必要で、いつ不要なのか?

3.1 暗黙の型変換(拡大変換)の例

Javaでは、表現できる範囲が広い型へ移す変換は基本的に安全とみなされます。
そのため、キャストを書かなくても自動で変換(暗黙変換)されます。

例:intdouble(暗黙変換OK)

int i = 10;
double d = i;

System.out.println(d); // 10.0

この変換が安全なのは、double の方が int より広い範囲を扱えるためです。

例:byteint(暗黙変換OK)

byte b = 100;
int i = b;

System.out.println(i); // 100

byte は -128〜127 の範囲ですが、int はさらに広いので問題ありません。

3.2 明示キャスト(縮小変換)の例と“丸め”の挙動

次に、初心者が必ず出会うのが 縮小変換です。
縮小変換は、情報が失われる可能性があるため、Javaは自動変換してくれません。

例:doubleint(明示キャストが必要)

double d = 10.9;
int i = (int) d;

System.out.println(i); // 10

ここで重要なのは、(int) によって起きるのは 四捨五入ではなく切り捨て という点です。

  • 10.9 → 10
  • 10.1 → 10
  • -10.9 → -10(マイナスの場合は「0に近づく方向」)

例:longint(明示キャストが必要)

long l = 100L;
int i = (int) l;

System.out.println(i); // 100

この例は安全に見えますが、long の値が大きすぎる場合は次のように壊れます。

3.3 オーバーフロー/アンダーフローの落とし穴

縮小変換が危険な理由は、単に「小数が消える」だけではありません。
**表現できる範囲を超えると、値が壊れる(オーバーフロー)**ことがあります。

例:大きすぎる longint にキャストする

long l = 3_000_000_000L; // 30億(intの最大値を超える)
int i = (int) l;

System.out.println(i); // 想定外の値になる

int の最大値は約21億なので、30億は入りません。
このとき Java は「エラーにせず、ビットとして切り詰めた結果」を入れてしまうため、見た目が完全に別の数になります。

ここが怖いポイントです。
コンパイルは通るし、実行もできるのに、値だけ壊れるので、バグが発見しづらくなります。

3.4 よくあるコンパイルエラー例(possible lossy conversion)

縮小変換をキャストなしで書くと、Javaはエラーを出します。
初心者が最初に見るのがこのタイプです。

例:doubleint に代入しようとして失敗

double d = 1.5;
int i = d; // コンパイルエラー

このときエラーは次のような意味になります。

  • lossy conversion(情報が失われる変換)になる可能性がある
  • だから、開発者が責任を持って (int) を書いてね

例:longint に代入しようとして失敗

long l = 100L;
int i = l; // コンパイルエラー

値が100なら安全そうですが、Javaは「将来的に大きい値が入る可能性」を考えるため、基本的に許可しません。

3.5 計算式で起きる“意外な型変換”(初心者がハマるポイント)

数値型キャストで地味に厄介なのが、計算式の中で型が変わるパターンです。

例:整数同士の割り算は整数になる

int a = 5;
int b = 2;

System.out.println(a / b); // 2

「2.5になってほしい」と思っても、int / intint なので小数が消えます。

例:片方を double にすると小数になる

int a = 5;
int b = 2;

System.out.println((double) a / b); // 2.5

このように、どこでキャストするかによって結果が変わります。

3.6 実務での使い分け(結論)

数値型キャストは、次の判断基準で考えると迷いません。

  • 拡大変換(小→大)は暗黙でOK
  • 縮小変換(大→小)は明示キャストが必要
  • 明示キャストは「値が変わる」ことを前提に使う
    (切り捨て・オーバーフローの可能性を理解する)

4. 参照型のキャスト:アップキャスト/ダウンキャスト

ここからは 参照型(クラス/インタフェース)のキャストを解説します。
数値型キャストと違い、参照型キャストは「値を変換する」というより、**オブジェクトをどの型として扱うか(見え方を変える)**という考え方になります。

そして参照型キャストで最も重要なのはこの一点です。

変数の型ではなく、実体(newされている型)が何かがすべて

この感覚が身につくと、ClassCastException で悩むことが激減します。

4.1 アップキャスト(子 → 親)は基本安全・暗黙でOK

アップキャストは、子クラスのオブジェクトを親クラス型で扱うことです。
これは Java のオブジェクト指向では日常的に使われます。

例:子クラスを親クラスとして扱う(アップキャスト)

class Animal {
    void eat() {
        System.out.println("eat");
    }
}

class Dog extends Animal {
    void bark() {
        System.out.println("bark");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();

        Animal a = dog; // アップキャスト(暗黙でOK)
        a.eat();        // OK(親にあるメソッド)
    }
}

このように、DogAnimal として扱うことは問題ありません。
なぜなら DogはAnimalの一種だからです。

アップキャストのメリット:共通処理を書ける

アップキャストが便利なのは、「同じ親を持つ複数の子」をまとめて扱えるからです。

Animal[] animals = { new Dog(), new Animal() };

for (Animal a : animals) {
    a.eat();
}

このように、親型で統一すると 処理を共通化できるため、設計がシンプルになります。

4.2 アップキャストすると“できなくなること”がある

アップキャストは安全ですが、見え方が親型になるので、子クラス独自のメソッドは呼べなくなります。

Animal a = new Dog();
a.bark(); // コンパイルエラー(Animalにはbark()がない)

ここで初心者は「Dogなのに呼べないのはなぜ?」となりがちですが、理由は単純です。

  • 変数 a の型は Animal
  • Javaはコンパイル時に Animal としてチェックする
  • Animalbark() はないのでエラー

つまり 実体がDogでも、変数型がAnimalならDogのメソッドは見えないのです。

4.3 ダウンキャスト(親 → 子)は危険・明示キャストが必要

ダウンキャストは、親型で扱っているものを 子型として扱い直す操作です。

Animal a = new Dog();
Dog d = (Dog) a; // ダウンキャスト(明示が必要)
d.bark();        // OK

この例は成功します。
なぜなら a の実体が new Dog() だからです。

4.4 ダウンキャストが危険な理由:ClassCastException

参照型キャストの怖さはここです。
実体がその子型でないのにダウンキャストすると、実行時に例外で落ちます。

例:実体がDogではないのにDogへキャストしてしまう

Animal a = new Animal(); // 実体はAnimal

Dog d = (Dog) a; // 実行時エラー(ClassCastException)

このコードは コンパイルは通ります
しかし実行すると落ちます。

なぜコンパイルが通るかというと、AnimalDog は継承関係があるため、文法的には「キャスト自体は可能」と判断されるからです。

ただし実行時には、

  • a の実体は Animal
  • Dog ではない
  • だから Dog として扱えない

となり、ClassCastException が発生します。

4.5 参照型キャストで最重要:変数型と実体型を分けて考える

参照型キャストで混乱する原因は、「型」という言葉が2種類あることです。

  • 変数の型(コンパイル時に見える型)
  • 実体の型(newされた実際のクラス)

例で整理するとこうです。

Animal a = new Dog();

このとき、

  • 変数の型:Animal
  • 実体の型:Dog

このズレは Java の多態性(ポリモーフィズム)では正常な状態です。
むしろ、ズレを許すからこそ「共通処理」が書けます。

4.6 “キャストが必要になる理由”はほぼこれ

参照型でキャストが必要になるのは、たいてい次の理由です。

  • 親型でまとめて扱っていた(アップキャストしていた)
  • でも、ある場面だけ子型の機能を使いたくなった
  • だからダウンキャストする必要が出た

つまり、キャストは 多態性の副作用として必要になることが多いです。

4.7 ただし、キャストは最後の手段にするのが基本

参照型のダウンキャストは、使うべき場面もありますが、乱用するとコードが壊れやすくなります。

  • if とキャストが増える
  • 型の追加・変更に弱くなる
  • 実行時例外が増える

そのため実務では、可能なら次のような方向で設計します。

  • 親クラス(またはインタフェース)に必要な操作を定義する
  • オーバーライドで振る舞いを切り替える
  • instanceof に頼りすぎない

この「キャストを減らす設計」は後半のセクションで扱います。

5. instanceofで型チェックしてからキャストする(安全策)

参照型のキャストで一番怖いのは、前のセクションで見た通り ClassCastException です。
この事故を防ぐために、Javaで昔から使われている基本テクニックが instanceof による型チェックです。

結論から言うと、ダウンキャストをする可能性があるなら、

キャストの前に instanceof で確認する

これが最も安全で、初心者にも分かりやすい基本形です。

5.1 instanceofとは?(やっていることは「実体型の確認」)

instanceof は、オブジェクトが「ある型のインスタンスかどうか」を判定する演算子です。

if (obj instanceof Dog) {
    // objはDogとして扱える可能性が高い
}

ここでチェックしているのは、変数の型ではなく実体の型です。

5.2 instanceofを使った安全なダウンキャスト(基本形)

まずは、もっとも標準的な書き方です。

Animal a = new Dog();

if (a instanceof Dog) {
    Dog d = (Dog) a; // ここで初めてキャストする
    d.bark();
}

この形のメリットは明確で、

  • a の実体が Dog のときだけキャストする
  • それ以外はキャストしない

という安全設計になっています。

5.3 instanceofがないと何が危険なのか(失敗例)

instanceof を省略してキャストすると、実体が違った瞬間に落ちます。

Animal a = new Animal();

Dog d = (Dog) a; // ClassCastException

この手の例外は、開発中は見つかっても、本番データで初めて出ることもあります。
だからこそ「安全策として instanceof」が重要です。

5.4 instanceofでチェックしても“安全とは限らない”ケース

ここは初心者向け記事としては少しだけ触れておくと価値が上がります。
instanceof は強力ですが、次のようなケースでは設計を見直した方がいいことがあります。

H3 分岐が増えすぎる(キャスト地獄)

型が増えるたびに if (x instanceof A) が増えると、コードが崩れます。

if (a instanceof Dog) {
    // ...
} else if (a instanceof Cat) {
    // ...
} else if (a instanceof Bird) {
    // ...
}

この状態は、次のような問題を生みます。

  • 型が増えるたびに修正箇所が増える
  • 変更に弱い(拡張が面倒)
  • “本来は多態性で解決できる”ことが多い

後半の「キャストを減らす設計」で、より良い方向性を紹介します。

5.5 instanceofのパターンマッチング(Javaの新しめの書き方)

Javaでは近年、instanceof とキャストをセットで書くのを簡単にする構文が追加されています。
それが パターンマッチングです。

従来の書き方(チェック → キャスト)

if (a instanceof Dog) {
    Dog d = (Dog) a;
    d.bark();
}

パターンマッチング(チェックしながら変数に束縛)

if (a instanceof Dog d) {
    d.bark();
}

この書き方のメリットは大きく2つです。

  • キャストを書く必要が減り、読みやすい
  • d のスコープが if の中に限定され、安全

初心者向けの記事でも、ここは「知っておくと便利」枠として紹介すると差別化になります。

5.6 instanceofを使うときの判断基準(実務向けの結論)

ダウンキャストをしたいとき、判断は次の順番で考えると安全です。

  1. そもそもキャストが必要か?
    → 親型(インタフェース)に必要なメソッドを置けないか検討する
  2. キャストが必要なら instanceof で守る
    → 事故を防ぐ最優先
  3. 分岐が増えるなら設計を疑う
    → 多態性やインタフェース設計で回避できることが多い

6. キャストを減らす設計(実務で効く考え方)

ここまでで、キャストの正しい使い方と安全策は理解できたはずです。
しかし実務では、次のような違和感を覚えることがあります。

  • instanceof とキャストがあちこちに出てくる
  • 型ごとに if / else が増え続ける
  • 変更や追加が入るたびに、既存コードを直す必要がある

この状態は、「キャストの使い方が悪い」というより、設計レベルで無理が出ているサインです。
このセクションでは、キャストを減らす=事故を減らす設計の考え方を整理します。

6.1 キャストが増えやすい典型パターン

まず、ありがちな悪い例から見てみます。

void process(Animal a) {
    if (a instanceof Dog) {
        Dog d = (Dog) a;
        d.bark();
    } else if (a instanceof Cat) {
        Cat c = (Cat) a;
        c.meow();
    }
}

一見すると問題なさそうですが、次の欠点があります。

  • 型が増えるたびに if が増える
  • process メソッドがすべての子クラスを知っている
  • 新しい型を追加すると既存コードを修正する必要がある

これは オブジェクト指向のメリットを捨てている状態です。

6.2 多態性(ポリモーフィズム)を使えばキャストは不要になる

上の例は、多態性を使えばキャストなしで書けます。

class Animal {
    void sound() {
    }
}

class Dog extends Animal {
    @Override
    void sound() {
        System.out.println("bark");
    }
}

class Cat extends Animal {
    @Override
    void sound() {
        System.out.println("meow");
    }
}

呼び出し側はこうなります。

void process(Animal a) {
    a.sound(); // キャスト不要
}

ここで重要なのは、

  • どのメソッドが呼ばれるかは実体の型で決まる
  • 呼び出し側は型分岐を意識しなくていい

という点です。
これがオブジェクト指向の基本であり、キャストを減らす最大の武器です。

6.3 「キャストしたくなる理由」を上位型に持ち上げる

実務でキャストが出てくる理由の多くは、次のようなものです。

「親型にはそのメソッドがないから、子型にキャストして呼びたい」

これは設計のヒントになります。

悪い発想

  • 子型にしかメソッドがない
  • だからキャストするしかない

良い発想

  • 本当に必要な操作なら、親型(またはインタフェース)に定義する

例:インタフェースを使った改善

interface Speaker {
    void speak();
}

class Dog implements Speaker {
    public void speak() {
        System.out.println("bark");
    }
}

class Cat implements Speaker {
    public void speak() {
        System.out.println("meow");
    }
}
void process(Speaker s) {
    s.speak(); // キャスト不要
}

このように、振る舞いを軸に設計するとキャストは自然に消えます。

6.4 ダウンキャストが許される代表的なケース

とはいえ、ダウンキャストが完全に悪というわけではありません。
次のようなケースでは、現実的にキャストが必要になることもあります。

  • フレームワークやライブラリのAPI仕様で、戻り値が抽象型になっている
  • コールバックやイベント処理で、共通型で受け取るしかない
  • 既存設計を大きく変えられないレガシーコード

この場合でも、次のルールを守ると安全性が上がります。

  • instanceof で守る
  • キャストする範囲を最小限にする
  • メソッドの入り口ではなく、内部に閉じ込める

6.5 キャストを減らすと得られるメリット(実務目線)

キャストを減らす設計にすると、次のような効果があります。

  • 実行時例外(ClassCastException)が激減する
  • コードの見通しが良くなる
  • 新しい型を追加しても既存コードを壊しにくい
  • テストが書きやすくなる

特に、「instanceof が並び始めたら設計を疑う」
この感覚を持っておくと、中級者以上への成長が早くなります。

7. ボクシング/アンボクシングとキャストの混同に注意

Javaの「キャスト(型変換)」を学んでいると、必ず出てくる混乱ポイントがあります。
それが ボクシング(boxing)/アンボクシング(unboxing) です。

結論から言うと、これはキャストとは別物ですが、見た目が似ているため初心者が混同しやすく、さらに 実行時エラーの原因にもなるので注意が必要です。

7.1 ボクシング/アンボクシングとは?

Javaには、次の2種類の型があります。

  • プリミティブ型int / double など
  • ラッパークラスInteger / Double など(オブジェクト)

この2つの間を自動で変換してくれる仕組みが、ボクシング/アンボクシングです。

  • ボクシング:プリミティブ → ラッパークラス
    例:int → Integer
  • アンボクシング:ラッパークラス → プリミティブ
    例:Integer → int

7.2 オートボクシングの例(int → Integer)

int x = 10;
Integer y = x; // オートボクシング

System.out.println(y); // 10

このコードはキャストを書いていませんが、Javaが内部で Integer.valueOf(x) のような変換を行っています。

7.3 オートアンボクシングの例(Integer → int)

Integer x = 10;
int y = x; // オートアンボクシング

System.out.println(y); // 10

これも同様に、内部で x.intValue() のような変換が行われます。

7.4 ボクシングは便利だが、落とし穴もある

ボクシング/アンボクシングは便利ですが、初心者がハマりやすい落とし穴が2つあります。

7.4.1 Nullが絡むと NullPointerException になる

アンボクシングは「オブジェクト → プリミティブ」なので、もしオブジェクトが null なら取り出せません。

Integer x = null;
int y = x; // NullPointerException(アンボクシングで落ちる)

これが怖いのは、見た目がただの代入なのに 実行時に突然落ちることです。

特に次のような場面で起きやすいです。

  • Mapから取り出した値が null だった
  • DBやJSONなど外部入力で null が入り得る
  • 条件分岐の漏れ

7.4.2 == の比較で混乱する(Integer同士の比較)

Integer はオブジェクトなので、== は「値」ではなく「参照(同じ物か)」の比較になります。

Integer a = 100;
Integer b = 100;

System.out.println(a == b); // trueになることがある
Integer a = 1000;
Integer b = 1000;

System.out.println(a == b); // falseになることがある

この違いは、Javaが内部で値をキャッシュする仕組みが絡むためで、初心者には非常に分かりにくい挙動です。

基本ルールはシンプルで、

  • ラッパークラスの比較は equals() を使う

これを徹底すると事故が減ります。

Integer a = 1000;
Integer b = 1000;

System.out.println(a.equals(b)); // true(値の比較)

7.5 キャストとボクシングは何が違うのか?

ここで整理すると、混乱がなくなります。

  • キャスト(数値型)
    double → int のように、型を変えて値を変換する
    → 情報落ち(切り捨て)やオーバーフローが起きる
  • キャスト(参照型)
    Animal → Dog のように、見え方を変える
    → 間違えると ClassCastException
  • ボクシング/アンボクシング
    int ↔ Integer のように、プリミティブとオブジェクトを行き来する
    null があると NullPointerException が起きる

このように、仕組みも危険ポイントも別物です。

7.6 実務でのおすすめ方針(初心者向けの結論)

初心者が安全に進めるなら、次の方針が無難です。

  • 計算や数値処理は プリミティブ型を基本にする(int, long, double
  • コレクション(List/Map)に入れる必要があるときだけラッパークラスを使う
  • ラッパークラス同士の比較は equals() を使う
  • Integernull になり得る場面ではアンボクシングに注意する

8. ジェネリクスとキャスト:unchecked警告の意味と対処

ここからは、Javaのキャストで中級者が必ず出会うテーマ
ジェネリクス(Generics)とキャストについて解説します。

初心者のうちは「キャスト=(int)や(Dog)」のように分かりやすい形で出てきますが、実務では次のような場面で困りがちです。

  • コンパイルは通るのに 警告(warning)が出る
  • unchecked cast と言われて不安になる
  • そのまま動くが、あとで壊れそうで怖い

このセクションでは、警告の意味を正しく理解し、実務で安全に対処する方法を整理します。

8.1 unchecked cast警告とは?(結論:安全性を保証できない)

unchecked cast とは、

「そのキャスト、本当に安全かコンパイラが確認できないよ」

という意味です。

重要なのは、これは コンパイルエラーではなく警告だという点です。
つまり Java は「実行はできるが、危ない可能性がある」と教えてくれています。

8.2 なぜ unchecked が出るのか?(型情報が消えるため)

ジェネリクスは便利ですが、Javaでは内部的に 型情報が実行時に消える仕組みがあります。
このため、コンパイラは「本当にその型なのか?」を実行時に確認できません。

その結果、次のようなコードで警告が出ます。

8.3 よくある例:List を生の型(raw type)で扱ってしまう

NG例:raw typeを使う

import java.util.*;

public class Main {
    public static void main(String[] args) {
        List list = new ArrayList(); // raw type(型パラメータなし)
        list.add("Hello");

        List<Integer> numbers = (List<Integer>) list; // unchecked cast 警告
        Integer x = numbers.get(0); // 実行時に落ちる可能性あり
    }
}

このコードは、かなり危険です。

  • list の中身は "Hello"(String)
  • それを List<Integer> だと思い込んでいる
  • Integer として取り出そうとして壊れる

つまり unchecked castは「嘘をついている可能性」を示す警告です。

8.4 まずやるべき対処:raw typeをやめる(最優先)

多くの unchecked 警告は、raw type をやめれば解決します。

OK例:型パラメータを付ける

import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("Hello");

        String s = list.get(0); // キャスト不要で安全
        System.out.println(s);
    }
}

この形にしておけば、コンパイル時点で型が守られます。

8.5 どうしても unchecked を避けられない場面

実務では、次のようなケースで unchecked が出ることがあります。

  • 外部ライブラリが raw type を返してくる
  • JSONパースなどで Object として受け取るしかない
  • 既存コードが古く、型を直せない

このとき重要なのは、

警告を消すのではなく、危険を小さくする

という考え方です。

8.6 安全に寄せる基本戦略:キャストする場所を最小化する

危険なキャストは、できるだけ狭い範囲に閉じ込めます。

例:変換メソッドに封じ込める

@SuppressWarnings("unchecked")
static List<String> toStringList(Object obj) {
    return (List<String>) obj;
}

そして呼び出し側では、キャストを意識しない形にします。

Object obj = new ArrayList<String>();
List<String> list = toStringList(obj);

ただし、この書き方は 本当にStringのListだと保証できる場合だけにしてください。

8.7 @SuppressWarnings("unchecked") を使うときのルール

@SuppressWarnings("unchecked") は便利ですが、使い方を間違えると危険です。
実務では、次のルールを守ると事故が減ります。

  • メソッド全体ではなく、必要最小限の行・ブロックに付ける
  • なぜ安全なのかをコメントで残す
  • “警告を消すためだけ”に付けない

例:最小範囲で抑制する

@SuppressWarnings("unchecked")
List<String> list = (List<String>) obj;

8.8 List<Object>List<String> は別物(初心者が誤解しやすい)

ジェネリクスでよくある誤解がこれです。

StringObject の子だから
List<String>List<Object> の子?

これは 違います

  • StringObject の子
  • でも List<String>List<Object> の子ではない

このルールがあるため、次のコードはコンパイルできません。

List<String> strings = new ArrayList<>();
List<Object> objects = strings; // コンパイルエラー

この制約があるおかげで、型安全が守られています。
もし代入できたら、objects.add(123) のように数字を混ぜられてしまい、壊れます。

8.9 ジェネリクスとキャストの結論(実務で迷わない指針)

ジェネリクスとキャストで迷ったら、次を守るのが最も安全です。

  • raw type を使わない(最優先)
  • キャストはできるだけ避ける
  • どうしても必要なら、範囲を最小にして閉じ込める
  • @SuppressWarnings は最終手段として慎重に使う

9. よくある失敗パターン集(コピペで再現できる短い例)

キャストは「理解しているつもり」でも、実際の開発ではミスが起きやすい分野です。
そこでこのセクションでは、初心者が特にやりがちな失敗を 短いコードで再現しながら整理します。

ポイントは、ただ失敗例を並べるのではなく、

  • なぜ失敗するのか
  • どう直すのが安全か

をセットで覚えることです。

9.1 数値型:doubleint で四捨五入されると思い込む

失敗例:四捨五入だと思ってキャストする

double x = 9.9;
int y = (int) x;

System.out.println(y); // 9

(int) は四捨五入ではなく、小数点以下を切り捨てます。
「9.9 → 10」にはなりません。

安全な考え方

  • キャストは切り捨て
  • 四捨五入が必要なら、別の方法を使う(要件に応じた丸め処理)

9.2 数値型:整数同士の割り算で小数が出ると思い込む

失敗例:2.5になると思ったのに2になる

int a = 5;
int b = 2;

System.out.println(a / b); // 2

int / intint なので、小数が消えます

修正例:片方を double にする

int a = 5;
int b = 2;

System.out.println((double) a / b); // 2.5

9.3 数値型:大きい数を int に入れても大丈夫だと思う(オーバーフロー)

失敗例:値が壊れる

long l = 3_000_000_000L;
int i = (int) l;

System.out.println(i); // 想定外の値

int の範囲を超えると、キャストで ビットが切り詰められて別の値になります。

対処の考え方

  • そもそも int に入れる必要があるか?
  • 入れるなら「範囲チェック」を先にする

9.4 参照型:ダウンキャストして落ちる(ClassCastException)

失敗例:実体が違うのにキャストする

class Animal {}
class Dog extends Animal {}

public class Main {
    public static void main(String[] args) {
        Animal a = new Animal();
        Dog d = (Dog) a; // ClassCastException
    }
}

これは コンパイルは通るが実行時に落ちる典型例です。

修正例:instanceof で守る

Animal a = new Animal();

if (a instanceof Dog) {
    Dog d = (Dog) a;
    // ...
} else {
    System.out.println("Dogではないのでキャストしない");
}

9.5 参照型:アップキャストしたら子のメソッドが呼べなくなって焦る

失敗例:Dogなのにbarkできない?

class Animal {}
class Dog extends Animal {
    void bark() {
        System.out.println("bark");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal a = new Dog();
        // a.bark(); // コンパイルエラー
    }
}

変数の型が Animal なので、Animal に存在しないメソッドは呼べません。

対処の考え方

  • 本当に必要ならダウンキャスト
  • ただし設計で回避できるなら、多態性に寄せる

9.6 ジェネリクス:unchecked cast を無視して実行時に崩れる

失敗例:raw type から危険なキャスト

import java.util.*;

public class Main {
    public static void main(String[] args) {
        List list = new ArrayList(); // raw type
        list.add("Hello");

        List<Integer> numbers = (List<Integer>) list; // unchecked cast 警告
        Integer x = numbers.get(0); // 実行時に落ちる可能性
        System.out.println(x);
    }
}

この例は、List<Integer> だと信じ込んでいるだけで、実体は List<String> です。

修正例:最初から型を付ける

List<String> list = new ArrayList<>();
list.add("Hello");

String s = list.get(0);
System.out.println(s);

9.7 Integer の比較で == を使ってしまう(値比較の罠)

失敗例:同じ数値なのにfalseになる

Integer a = 1000;
Integer b = 1000;

System.out.println(a == b); // falseになることがある

== は参照比較です。

修正例:equals() を使う

Integer a = 1000;
Integer b = 1000;

System.out.println(a.equals(b)); // true

9.8 Nullのアンボクシングで落ちる(NullPointerException)

失敗例:ただの代入に見えるのに落ちる

Integer x = null;
int y = x; // NullPointerException

アンボクシングで nullint にできないためです。

対処の考え方

  • null の可能性があるなら、プリミティブに直接入れない
  • 先に null チェックをする

10. FAQ(よくある質問)

10.1 Javaのキャスト(型変換)とは何ですか?

キャストは、ある値やオブジェクトを 別の型として扱うための操作です。
数値型キャスト(例:double → int)と参照型キャスト(例:Animal → Dog)で性質が異なります。

10.2 暗黙の型変換と明示的キャストの違いは?

  • 暗黙変換:安全と判断される変換(主に拡大変換)をJavaが自動で行う
  • 明示キャスト:情報落ちの可能性がある変換(縮小変換など)を開発者が指定する

10.3 (int) 3.9 は四捨五入ですか?切り捨てですか?

切り捨てです。
(int) 3.93 になります。

10.4 doubleint にするとき、なぜ注意が必要なのですか?

小数点以下が失われるためです。
また、値が大きい場合はオーバーフローにより想定外の数値になる可能性があります。

10.5 アップキャストとダウンキャストの違いは?

  • アップキャスト(子→親):基本安全で暗黙変換できる
  • ダウンキャスト(親→子):危険で明示キャストが必要、間違えると ClassCastException

10.6 ClassCastExceptionが出る原因と直し方は?

実体の型が、キャスト先の型と一致していないのが原因です。
instanceof でチェックしてからキャストすることで回避できます。

10.7 instanceofはキャスト前に必須ですか?

ダウンキャストする可能性があるなら、基本的には instanceof を推奨します。
また、Javaのパターンマッチングを使うと、より読みやすく書けます。

10.8 unchecked cast警告は無視していいですか?

基本的には無視しない方が安全です。
raw typeをやめ、ジェネリクスを正しく使うのが第一です。
どうしても必要な場合のみ、範囲を最小限にして @SuppressWarnings("unchecked") を使います。

10.9 キャストを多用しない設計にするにはどうすればいいですか?

多態性(ポリモーフィズム)やインタフェース設計を活用し、
「型分岐してキャストする」のではなく「共通のメソッドを呼ぶ」形に寄せるのが基本です。