2013年5月14日に開催された「Java Day Tokyo 2013」において参加者の注目を集めたセッションの1つが「帰ってきたJavaパズラー」だ。もともとは本家JavaOneで人気を集めた参加型セッションであり、登壇者が出題するJavaコーディングに関するクイズに会場の参加者全員が答えていくというスタイルで進行する。果たして同セッションでは、どのような問題が出題されたのだろうか。その一部を紹介しよう。(川添貴生)
クイズの内容は、提示されたコードを実行した結果がどうなるのかを4択から選択するというものであり、すべての設問に"Java SE 8"などのバージョン指定がある。参加者は短い時間でコードの内容を読み取り、実行結果を予想しなければならない。
それでは、具体的にどのような問題が出題されたのだろうか。以下に当日の出題から2つを抜粋して紹介しよう。
このコードでは、Java SE 8で新たに導入されるラムダ式が使われている。内容は、純資産と総資産、負債の値が納められた2つのコレクションをストリームに変換して、並列実行を許可しつつ、フィルタを使って条件に合致するストリームを取り出してカウントし、最後にカウント数を出力するというものだ。このコードを実行して得られる結果は、果たして「0」、「1」、「2」、「不定」のどれだろうか。
ここでのポイントは、クラスBigDecimalの値の扱いである。最初のコレクションでは純資産として「"100"」が代入されており、総資産と負債を足した値は「100.00」になっている。次のコレクションは純資産が「"10000"」、総資産と負債を加算した結果は「10000」となる。
実は値の比較でクラスBigDecimalのメソッドequalsを使用した場合、小数点以下の桁数も比較対象になる。つまり、2つ目のコレクションの純資産「"10000"」と総資産「10000」は等しいと判断されるが、1つ目のコレクションの「"100"」と「100.00」は等しくないと判断されてしまうのだ。
例題では、フィルタの中でメソッドequalsを使っているため、2つ目のコレクションだけが等しいと判定されてカウントアップが行われ、結果として「1」が出力されることになる。ちなみに、メソッドcompareToを使用した場合、桁数を無視して値を比較することができる。
次にコンパイルエラーの可能性を考えてみよう。可能性としてありうるのは、switch文で文字列を条件として指定している部分である。確かにJava SE 6まではswitch文の条件として文字列を使うことはできなかったが、Java SE 7からはそれが可能になっている。したがって、このコードはコンパイルエラーにはならない。
その後にある、引数をシーケンスに追加するメソッドappendは正常に動作するので、nameの内容(applet)と「>」がシーケンスに追加される。これにより、最終的に「applet>」が戻り値となる。
ここで元に戻ってみると、Mapのstatusに「<applet>」はあるが、「applet>」はない。したがって、switch文の条件を「status.get(key)」としているが、対応するstatusがないためnullが返されることになる。
そして、ここが"味噌"なのだが、Javaの仕様ではswitch文の評価結果がnullである場合、例外NullPointerExceptionがスローされてしまう。
つまり、正解は「何も出力されない」ではなく、「例外がスローされる」である。
こうしたchar型からの暗黙変換によるミスは、静的解析ツールや統合開発環境(IDE)の警告をチェックすることなどで発見できるので、積極的に活用してほしい。また、switch文で文字列を利用する際にはnullチェックを行うようにすることも重要だろう。
JavaOneの人気セッションがJava Day Tokyo 2013に登場
「帰ってきたJavaパズラー」は、日本Javaユーザーグループ(JJUG)の協力によって実現したセッションである。グリーの岡崎隆之氏、JJUGの橋本吉治氏、日本オラクルの大渕雅子氏らが次々にクイズを出題し、それに対して参加者が挙手で回答するかたちでセッションは進められた。出題者の大渕氏、岡崎氏、橋本氏(写真左より)
それでは、具体的にどのような問題が出題されたのだろうか。以下に当日の出題から2つを抜粋して紹介しよう。
クラスBigDecimalのメソッドequalsの理解度を試す問題
1つ目は、クラスBigDecimalのメソッドequalsの理解度を試す問題だ。出題されたのは貸借対照表に関するコードで、総資産(equity)と負債(liabilities)を足した額が純資産(asset)と等しければ1カウントされるという内容である。このコードでは、Java SE 8で新たに導入されるラムダ式が使われている。内容は、純資産と総資産、負債の値が納められた2つのコレクションをストリームに変換して、並列実行を許可しつつ、フィルタを使って条件に合致するストリームを取り出してカウントし、最後にカウント数を出力するというものだ。このコードを実行して得られる結果は、果たして「0」、「1」、「2」、「不定」のどれだろうか。
ここでのポイントは、クラスBigDecimalの値の扱いである。最初のコレクションでは純資産として「"100"」が代入されており、総資産と負債を足した値は「100.00」になっている。次のコレクションは純資産が「"10000"」、総資産と負債を加算した結果は「10000」となる。
実は値の比較でクラスBigDecimalのメソッドequalsを使用した場合、小数点以下の桁数も比較対象になる。つまり、2つ目のコレクションの純資産「"10000"」と総資産「10000」は等しいと判断されるが、1つ目のコレクションの「"100"」と「100.00」は等しくないと判断されてしまうのだ。
例題では、フィルタの中でメソッドequalsを使っているため、2つ目のコレクションだけが等しいと判定されてカウントアップが行われ、結果として「1」が出力されることになる。ちなみに、メソッドcompareToを使用した場合、桁数を無視して値を比較することができる。
switch文の盲点を突く問題
次の出題は、HTMLタグのHTML5での対応状況を出力するコードだ。Mapの中には「<Progress>」、「<tt>」、「<applet>」という3つのタグと、それぞれの対応状況(new=新規追加、deprecated=非推奨、removed=廃止)が入っている。その後、switch文で入力された文字列と一致するキーのデータを出力するという流れである。次にコンパイルエラーの可能性を考えてみよう。可能性としてありうるのは、switch文で文字列を条件として指定している部分である。確かにJava SE 6まではswitch文の条件として文字列を使うことはできなかったが、Java SE 7からはそれが可能になっている。したがって、このコードはコンパイルエラーにはならない。
暗黙変換が思わぬトラブルを引き起こす
残る可能性は「何も出力されない」と「例外がスローされる」のいずれかである。ここでメソッドdecorateを見てみると、クラスStringBuilderをインスタンス化している個所で、コンストラクタ呼び出し時にchar型となる文字列(<)を指定している。だが、クラスStringBuilderのコンストラクタにchar型はないので、結果として、文字を持たず、引数で指定された初期容量の文字列ビルダを構築するint型の「99」に変換されることになる。その後にある、引数をシーケンスに追加するメソッドappendは正常に動作するので、nameの内容(applet)と「>」がシーケンスに追加される。これにより、最終的に「applet>」が戻り値となる。
ここで元に戻ってみると、Mapのstatusに「<applet>」はあるが、「applet>」はない。したがって、switch文の条件を「status.get(key)」としているが、対応するstatusがないためnullが返されることになる。
そして、ここが"味噌"なのだが、Javaの仕様ではswitch文の評価結果がnullである場合、例外NullPointerExceptionがスローされてしまう。
つまり、正解は「何も出力されない」ではなく、「例外がスローされる」である。
こうしたchar型からの暗黙変換によるミスは、静的解析ツールや統合開発環境(IDE)の警告をチェックすることなどで発見できるので、積極的に活用してほしい。また、switch文で文字列を利用する際にはnullチェックを行うようにすることも重要だろう。