X

A blog about Oracle Technology Network Japan

クイズに挑戦:Java演算子の使用(中級者向け)

Guest Author

※本記事は、Simon RobertsとMikalai Zaikinによる"Quiz Yourself: Use Java operators (Intermediate)"を翻訳したものです。


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

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

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

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

このJava SEに関する設問では、Javaの演算子、特に条件演算子の使い方について取り上げます。

次のコードについて:

public class Tester {
    
    public static String test(byte b) {
        return "Byte ";
    }
    
    public static String test(char c) {
        return "Char ";
    }
    
    public static String test(int i) {
        return "Int ";
    }
    
    public static void main(String[] args) {
        byte b = 0;
        char c = 'A';
        System.out.print(test(true  ? b : c));
        System.out.print(test(false ? b : c));
        System.out.print(test(true  ? 0 : 'A'));
        System.out.print(test(false ? 'A' : (byte)0));
    }
}
   
A. Byte Char Int Byte
B. Int Int Int Int
C. Int Int Char Int
D. Int Int Char Byte

 

解答:この設問は、試験対象の「Javaの演算子の使用」に分類されるでしょうが、変数の初期化の問題に密接に関連した知識とも関係があります。というのも、リテラル式が登場するコンテキストによってその実質的な型がどう変わる可能性があるのかについても知っていなければ、設問の解答にはたどりつけないからです。他にも、いくつか注意しなければならないことがあります。

  • おそらく、この設問は実際の試験問題よりもかなり難しいものです。
  • 実際にこのように書かれたコードを目にした場合、ほぼ確実に、もう少しわかりやすい形へと書き換えるはずです。この点については、後ほど詳しく説明します。

条件演算子(三項演算子と呼ばれることもあります)になじみがない方のために、簡単に解説します。この演算子は、3つの式を受け取ります。最初の2つはクエリー(?)で、2つ目と3つ目はコロン(:)で区切られています。これによってできるのは、if/elseの効果を持つ式です。つまり、次のような式では、式aがboolean(またはBoolean)型である必要があります。

a ? b : c

aの値がtrueである場合、式全体の値はbに、そうでない場合はcになります。ifとelseの両方で同じ変数に違う値を1回代入するif/elseがある場合、条件演算子は特に便利です。

つまり、次の式

String message = isMorning(theTime) ? "Good morning" : " Good afternoon"

は、次のコードと同じ効果を持ちます。

String message;
if (isMorning(theTime)) {
  message = "Good morning";
} else { 
  message = "Good afternoon";
}

この使用例からわかるのは、選ばれる可能性がある2つの値の両方に、結果を代入する変数との代入互換性が必要であることです。確かに、より一般的な場合を考えれば、2つの型には何らかの共通性があるはずです(共通しているのがObjectのみの場合も含みます)。また、スタイルの観点から言えば、2つ目と3つ目のオペランドは同じ型にするのが賢明でしょう。引数がオブジェクト型である場合、自然とそうなります(上記の例はそうなっています)。しかし、プリミティブの場合は、2つ目と3つ目の場所に登場する型が異なる可能性が高くなるかもしれません。式の中でプリミティブが混在している場合、昇格によって最終的な結果が変わることもあります。

背景の説明はこのくらいで十分です。次は、設問の詳細を見てみることにします。ここで、考察が必要になるかもしれない点がいくつかあります。

  • 条件演算子のboolean引数は条件式全体の型にそもそも影響するのか
  • どのようにしてオペランドの型から条件演算子の型を決めることができるのか
  • 異なるプリミティブ引数型でオーバーロードされたメソッドが複数存在する中から、呼び出されるメソッド1つはどのようにして選ばれるのか
  • コンテキストの中でリテラル値の型はどのようにして決まるのか

 

当然ながら、Javaには、演算子に渡されるオペランドの型の組み合わせについて、その結果の厳密な型(つまり代入互換性)を決める方法を定めたルールがあります。通常、このルールは十分シンプルなものですが、条件演算子には一般的な場合とは若干異なる特殊なルールが定められています。条件演算子とそのルールは、Java言語仕様、Java SE 11版のセクション15.25に詳しく書かれています。

この説明は気が遠くなるほど長いですが、条件演算子の結果の型はBoolean条件の値には影響されないと書かれています。これは、Boolean条件が定数であるため、コンパイラがわかっている場合にも当てはまります。そのため、コードのBooleanリテラルは無視して問題なく、2つ目と3つ目のオペランドの組み合わせにのみ注目すればよいことになります。

次に、条件式の型についての疑問は置いておき、オーバーロードされたメソッドと、どのような条件でどれが呼び出されるかについて考えてみます。出力、つまり正解は、4回のprint呼出しのそれぞれでどのメソッドが呼び出されるのかに大きくかかっています。ありがたいことに、このコンテキストでは、オーバーロードされたメソッドの中から、呼び出すメソッドを選ぶルールもかなりシンプルに表現できます。この問題の最初のポイントは、オーバーロードされたメソッドが存在する場合、ターゲットはコンパイル時に選ばれることです。2つ目のポイントは、簡単にまとめれば、引数型がもっとも適合するものが選ばれてターゲット・メソッドになると言えることです。それでは、次のような1つのメソッドがある場合について考えてみます。

void doStuff(int x) {}

上記のメソッドは、次に示す形式の呼出しの有効なターゲットです。

short s = 99;
x.doStuff(s);

