【Java】基本型と参照型の使い分け|intとIntegerの違い・null安全な考え方

Javaを勉強していると、int と Integer のように、似たような型が出てきます。

例えば以下のような型です。

  • int と Integer
  • long と Long
  • double と Double
  • boolean と Boolean

最初は「どちらを使えばよいのか?」と迷いやすいポイントです。

この記事では、Javaの基本型と参照型の違い、実務での使い分け、nullチェックが必要になる場面についてまとめます。

スポンサーリンク

※このページにはプロモーションが含まれています。当サイトは各種アフィリエイトプログラムから一定の収益を得ています。

基本型と参照型の違い

Javaの型は、大きく分けると以下の2種類があります。

  • 基本型・・・int、long、double、boolean など
  • 参照型・・・Integer、Long、Double、Boolean、String、クラス など

ざっくり言うと、基本型は値そのものを扱う型です。

一方、参照型はオブジェクトを扱う型です。

まずは、以下の違いを押さえておくと分かりやすいです。

分類null主な用途
基本型int入れられない必ず値がある数値
参照型Integer入れられる未入力・未設定を表したい数値

よく使う基本型と参照型

  • int・・・整数
  • Integer・・・nullを扱える整数
  • long・・・大きい整数
  • Long・・・nullを扱える大きい整数
  • double・・・小数
  • Double・・・nullを扱える小数
  • boolean・・・true / false
  • Boolean・・・true / false / null

intとIntegerの違い

まずは、一番よく迷う int と Integer の違いです。

int は基本型です。

// intは基本型
int age = 20;

Integer は参照型です。

// Integerは参照型
Integer age = 20;

大きな違いは、Integer は null を入れられることです。

// intにnullは入れられない
int age1 = null; // コンパイルエラー

// Integerにはnullを入れられる
Integer age2 = null;

基本方針

実務での基本方針は以下です。

  • 値が必ずあるなら基本型を使う
  • 未入力・未設定・DBのNULLを表したいなら参照型を使う
  • 計算用の一時変数は基本型を使う
  • ListやMapなどのコレクションでは参照型を使う

特に int と Integer では、以下のように考えると分かりやすいです。

場面使う型
必ず値があるint
未入力の可能性があるInteger
計算用のカウンタint
画面入力値Integer
DBのNULL許可カラムInteger
Listに入れる数値Integer

値が必ずある場合はintを使う

例えば、ループのカウンタや計算用の変数など、値が必ず存在する場合は int を使います。

// カウンタ
int count = 0;

// 合計値
int total = 0;

// 繰り返し処理のインデックス
for (int i = 0; i < 10; i++) {
    System.out.println(i);
}

このような値は、通常 null である必要がありません。

そのため、Integer にするより int の方がシンプルです。

未入力や未設定を表したい場合はIntegerを使う

画面入力値などで、未入力の可能性がある場合は Integer を使うことが多いです。

public class UserRequest {

    // 画面で未入力の可能性がある場合
    private Integer age;

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

例えば年齢が未入力の場合、Integer であれば null として扱えます。

一方、int にしてしまうと、未入力を 0 と区別しにくくなります。

// intの場合、未入力なのか0なのか判断しにくい
int age = 0;

業務的に「未入力」と「0」は意味が違うことがあります。

その場合は、Integer を使って null を表現する方が自然です。

画面入力値はJava側でもチェックする

画面入力値について、JavaScriptでチェックしている場合でも、Java側でチェックするのが基本です。

JavaScriptのチェックは、あくまでユーザー向けの事前チェックです。

ブラウザの開発者ツールやAPIクライアントを使えば、画面を通さずにリクエストを送ることもできます。

そのため、Java側でも最終チェックを行います。

public void register(UserRequest request) {

    Integer age = request.getAge();

    if (age == null) {
        throw new IllegalArgumentException("年齢は必須です");
    }

    int ageValue = age;

    // ここから先はintとして計算できる
    System.out.println(ageValue + 1);
}

重要なのは、Integer をそのまま何となく計算しないことです。

Integerをそのまま計算するとNullPointerExceptionになることがある

Integer は null になる可能性があります。

そのため、以下のようなコードは危険です。

Integer quantity = null;

// quantityがnullの場合、NullPointerExceptionになる
int total = quantity * 100;

一見すると普通の計算に見えますが、Integer を計算に使うと、Javaが内部的に int へ変換します。

この変換をアンボクシングといいます。

quantity が null の場合、int に変換できないため、NullPointerException が発生します。

計算前にnullの扱いを決める

画面入力値を計算に使う場合、まず null の扱いを決めます。

