Integerラッパー・クラスを使って2つの整数のインスタンスを作成したときに、値を比較する正しい方法
著者:Simon Roberts、Mikalai Zaikin
2019年8月26日
過去にこのクイズの設問に挑戦したことがある方なら、どれ1つとして簡単な問題はないことをご存じでしょう。クイズの設問は、認定試験の中でも難しいものに合わせています。「中級者向け」「上級者向け」というレベルは、設問の難易度ではなく、対応する試験による分類です。しかし、ほとんどすべての場合において、「上級者向け」の方が難しくなります。設問は認定試験対策として作成しており、認定試験と同じルールの適用を意図しています。文章は文字どおりに解釈してください。回答者を引っかけようとする設問ではなく、率直に言語の詳細な知識を試すものだと考えてください。
設問(中級者向け):Boolean、Double、Integer、などのラッパー・クラスを使ったコードを作成したいと思います。
次のコード部分について:
String one = "1";
Boolean b1 = Boolean.valueOf(one); // line n1
Integer i1 = new Integer(one);
Integer i2 = 1;
if (b1) {
System.out.print(i1 == i2);
}
どのような結果になりますか。1つ選んでください。
- line n1で実行時例外が発生する
- true
- false
- コードは実行されるが、何も出力されない
解答:この設問は、プリミティブのラッパー・クラス、とりわけBooleanクラスとそのファクトリに関する奇妙な側面について問うものです。この設問で取り上げた内容がそっくりそのまま実際の試験に出題される可能性は低いでしょう。というのも、解けるかどうかは仕様を丸暗記しているか次第であり、試験ではそのような設問は回避されることが多いからです。ただし、この設問では、1つの問題で複数の側面にわたる理解と知識を確認しています。運が良ければ、その点がさらにおもしろいものになります。
ラッパーでは、インスタンスを取得する方法が主に3つあります。各ラッパーでは、valueOfという名前のメソッドで静的ファクトリが提供されています。また、ラッパー型の変数に、型が一致するプリミティブから代入する場合のように、コンテキストが十分明示的であれば、オートボクシングが行われます。オートボクシングは、構文の単なる省略表記で、コンパイラにvalueOfメソッドを呼び出すコードを書かせることができ、ソース・コードが簡潔になります。3つ目のアプローチは、newキーワードを使ってコンストラクタを呼び出すというものです。実は、この3つ目のアプローチはJava 9で非推奨になっています。この設問のねらいの1つは、なぜ非推奨になったのかを問うことにあります。
Javaで、newキーワードを使ってコンストラクタを呼び出すたびに起きる可能性があるのは、2つのことだけです。すなわち、指定されたとおりの名前の型の新しいインスタンスが生成されて返されるか、例外が発生するかのいずれかです。実のところ、これは制約です。現在では、一般的にファクトリ・メソッドが好まれています。これら2つの効果を持たせることができるうえに、追加の結果を提供することもできるからです。
コンストラクタでは不可能で、ファクトリでは可能な機能の1つに、リクエストに沿った既存のオブジェクトを返こすとがあります。Integerラッパーが不変であることを考えれば、同じ数値を表す、この型の2つのオブジェクトは完全に交換可能です。そのため、同じ値を表す2つのオブジェクトを作るのは、メモリの無駄です。さらに、このアプローチでは、このようなオブジェクトをequals(Object o)メソッドではなく==を使って比較できます。Integerクラスでは、通常、-128から+127までの値がこのようにして再利用されます。
(補足ですが、この動作は、new String(“1”)のようにしてStringオブジェクトを作成する代わりに、Stringリテラルを使用することと実質的に同じです。)
ファクトリ・メソッドには有利な点があと2つあります。複数のファクトリを作成する場合、各メソッドを異なる名前にすることができます。つまり、適切であれば、同じ引数型リストを受け取れるということです。コンストラクタでは、互いが有効なオーバーロードである必要があるため、これを行うのは不可能です。
3つ目の有利な点は、コンストラクタでは指定されたとおりの名前の型のオブジェクトを必ず返すということです。ファクトリでは、代入において宣言された型に対応するものであれば、何でも返すことができます(インタフェースの実装も含みます。こうすることにより、実装の詳細をうまく隠すことができます)。
この設問にもっとも関連するのは、Boolean.valueOf(…)を使う場合です。この場合、厳密に2通りの定数オブジェクトを得ることになります。1つはBoolean.TRUE、もう1つはBoolean.FALSEです。この2つは、追加のメモリを占有することなく、必要に応じて何度でも再利用されます。この動作は、newの呼出しでは不可能です。
ところで、ほとんどのラッパーのファクトリは、NULL引数や、作成する型を適切に表していない文字列が渡された場合、例外をスローします。たとえば、Integer.valueOfファクトリを“5”ではなく“five”という引数で呼び出した場合です。ただし、java.lang.Booleanクラスのファクトリは、引数の文字列が存在し、“true”という値(大文字、小文字は区別しません)が含まれているかどうかを確認します。その条件を満たす場合は、値Boolean.TRUEを返します。そうでない場合は、引数についてそれ以上何も知らせることなく、Boolean.FALSEを返します。つまり、NULL引数やテキスト“nonsense”でファクトリを呼び出しても、Boolean.FALSEが返され、例外はスローされません。
そのため、line n1のコードでは、例外はスローされず、変数b1にBoolean.FALSEが代入されると判断できます。そのため、選択肢Aは誤りです。
次は、if文の動作と、そこで行われている比較について吟味する必要があります。
一般的なルールに、if文の条件式はboolean型でなければならないというものがあります。Booleanオブジェクトがアンボクシングされて、そのままboolean型になることは明らかでしょう。ここで疑問を感じたとしたら、コードがコンパイルできないのではということかもしれません。そう考えるのは、おかしなことではありません。Java 5でオートボクシングが導入されるまで、このコードはコンパイルできなかったからです。しかし、そう心配したとしても、その点について触れている選択肢はないため、見た目どおりの動作が起こると考えて問題ありません。
この場合、b1が参照するオブジェクトはfalse値を表していることを確認しました。そのため、ifの条件は満たされず、コードの本体は実行されません。そこから、選択肢Dは正解であると判断できます。
実行されないことはわかりましたが、この議論に関連して、if文の中にあるprint呼出しの引数が評価されるとしたら何が起こるかについて考えることには価値があると思われます。
Javaでは、2つの形態の等価比較を提供しています。1つは、コア言語の一部である==演算子です。もう1つは、実質的にAPI機能と言えるequals(Object o)メソッドです。このメソッドはjava.lang.Objectクラスで定義されているため、すべてのオブジェクトで利用できます。しかし、対象のクラスでこのメソッドが実装されていない場合、有用な動作とならない可能性があります。この2つは、どの場面にいずれを使うかを把握し、正しく使い分けることが重要です。しかし、この設問には登場するのは==演算子だけであるため、こちらを詳しく見てみます。
==演算子では、2つの式の値を比較します。これは一見簡単なように思えますが、それぞれの値で式の基本型が異なる場合、簡単とは言い切れません。==の外見上の効果は2つの型でまったく異なるため、この事実は重要です。ところで、ここでは意図的に「式」という用語を使っています。変数はシンプルな式の1つです。変数の値と型という面から考えてみても、この議論は成り立ちます。ただしその場合、真実の一部だけを捉えていることになります。
式を大きく2つに分ければ、プリミティブ(boolean、byte、short、char、int、long、float、doubleという8つの型のいずれか)と、いわゆる参照となります。参照は、メモリ内の別の場所にあるオブジェクトを見つけるために使う値という意味で、ポインタとよく似ています。
式がプリミティブタイプである場合、実のところ、関心の対象はその式の値です。そのため、あるint式の値が32であれば、事実、その式の値は32をバイナリで表現したものになります。そんなことは当たり前だと言われるかもしれません。問題は、変数がたとえばInteger型への参照タイプであり、その変数が、32という値を含むオブジェクトを参照するために使用されているような場合です。この場合、変数の値は32にはなりません。そうではなく、32を含むオブジェクトをJVMが見つけられるようにするための魔法の数値(参照)になります。つまり、参照タイプ(前述の8つのプリミティブを除いたすべてのものを指すことを思い出してください。したがって、Integer式も含まれます)の場合、==という条件で判定されるのは、2つの式が同じ意味を持つかどうかではなく、厳密に同じオブジェクトを参照しているかどうかです。重要なことに、32という値を含む2つのIntegerオブジェクトがあり、その2つが異なるオブジェクトである場合、それぞれの参照値は異なることになります。そのため、==を使って、それぞれを参照する式を比較すると、結果はfalseになります。
ここまで来れば、次のコードがあった場合、
Integer v1 = new Integer("1");
Integer v2 = new Integer("1");
System.out.print(v1 == v2);
確実にfalseが出力されることは明白でしょう。先ほども触れましたが、newを呼び出すと、指定されたとおりの名前の型の新しいオブジェクトか、または例外が必ず生成されます。つまり、v1とv2は必ず違うオブジェクトを参照します。すなわち、==操作は必ずfalseを返します。
代わりに次のコードがあったとします。
Integer v1 = new Integer("1");
Integer v2 = 1;
System.out.print(v1 == v2);
設問のコードととてもよく似ており、片方ではコンストラクタを呼び出し、もう片方ではアンボクシングを使っています。このコードでも、確実にfalseが出力されます。 コンストラクタで生成されるオブジェクトは一意な新しいオブジェクトであるため、オートボクシングされた値を生成するファクトリから返されるものとは異なります。
多くの場合、不変オブジェクトのファクトリは、同じ引数で呼ばれた場合には毎回同じオブジェクトを返すようにコードが書かれています。IntegerクラスのvalueOf(int)メソッドのAPIドキュメントには、次のように書かれています。
「このメソッドは、-128から127の範囲の値を常にキャッシュしますが、この範囲に含まれないその他の値をキャッシュすることもあります」
言い換えれば、次のコード
Integer v1 = Integer.valueOf(1);
Integer v2 = Integer.valueOf(1);
System.out.print(v1 == v2);
では、確実にtrueが出力されます。
先ほど引用した仕様は、valueOf(int)メソッドのドキュメントにのみ書かれており、valueOf(String)では触れられていません。しかし実際は、両方のメソッドで同じプーリング動作が見られます。
もちろん、この設問で扱っているのは2つのIntegerオブジェクトです。片方はコンストラクタで生成され、もう片方は(Integer.valueOf(int)メソッドを使って)オートボクシングで生成されています。つまり、if文の本体に入力されたとすれば、出力はfalseになりました。しかし、選択肢Dが正解であることはすでに確定しているため、選択肢BとCは誤りとなります。ここまで説明してきたことは、おもしろい補足にすぎません。もちろん、実際におもしろいと思っていただけると幸いです。正解は選択肢Dです。
Java Magazine December 2019の他の記事
プロパティベース・テストを習得するArquillian:簡単なJakarta EEテスト
ArchUnitでアーキテクチャの単体テストを行う
新しいJava Magazine
作ってみよう:自分だけのテキスト・エディタ(パート1)
クイズに挑戦:Collectorsの使用(上級者向け)
クイズに挑戦:ループ構造の比較(中級者向け)
クイズに挑戦:スレッドとExecutor(上級者向け)
書評:Core Java, 11th Ed. Volumes 1 and 2
![]() |
Simon RobertsSimon Roberts:Sun Microsystemsがイギリスで初めてJavaの研修を行う少し前にSunに入社し、Sun認定Javaプログラマー試験とSun認定Java開発者試験の作成に携わる。複数のJava認定ガイドを執筆し、現在はフリーランスでPearson InformITにおいて録画やライブによるビデオ・トレーニングを行っている(直接またはO’Reilly Safari Books Onlineサービス経由で視聴可能)。OracleのJava認定プロジェクトにも継続的に関わっている。 |
![]() |
Mikalai ZaikinMikalai Zaikin:ベラルーシのミンスクを拠点とするIBA IT ParkのリードJava開発者。OracleによるJava認定試験の作成に携わるとともに、複数のJava認定教科書のテクニカル・レビューを行っている。Kathy Sierra氏とBert Bates氏による有名な学習ガイド『Sun Certified Programmer for Java』では、3版にわたってテクニカル・レビューを務めた。 |