この場合、shortの引数はintに昇格することになります。ここで、次のような2つ目のメソッドを追加するとします。

void doStuff(short x) {}

すると、1つ目のメソッドよりもこの2つ目のメソッドが優先されて選ばれることになります。short型の引数sはintに昇格可能ですが、厳密に同じ型の引数をとるメソッドを選んだ方が適合度は高いからです。別の状況では、必要とする、引数の型の変更が最低限で済むメソッドが選ばれる場合もあります。

オーバーロードされた複数のメソッドから1つを選ぶ方法の詳細については、Java言語仕様、Java SE 11版のセクション15.12.2にすべて書かれています。このように細かい部分も重要です。しかし、今回のメソッドにはジェネリクスや可変長引数リスト、オートボクシングやアンボクシングは使われていないため、この設問では、複雑になるそのような材料には触れません。

ここまでの説明で、オーバーロードされたtestメソッドのうちどれが呼び出されるか(したがって、どのメッセージが出力されるのか)を決定するために必要となる極めて重要な情報は、4つの条件式の型のみであることがわかっています。

4つの条件式のオペランドは、次のとおりです。

  • 1つ目:byte変数とchar変数
  • 2つ目:byte変数とchar変数
  • 3つ目:int型の定数式とcharリテラル
  • 4つ目:charリテラルとbyte式

前述のように、これらの組み合わせ方を定めたルールがあります。今回の場合は、広く適用できるルールと、条件演算子にのみ適用される特殊なルールが存在します。まずは、一般的なルールについて確認します。

Java言語仕様、Java SE 11版のセクション5.6.2(箇条書きの2番)に、二項演算子には「プリミティブの拡大変換」のルールが適用されると書かれています(条件演算子は二項演算子ではなく、三項演算子であることに注意してください。そのため、今回の設問で重要になるポイントすべてがこのルールに記述されているわけではないことがわかります)。このルールはかなりシンプルで、次のことが述べられています。

  • いずれかのオペランドがdouble型であれば、もう片方はdoubleに変換される
  • そうでない場合、いずれかのオペランドがfloat型であれば、もう片方はfloatに変換される
  • そうでない場合、いずれかのオペランドがlong型であれば、もう片方はlongに変換される
  • そうでない場合、両方のオペランドがintに変換される

条件演算子に適用されるのが上記のルールであるとすれば、4つ目のルールが設問の4つの式すべてに当てはまることは明らかです。その結果、出力はInt Int Int Intになるでしょう。しかし、適用されるルールはこれだけではありません。

条件演算子に適用される正確なルール・セットは、Java言語仕様、Java SE 11版のセクション15.25に書かれており、多くのページにわたっています。設問の1つ目、2つ目、4つ目の式を吟味すれば、これらの組み合わせに対して示されているルールは、実際には先ほど説明した動作と同じになることがわかります。仕様には、型はbnp(byte,char)の結果となると書かれています。

bnp(二項数値昇格という意味です)を評価するとintが返され、その結果、1つ目、2つ目、4つ目の式でテキストIntが出力されます。

続いて、3つ目の組み合わせについて吟味してみます。この場合、条件式のルールが「最低でもint」になるとは限らないことと、リテラル式に関するルールから、この式の結果の型は実際にはcharになることがわかります。これは、Java言語仕様、Java SE 11版の表15.25-Aとその補足説明から判断できます。この表で、intリテラルとcharの組み合わせの効果を示す欄に、その結果の型はchar | bnp(int,char)であると記載されています。つまり、リテラルがcharのデータ・サイズに収まる場合、結果はcharになります。そうでない場合は、通常どおり昇格が行われた結果(つまりint)になります。厳密に言えば、このルールは「int型の定数式」に適用されます。リテラルと定数式はまったく同じではありません。定数式の場合、コンパイラが完全に評価できるものであれば、追加の式が含まれていても構いません。

この要素のintリテラルはゼロであり、charで表現できる範囲内に完全に収まるため、結果の型はcharになります。また、全体の出力はInt Int Char Intになり、選択肢Cが正解となります。

4つ目の式にも、同じロジックが適用されるように見えるかもしれません。しかし、この場合、キャスト式(byte)0はリテラルの扱いにはなりません。単なるbyte型の式として扱われます。その結果、通常の二項昇格が行われ、結果の型はintになります。

注目すべきもう1つのポイントは、リテラル値と型の間のやや特殊な関係がどのようになっているかです。これがもっともよくわかるのは、変数の宣言と同時に初期化を行う場合です。たとえば、単純なルールしかない(Java 1.0当時はそうでした)とすれば、次のコードは無効でしょう。

short x = 1; // Java 1.0では無効、現在は有効

これが無効なのは、1はintリテラルであり、intにはshortとの代入互換性がないからです。しかし、Javaはすばやく変更され、ターゲット変数でリテラル値を問題なく表現できる場合、この代入(およびこれに類似した代入)が認められるようになりました。同じように興味深いと思われるのは、float変数の初期化にはこれが適用されないことです。そのため、次のコードは(まだ)無効です。

float pi = 3.14;

この初期化が無効なのは、浮動小数点形式のリテラルがdoubleであり、floatではないからです。この場合、引数が範囲内に収まるかどうかは関係ありません。

正解は選択肢Cです。


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版にわたってテクニカル・レビューを務めた。

Be the first to comment

Comments ( 0 )
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.