  • 未入力ならエラーにする
  • 未入力なら処理をスキップする
  • 未入力なら0として扱う
  • 未入力なら既存値を使う

この判断は、文法ではなく業務ルールです。

未入力ならエラーにする場合

必須入力の項目であれば、未入力の場合はエラーにします。

Integer quantity = request.getQuantity();

if (quantity == null) {
    throw new IllegalArgumentException("数量は必須です");
}

int quantityValue = quantity;
int total = quantityValue * 100;

未入力なら処理をスキップする場合

任意入力で、入力されている場合だけ処理したいこともあります。

Integer point = request.getPoint();

if (point == null) {
    // 未入力の場合は何もしない
    return;
}

int addPoint = point + 100;
System.out.println(addPoint);

未入力なら0として扱う場合

未入力を0として扱ってよい場合は、計算前に0へ変換します。

Integer discount = request.getDiscount();

// nullの場合は0として扱う
int discountValue = discount == null ? 0 : discount;

int total = price - discountValue;

ただし、何でも null を 0 にすればよいわけではありません。

業務的に「未入力」と「0」が同じ意味である場合だけ、この方法を使います。

Spring BootではDTOでバリデーションする

Spring Bootを使っている場合、入力チェックはDTOで行うことが多いです。

例えば、数量が必須で1以上の場合は以下のように書けます。

import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;

public class OrderRequest {

    @NotNull(message = "数量は必須です")
    @Min(value = 1, message = "数量は1以上で入力してください")
    private Integer quantity;

    public Integer getQuantity() {
        return quantity;
    }

    public void setQuantity(Integer quantity) {
        this.quantity = quantity;
    }
}

Controller側では、@Valid または @Validated を使います。

import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

@PostMapping("/orders")
public void createOrder(@Valid @RequestBody OrderRequest request) {

    // @NotNullにより、ここに来た時点でquantityはnullではない前提にできる
    int quantity = request.getQuantity();

    int total = quantity * 100;
}

ただし、Serviceが他の処理からも呼ばれる場合は、Service側でも重要な条件をチェックすることがあります。

EntityではDBのNULL許可に合わせる

Entityでは、DBのカラムがNULLを許可するかどうかに合わせて型を決めることが多いです。

DBのカラムがNULLを許可する場合、Java側は Integer にします。

// DBでNULL許可されている場合
private Integer age;

DBのカラムがNOT NULLで、必ず値が入る設計であれば、用途によっては int でも問題ありません。

// DBでNOT NULL、かつ必ず値がある場合
private int quantity;

ただし、Entityではフレームワークとの相性や未設定状態を扱いたい都合で、ラッパークラスを使う設計もあります。

ListやMapではIntegerを使う

Javaのコレクションでは、基本型を直接使えません。

そのため、List や Map では Integer などの参照型を使います。

// List<int> は書けない
List<int> numbers = new ArrayList<>(); // コンパイルエラー

// Integerを使う
List<Integer> numbers = new ArrayList<>();
numbers.add(1);
numbers.add(2);
numbers.add(3);

Map の場合も同じです。

Map<String, Integer> scoreMap = new HashMap<>();

scoreMap.put("Taro", 80);
scoreMap.put("Jiro", 90);

Integerの比較は注意

int 同士の比較であれば、== で問題ありません。

int a = 100;
int b = 100;

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

一方、Integer は参照型です。

値の比較では、基本的に equals を使います。

Integer a = 1000;
Integer b = 1000;

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

ただし、a が null の場合、a.equals(b) は NullPointerException になります。

nullの可能性がある場合は、Objects.equals を使うと安全です。

import java.util.Objects;

Integer a = null;
Integer b = 1000;

System.out.println(Objects.equals(a, b)); // false

Objects.equals は、どちらかが null でも安全に比較できます。

Integerを==で比較しない方がよい

Integer を == で比較すると、意図しない結果になることがあります。

Integer a = 1000;
Integer b = 1000;

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

== は、値が同じかどうかではなく、同じオブジェクトかどうかを見ます。

そのため、Integer の値比較では Objects.equals を使う方が安全です。

if (Objects.equals(a, b)) {
    System.out.println("同じ値です");
}

booleanとBooleanの使い分け

boolean と Boolean も考え方は同じです。

boolean は true か false のどちらかです。

boolean active = true;
boolean deleted = false;

Boolean は true、false、null を扱えます。

Boolean agreed = null;

例えば、以下の3状態を区別したい場合は Boolean が使えます。

