LoginSignup
0

More than 3 years have passed since last update.

Shortの比較でNullPointerExceptionになって知ったオートボクシング

Last updated at Posted at 2020-04-02

ラッパークラスで値を比較するときはequalsを使う

Stringの比較でよく言われています。
比較で==を使うと
ラッパーなどの参照型の場合は参照先を比較、プリミティブ型の場合は値を比較します。

String hoge = "hoge";
String fuga = "hogehoge";
fuga = fuga.substring(4); // << "hoge"になる

// 参照先を比較するので「false」になる
boolean isEqual1 = hoge == fuga;

// 値を比較するので「true」になる
boolean isEqual2 = hoge.equals(fuga);
System.out.print(isEqual2);

Shortを比較するのに==を使っても「-128~127」の範囲は値を比較してくれる

うっかりShort(shortのラッパー)を==で比較していたけど途中まで気が付かなかった、気が付いたのは値が200ぐらいになった時。へぇー知らなかった・・・とはいえequalsを使うべきですね。

Boolean isEquala = null;
// 「-128~127」の範囲外なので「false」になる
Short a1 = -129;
Short a2 = -129;
isEqual = a1 == a2;
// 「-128~127」の範囲内なので「true」になる
Short b1 = -128;
Short b2 = -128;
isEqual = b1 == b2;
// 「-128~127」の範囲内なので「true」になる
Short c1 = 127;
Short c2 = 127;
isEqual = c1 == c2;
// 「-128~127」の範囲外なので「false」になる
Short d1 = 128;
Short d2 = 128;
isEqual = d1 == d2;

たぶん、Integerと同じ理由だと思われます。

intからIntegerへは「new Integer()」でなく「Integer.valueOf()」が使われるから。
これは(現状では)-128~127の範囲ではキャッシュされたIntegerを返すようになっているので、その範囲なら同一のインスタンスとなる。だから==で比較しても等しくなる。
Java型メモ(Hishidama's Java type Memo)

Shortの比較で==を使っていてもNullPointerExceptionになる

意外だった・・・というか納得できない

Short a = null;
Boolean isOne = null;
// 想像通りにNullPointerExceptionになる
isOne = a.equals(1);
// 想像通りに「true」になる
isOne = a == null;
// 意外にNullPointerExceptionになる!
isOne = a == 1;
// これはNullPointerExceptionにならずに「false」になる
isOne = a == new Short("1");
// これも「false」になる
Integer i = 1;
isOne = a == new Short(i.shortValue());

プリミティブ型とラッパークラスを自動変換するボクシング変換というものがある

JDK1.5からできるようになったそうで、そんな名前があることを知らなかった。

// ラッパークラスに普通の代入
Short a1 = new Short("1000");
// オートボクシング:プリミティブ型をラッパークラスに自動変換
Short a2 = 1000; // classファイルを逆コンパイルするとこうなる >> Short a2 = Short.valueOf((short)1000);

// プリミティブ型に普通の代入
short b1 = a1.shortValue();
// アンボクシング:ラッパークラスをプリミティブ型に自動変換
short b2 = a2; // classファイルを逆コンパイルするとこうなる >> short b2 = a2.shortValue();

ボクシング変換されているかを確かめたくてShortの比較で==を使ったところを逆コンパイルしてみた

ボクシング変換されているかは逆コンパイルするとわかるらしい、だから逆コンパイルしてみた。

コンパイルしたものを逆コンパイルして見ると、ただ単に変換メソッドに置き換えられているだけ…。
Java型メモ(Hishidama's Java type Memo)

isOne = a.equals(1);
// ↑これをコンパイルして逆コンパイルするとこうなる↓
isOne = Boolean.valueOf(a.equals(Integer.valueOf(1)));

isOne = a == null;
// ↑これをコンパイルして逆コンパイルするとこうなる↓
isOne = Boolean.valueOf(a == null);

isOne = a == 1;
// ↑これをコンパイルして逆コンパイルするとこうなる↓
isOne = Boolean.valueOf(a.shortValue() == 1);

isOne = a == new Short("1");
// ↑これをコンパイルして逆コンパイルするとこうなる↓
isOne = Boolean.valueOf(a == new Short("1"));

Integer i = 1;
isOne = a == new Short(i.shortValue());
// ↑これをコンパイルして逆コンパイルするとこうなる↓
Integer i = Integer.valueOf(1);
isOne = Boolean.valueOf(a == new Short(i.shortValue()));

Shortをプリミティブ型と==で比較するとshortValue()が実行されるからNullPointerExceptionになる!

a == 1;
// ↑は、こう↓なる
Boolean.valueOf(a.shortValue() == 1);

NullPointerExceptionになる理由は分かったけどなんでリテラルの数値の時だけshortValue()なんだろう?
a == new Short("1");は、Boolean.valueOf(a.shortValue() == new Short(i.shortValue()));にならないんだろう?

そんな疑問に @swordone さんが教えてくれました。

ラッパークラス(参照型)をプリミティブ型と比較するとオートボクシングが行われるので、
比較対象が参照型であれば.shortValue()は実行されずNullPointerExceptionは発生しない。

// 例えばこれは、比較対象がプリミティブ型なので
boolean isOne = a == (short) 1;
// オートボクシングが実行される(↓逆コンパイルした結果)
boolean isOne = a.shortValue() == 1;

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
0