※本記事は、Simon RobertsとMikalai Zaikinによる”Quiz Yourself: Creating and Invoking Overloaded Methods (Intermediate)“を翻訳したものです。


条件演算子の使い方を理解するためのJava SE問題

著者:Simon Roberts、Mikalai Zaikin
2020年4月28日 | 本記事をPDFでダウンロード

その他の設問はこちらから

過去にこのクイズの設問に挑戦したことがある方なら、どれ1つとして簡単な問題はないことをご存じでしょう。クイズの設問は、認定試験の中でも難しいものに合わせています。「中級者向け」「上級者向け」という指定は、設問の難易度ではなく、対応する試験による分類です。しかし、ほとんどすべての場合において、「上級者向け」の方が難しくなります。設問は認定試験対策として作成しており、認定試験と同じルールの適用を意図しています。文章は文字どおりに解釈してください。回答者を引っかけようとする設問ではなく、率直に言語の詳細な知識を試すものだと考えてください。

このJava SEに関する設問では、オーバーロードされたメソッドの作成と呼出しを取り上げます。

次のクラスについて:

public class Logger {
    static void log(Object o)   { /* code */ }  // line n1
    static void log(Long[] lwa) { /* code */ }  // line n2
    
    public static void main(String[] args) {
        log(new Integer[]{});
        long[] arr = null;
        log(arr);
        log(null);
    }
}

 

それぞれの変更が独立して行われると仮定した場合、正しい文はどれですか。2つ選んでください。

A. このコードのコンパイルは成功する
B. 次のメソッドを新しく追加すれば、このコードはコンパイルできるstatic void log(Integer[] iwa) { /* code */ }
C. line n1をコメント・アウトすれば、このコードはコンパイルできる
D. line n2をコメント・アウトすれば、このコードはコンパイルできる

 

解答:選択肢Aは、何もしなくてもコードはコンパイルできることを意味しています。そうなるためには、2つのオーバーロードされたメソッドが有効なオーバーロードであることと、すべての呼出しが曖昧さを残すことなく、いずれかのオーバーロードに解決されることが必要です。当然ながら、その他の構文的な問題があってはなりません。

「その他の構文的な問題」に関して言えば、このコードに問題はありません。次に、前述の条件の1つ目について検討します。2つのオーバーロードされたメソッドは、引数リストの型シーケンスが異なっています。片方は1つのObject、もう片方は1つの配列です。この違いがあれば、これらのオーバーロードは十分に共存できます。このルールは、2つの場所にまたがって記載されています。

Java言語仕様のセクション8.4.9「オーバーロード」には、「同じ名前でシグネチャが異なり、オーバーライド等価でない2つのメソッドがクラス内に存在する場合、メソッド名がオーバーロードされていると言う」と書かれています。また、Java言語仕様のセクション8.4.2「メソッドのシグネチャ」では、オーバーライド等価性が説明されています。

この説明は多少回りくどいですが、次のように言い換えることができます。ジェネリクス型パラメータの型消去後の型シーケンスが同じである場合、メソッドはオーバーライド等価です。今回の場合、ジェネリクスは使われておらず、型(ObjectとLongラッパーの配列)は明らかに違うものであるため、オーバーロード自体は有効です。

2つ目のポイントは、オーバーロードされたメソッドの呼出しの曖昧性排除に関することです。この点についても、コードは有効です。ただし、細かい部分について言えば、すべてが予想どおりに動作するというわけではないかもしれません。どの呼出しによってどのメソッドが呼び出されるのかについて考えてみます。そのために、Java言語仕様のセクション15.12.2「コンパイル時の手順2:メソッド・シグネチャの決定」を参照します。ここには、オーバーロードの解決について書かれています。

まず、log(new Integer[]{})という呼出しは、Longの配列を受け取るメソッドに一致することはありませんが、Object引数型には一致します。intはlongに代入できることから、Long[]引数のメソッドが呼び出されると考える方もいるかもしれません。しかし、2つの理由により、そうはなりません。IntegerはLongに代入できないため、この呼出しはうまくいきません。なぜなら、これらのクラスは兄弟クラスであり、親子関係はないからです。しかし、本当の理由は、Javaはさまざまなプリミティブ変換ができるものの、配列の内容の型をこのような形で変更することはできない点にあります。

