Javaのボクシングは危険なかほり

バルテック アドベントカレンダー2014 Advent Calendar 2014 - Adventar

 

バルテックアドベントカレンダー2014年の12月6日担当として『Javaのボクシングは危険なかほり』と題して、Javaのボクシング機能のちょっと危険な挙動を簡単に紹介してみたいと思います。。

 

まず、ボクシング、アンボクシングとは

ボクシング・アンボクシングは、プリミティブ型と対応するラッパクラス参照(たとえば int と Integer)を自動的に相互変換するするものです。J2SE1.4までは、プリミティブ型とラッパクラス参照を相互変換するためには、明示的にデータ変換しなければいけませんでした。コレクションにはオブジェクトしか登録できないため、プリミティブ型データをコレクションに登録したりコレクションから取得する際など、毎回データ変換が必要でした。J2SE5.0では、そのような変換が自動的に行われるようになりました。

引用元:TECHSCOREさんから引用させていただきました。

3. オートボクシング | TECHSCORE(テックスコア)

 

要するにこんな感じ

f:id:daisuke6106:20141203233959p:plain

 

ボクシング、アンボクシングの説明にも書いてありますが、よくintなどのプリミティブ型をリストに突っ込んだりするときに使います。

 

ボクシング、アンボクシングを使用した変数での比較に注意

 

この機能はプリミティブか、ラッパーかを意識しなくてすむので便利に思えますが、比較したりするときには注意しなければなりません。

 

例えば以下のように

f:id:daisuke6106:20141204003006p:plain

この挙動は正しく見えます。

 

ただし、変数に入れている値をちょっと変えてみると・・・・

f:id:daisuke6106:20141204003410p:plain

a1、b1に1を足した、a2、b2から1を引いただけなのに比較結果はfalseに変化しました。

 

何故か?

ラッパークラスはあくまでクラスのインスタンスであるため、==比較した場合、値の比較ではなく、通常のクラスと同じように参照場所(メモリ上にあるインスタンスが存在する場所)の比較となります。

(よって、明示的にnew を使って、メモリ上に別々のインスタンスを生成した場合での比較は以下のようにfalseとなります。)

f:id:daisuke6106:20141204001538p:plain

このことからボクシングを使った場合の比較も常に『false』になるのが正しく思えますが、実はIntegerクラスの中で『-128から127』の間のIntegerのインスタンスはメモリ上にキャッシュを持っており、ボクシングを行った場合、『-128から127』の範囲ではこのキャッシュのインスタンスを使用するといった処理になります。

 

なので『-128から127』の間では『true』、その範囲から外れると『false』ということになります。

 

リストから数値を取り出して比較する等ありがちな処理でも、気を付けないとこれによっていらぬバグを混入してしまう場合がありえます。

 

対策

対策としてボクシング・アンボクシングされた値を比較するときには常にプリミティブ型なのかラッパーのクラスなのか意識し、明示的に変換を行って比較を行うということで対処できます。

f:id:daisuke6106:20141204011017p:plain

 

めんどくさいなぁという人は以下の設定をeclipseに盛り込むことでボクシング・アンボクシングを検知できるようになります。

『設定』→『Java』→『コンパイラー』→『エラー/警告』→『ボクシング、及びアンボクシング変換』の項目を『エラー』、もしくは『警告』とすること。 

f:id:daisuke6106:20141204013113p:plain

『エラー』を設定した結果、ボクシングを使用している箇所は赤くなった。

f:id:daisuke6106:20141204013226p:plain

 

以上です!!

 

おまけ① 

アドベントカレンダーの12月5日にてeclipseの小技について書かれてたのでちょっと追記してみたいと思います。

 

WEBアプリケーションを作成している時にデバックするときは、自身のPC上(以下ローカル)のeclipseでサーバを稼働させて、ブレークポイントを設定すればデバックはできます。

 

が、よくビルドしたWEBアプリケーションをローカル以外のサーバにデプロイして、画面の結合試験を行うことがよくありますが(というか普通そうだとと思いますが・・・)、画面が期待値通りの動きをしない、けど原因がわからないからデバックしたい。でもローカル上でないからeclipseでデバックできないという状況がたまにあります。

 

その時に便利なのがeclipseのリモートデバックです。

 

準備

tomcatなどのサーバ起動用スクリプト(startup.shや、startup.bat)にはjavaに渡される引数がこのスクリプト内で渡されていますが、その中に

-Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=8080,suspend=n

という引数が加わるように設定します。

 

そののち、サーバを起動します

weblogicだと管理用画面から設定可能。)

 

デバック実施する場合

eclipseのメニューバーから「デバックの構成」を選択して、「リモートデバック」を選択します。

f:id:daisuke6106:20141206015240p:plain

 

ホストに現在WEBアプリケーションが稼働しているサーバのIPアドレス、ポートに起動スクリプトに記載したポート番号(上記の場合だと『address=8080』の部分)を記入して、「デバック」をすると接続されます。(うまく設定されてないとエラーがでます)

接続できたらeclipseソースコードに対してブレークポイントをおいて、画面を動かすことでデバックができるようになります。

 

 

おまけ②

12月3日にはJMockitについても書かれていたのでまた小ネタを入れてみます。

JMockitにはカバレッジを取得してくれる機能も持っています。

JMokitをダウンロードしてきたら「jmockit-coverage.jar」をクラスパスに含めてあげます。

f:id:daisuke6106:20141206112905p:plain

注意:この時クラスパスの順序はJUnitのライブラリより上にしてください。

f:id:daisuke6106:20141206115122p:plain

 

 

あとは、通常どおりJunitでテストコードを書いてJunitを動かすだけでカバレッジが取得される。

f:id:daisuke6106:20141206114332p:plain

f:id:daisuke6106:20141206114530p:plain

index.htmlを開けば今回のテスト結果のカバレッジが取得されている。

f:id:daisuke6106:20141206114710p:plain

javaファイル名のアンカーを押下すればJUNITでテストされていない箇所を見ることもできる。

f:id:daisuke6106:20141206114910p:plain

大量にクラスがあるときはこんな感じ。パッケージ毎に表示されて便利

f:id:daisuke6106:20141206115352p:plain

 以上です!!!!

 

また、趣味グラミングで作成したモノをGitHubで公開してるんで、興味ある人はcloneして見てくだしい、バグってたらプルリクおなしゃす。


daisuke6106 (Daisuke6106) · GitHub

 

 

次回(アドベントカレンダーとは関係なく、気が向いたら書くかもしれません)!! 

  • RuntimeExceptionと普通のExceptionの違い
  • Serializableとはなんぞや?
  • Javaでリストのソートをするときってどうやるんだっけ?
  • 自分の作ったクラスでequalsを実装するときに守らなければならないこと
  • サーバにデプロイされたプログラムのデバック方法 
  • 現場でよく使う便利なLinuxコマンド集