  • 同意する
  • 同意しない
  • 未回答

一方、単純に有効・無効だけを表したい場合は boolean で十分です。

// 有効・無効の2状態でよいならboolean
private boolean active;

Booleanのif文は注意

Boolean は null になる可能性があるため、そのままif文で使うと危険です。

Boolean active = null;

// activeがnullの場合、NullPointerExceptionになる
if (active) {
    System.out.println("有効です");
}

nullの可能性がある場合は、以下のように書くと安全です。

Boolean active = null;

if (Boolean.TRUE.equals(active)) {
    System.out.println("有効です");
}

Boolean.TRUE.equals(active) と書くことで、active が null でも安全に判定できます。

longとLongの使い分け

long と Long も、int と Integer と同じ考え方です。

必ず値があるなら long、未設定やDBのNULLを扱いたいなら Long を使います。

// 必ず値がある場合
long count = 100L;

// 未設定の可能性がある場合
Long userId = null;

ID項目では Long を使うことも多いです。

例えば、新規登録前のデータは、まだIDが発行されていない場合があります。

public class User {

    // 新規登録前はnullの可能性がある
    private Long id;

    private String name;
}

doubleとDoubleの使い分け

double と Double も、nullを扱うかどうかで使い分けます。

// 必ず値がある小数
double rate = 0.1;

// 未入力の可能性がある小数
Double inputRate = null;

ただし、金額計算では double や Double は基本的に避けます。

小数の誤差が発生する可能性があるためです。

System.out.println(0.1 + 0.2); // 0.3ぴったりにならないことがある

金額を扱う場合は、BigDecimal を使うことが多いです。

BigDecimal price = new BigDecimal("100.50");
BigDecimal tax = new BigDecimal("1.10");

BigDecimal total = price.multiply(tax);

BigDecimal を作るときは、基本的に文字列から作るのが安全です。

オートボクシングとアンボクシング

Javaでは、基本型とラッパークラスの間で自動変換が行われることがあります。

基本型からラッパークラスへの変換をオートボクシングといいます。

// intからIntegerへ自動変換
Integer count = 10;

ラッパークラスから基本型への変換をアンボクシングといいます。

// Integerからintへ自動変換
Integer count = 10;
int countValue = count;

便利な仕組みですが、null の場合は注意が必要です。

Integer count = null;

// nullをintに変換しようとしてNullPointerExceptionになる
int countValue = count;

使い分けの判断基準

迷ったときは、以下の順番で考えると分かりやすいです。

  • nullを表したいか?
  • 画面入力で未入力の可能性があるか?
  • DBでNULLを許可しているか?
  • ListやMapに入れるか?
  • 計算用の一時変数か?

判断を表にすると、以下のようになります。

場面使う型理由
計算用の一時変数intnullが不要
ループのカウンタintnullが不要
画面の任意入力Integer未入力をnullで表せる
画面の必須入力Integer + @NotNull入力チェック前はnullの可能性がある
DBのNULL許可カラムIntegerDBのNULLを表せる
Listの要素Integerコレクションは参照型を使う
true / falseだけでよいフラグboolean2状態で十分
未回答も表したいフラグBooleantrue / false / nullを扱える

レビュー観点

コードレビューでは、以下のような点を確認するとよいです。

  • nullになる意味がないのにIntegerを使っていないか
  • nullになる可能性があるIntegerをそのまま計算していないか
  • Integerを==で比較していないか
  • Booleanをそのままif文で判定していないか
  • 画面入力値をJava側でチェックしているか
  • 金額計算にdoubleを使っていないか

危険な例

Integer quantity = request.getQuantity();

// quantityがnullの場合、NullPointerExceptionになる
int total = quantity * 100;

改善例

Integer quantity = request.getQuantity();

if (quantity == null) {
    throw new IllegalArgumentException("数量は必須です");
}

int quantityValue = quantity;
int total = quantityValue * 100;

まとめ

Javaの基本型と参照型の使い分けでは、null を扱う必要があるかどうかが重要です。

  • 値が必ずあるなら基本型を使う
  • 未入力・未設定・DBのNULLを表したいなら参照型を使う
  • 計算前にはnullの扱いを決める
  • Integerをそのまま計算するとNullPointerExceptionになることがある
  • ListやMapではIntegerなどの参照型を使う
  • Booleanはtrue / false / nullの3状態を扱える

特に int と Integer では、以下の考え方が基本です。

// 値が必ずあるならint
int count = 0;

// 未入力や未設定を表したいならInteger
Integer age = null;

実務では、単に文法として覚えるのではなく、「この値はnullになる可能性があるか?」「nullの場合はどう扱うべきか?」を考えることが大切です。

関連記事

【Java】チートシート

スポンサーリンク

Java