現在のところ、Javaでは配列のオートボクシングが行われません。そのため、2回目の呼出しであるlog(arr)でも、Objectのパラメータを持つメソッドが選ばれます。

次に、log(null)という呼出しについて見てみます。これは特殊なケースで、驚きに値するかもしれません。コンパイラでは、オーバーロードされたメソッドの中から、コール元の引数型(この場合はnull)を受け取るものを探します。次に、条件を満たすメソッドが複数見つかった場合、コンパイラでは「もっとも具体的な」ものを探します。今回の場合、両方のメソッドが該当します(nullは、プリミティブ以外のどんな型にも適合します)。しかし、Long[]の方が具体的であるため、Object引数よりも優先されます。Objectはすべてのスーパータイプであることから、存在し得るものの中でもっとも一般的な形式であることを覚えておいてください。

結論として、選択肢Aは正解となります。コードはこのままで本当にコンパイルできます。

選択肢Bでは、Integer[]型を引数として受け取る新しいメソッドを追加しようとしています。この追加自体は問題ありません。log(new Integer[]{});という呼出しが、厳密に一致したターゲットを持つようになります。しかし、log(null)が、具体性の点で等しい2つのターゲット・メソッドに一致するようになるため、この行でコンパイルに失敗します(いずれもObjectよりは具体的ですが、両者の具体性に差があるわけではありません)。したがって、コンパイラでは2つのメソッドから1つを選ぶことができないため、メソッド追加後のコードはコンパイルできません。

オーバーロードされたメソッドの宣言が有効でも、呼出しが曖昧であれば、その呼出しはコンパイルできないと覚えておくことが重要です。このことに特に驚いてしまうのは、新しいメソッド1つの追加によって、それまで有効だったメソッド呼出しが有効でなくなる場合でしょう。したがって、選択肢Bは誤りです。

選択肢Cは、log(Object o)メソッドの削除を意味しています。削除した場合、引数がlong[]とInteger[]であるlogの呼出しの有効なターゲットがなくなってしまいます。これらの呼出し先がなくなってしまうため、この変更により、コードはコンパイルできなくなります。以上より、選択肢Cは誤りであることがわかります。

選択肢Dは、log(Long[] lwa)の実装のコメント・アウトを意味しています。コメント・アウトした場合、3回の呼出しのターゲットすべてがlog(Object o)メソッドになります。java.lang.Objectには、nullリテラルを含め、プリミティブ以外のすべてを代入できるからです。この点から、変更後のコードはコンパイルできるため、選択肢Dは正解です。

オーバーロードされたメソッドは、常にコンパイラが引数の型に基づいて選択します。引数の値(nullになる可能性があります)は重要ではありません。そのため、次の2つの呼出しでは、確実に同じターゲット・メソッドが選ばれるでしょう。ただし、log(null)の呼出しでは、違うターゲットが呼び出されるかもしれません。

long[] arr1 = null;
log(arr1);
long[] arr2 = new long[]{1L};
log(arr2);

正解は選択肢AとDです。


Simon Roberts

Simon Roberts:Sun Microsystemsがイギリスで初めてJavaの研修を行う少し前にSunに入社し、Sun認定Javaプログラマー試験とSun認定Java開発者試験の作成に携わる。複数のJava認定ガイドを執筆し、現在はフリーランスでPearson InformITにおいて録画やライブによるビデオ・トレーニングを行っている(直接またはO’Reilly Safari Books Onlineサービス経由で視聴可能)。OracleのJava認定プロジェクトにも継続的に関わっている。

 

Mikalai Zaikin

Mikalai Zaikin:ベラルーシのミンスクを拠点とするIBA IT ParkのリードJava開発者。OracleによるJava認定試験の作成に携わるとともに、複数のJava認定教科書のテクニカル・レビューを行っている。Kathy Sierra氏とBert Bates氏による有名な学習ガイド『Sun Certified Programmer for Java』では、3版にわたってテクニカル・レビューを務めた。