条件演算子の二項目と三項目が違う型でも動くことがあるやつ

関ジャバ'24 7月度 - connpass のショートセッションで出てきたネタ、「条件演算子の二項目と三項目が違う型でも動くことがあるやつ」の言語仕様解説。

 

// これはいける

System.out.println(i % 3 == 0 ? "Fizz" : i);

// これは無理

String str = (i % 3 == 0 ? "Fizz" : i);

 

この挙動はなぜなのか?

§15.25. Conditional Operator ? :

いわゆる三項演算子 ? : の型の仕様についてはJava言語仕様の §15.25. Conditional Operator ? : を読み解く必要がある。

 

第二項と第三項の型の組み合わせでいろいろあるのだけども、組み合わせによりどういう型になるのかという表が掲載されている。例の場合は"Fizz"がString型であるが、これはまるっとObject型という扱いになって Table 15.25-A. Conditional expression type (Primitive 3rd operand, Part I) より、第二項がObject型で第三項がint型の場合、lub(Object,Integer) 型になると分かる。

 

この lub(Object,Integer) とは何かということなのだけど、 

§4.10.4. Least Upper Bound に記載があって参照型の集合の最小上限(least upper bound = lub)のことである。ふたつの参照型の一番具体的な共通のスーパークラスと思ってもらえば良い。

 

lub(Object,Integer) とあるけども、上記例ではこのObjectの部分はString型だった。ここではString型とInteger型の共通のスーパークラスと考えてもらえば良くて、方便で言えばこれはObject型ということになる。

 

これで System.out.println(Object) が大丈夫で、String str = (i % 3 == 0 ? "Fizz" : i); はダメな理由が分かるだろう。String型の変数にObject型を代入しようとしたらダメなわけである。

なお、System.out はPrintStream型なのでどんなメソッドがあるかは 

PrintStream型のjavadoc を参照すると良い。

 

正確な型

先ほど方便で言えばObject型であると言った。正確なところはどうなのか?

これは Object & Comparable<?> & Constable & ConstantDesc & Serializable型 となる。

 

この & がつく型は §4.9. Intersection Types で解説されており、日本語だと交差型などと呼ばれる。Javaの型システムはclassの単一継承、interfaceの複数実装が可能となっており、ジェネリクスの境界などの指定では&を用いてこのclassを継承しつつ、このinterfaceを実装している型変数 T みたいな指定をすることができる。

 

残念ながら通常の変数宣言では交差型での変数宣言はできないが、メソッドスコープの型変数を型推論させたりvarを用いたりすると明示的ではないが交差型を用いることができる。

 

ここで String型を確認してみよう。javadocを見ると

すべての実装されたインタフェース:

と書かれている。

 

Integerの方も確認してみよう。

すべての実装されたインタフェース:

この共通部分を括りだすと Object & Comparable<?> & Constable & ConstantDesc & Serializable型 になるというわけである。

 

細かいことはさておき、System.out.println(Object) に食わせることができる。

まとめ

正式名称がわからないJavaの仕様も 好き 好き 大好き

 

参考

lub の推論が気になる方はこの記事がおすすめ

yohhoy.hatenadiary.jp