Java7からJava8に移行した際にClassCastException発生 ～ジェネリックスとオーバロード～


------
# 自己紹介
* Terasoluna Server for Javaで社内Webシステム開発（Java7, Java8) : 4年間
    - NTTのオープンソース http://www.terasoluna.jp/product/framework/server_framework_5.html
    
* Spring Bootで社内Webシステム開発：半年
* 4月に転職。Scala勉強中

[名古屋Javaユーザグループ 2018年4月](https://ngo-java.connpass.com/event/82734/)でLTしました！

------
# 実行環境
* Java 1.8.0_162
* Java 1.7.0_80
* Eclipse 4.7.2(Pleiades)


------
# 目次
1. 発生した問題
2. 原因を調査
3. 調査した結果



------
# 1. 発生した問題

------
## 実行するとどうなるでしょうか？

```java:Main.java

public class Main {

	public static void main(String[] args) {
		String.valueOf(hoge());
	}

	static <E> E hoge() {
		return (E) "hoge";
	}

}
```


------
## Java7は正常終了, Java8はClassCastException発生

* Java 1.7.0_80：正常終了
* Java 1.8.0_162：「java.lang.Stringはchar配列にキャストできない」というExceptionが発生

```console:Console
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to [C
	at jp.co.sample.Main.main(Main.java:13)
```

`[C`はchar配列
https://docs.oracle.com/javase/specs/jls/se7/html/jls-10.html#jls-10.8


------
## そもそも、このソースは何？

* 2000年頃に作られたWebシステムの既存コード（発表用にソースは修正した）
* Java8での老朽更新対応で、「Java8のマイナーバージョン違いで、実行結果が異なる」という問題が発生した
    - エラーが起きなかったJava8のマイナーバージョンを忘れてしまったため、本発表ではJava7とJava8で比較した
    

------
## このソースの問題点

* ジェネリックスの使い方が間違っている
    - 型パラメータが不要
* `String.valueOf`の意味がない

本発表では、気にしないでください。


------
# 2. 原因を調査


------
## Eclipseで確認

コンパイラはECJ（Eclipse Java Compiler）でJDKと異なるが、結果は同じだった。


![image](https://qiita-image-store.s3.amazonaws.com/0/37903/cd48c3fa-ac07-aec0-936f-b6894fffc123.png)

------
### Java7ではhogeの型パラメータは`Object`
![Java7](https://qiita-image-store.s3.amazonaws.com/0/37903/84ed321e-7ecf-3396-36a5-c0354a7bec05.png)

------
### Java8ではhogeの型パラメータは`char[]`


![Java8](https://qiita-image-store.s3.amazonaws.com/0/37903/4f8ed465-5332-0b1e-9a18-b5159956b56c.png)

`char[]`は`String.valueOf`から来ている。

------
## `String.valueOf`はオーバロードされている
char配列型 or Object型の引数を受け取れる

* `valueOf(char[] data)`
* `valueOf(Object obj)`

![String.valueOfのオーバロード](https://qiita-image-store.s3.amazonaws.com/0/37903/0f6b851a-cf8e-b87e-9807-be779e31fca3.png)


------
## 状況整理

* ジェネリックスメソッドの結果を、値Aとする
    - 戻り値は仮型パラメータ
    - 引数がないので、実型パラメータが不明
* char配列型 or Object型の引数を受け取れるメソッドに、値Aを渡している


------
### ジェネリックメソッドの確認
* 型パラメータが決まれば（String）、java8でも正常終了する。


```java:MainGenericsMethod.java
public class MainGenericsMethod {

	public static void main(String[] args) {
		String.valueOf(fuga("fuga"));
	}

	/** 引数から型パラメータが決まる */
	static <E> E fuga(E arg) {
		return (E) arg;
	}
}
```

![image](https://qiita-image-store.s3.amazonaws.com/0/37903/0f1f3c42-db04-8afd-2285-4eabf58ba5dc.png)


------
### オーバロードの確認
オーバロードされたメソッドの組み合わせを変えて、Java8で実行した結果。

* Objectと`char[]` : NG
* Objectと`int[]` : NG
* Objectと`char` : OK
* Objectと`int` : OK

Object型 or 配列型を受け取るオーバロードされたメソッドが問題。

```java:MainOverload.java
public class MainOverload {

	public static void main(String[] args) {
		overload(hoge());
	}

	static <E> E hoge() {
		return (E) "hoge";
	}

	static void overload(Object arg) {}
	static void overload(char[] arg) {} //引数の型を変える
}
```

![image](https://qiita-image-store.s3.amazonaws.com/0/37903/b67a2fb2-214d-dc9f-0584-2fc7b77e3a40.png)

----
# 3. 調査した結果


----
## まとめ
* 型パラメータが不明な値を、配列 or Objectを受け取るオーバロードされたメソッドに渡したときの動きは、Javaのバージョンによって異なる
* Java1.7.0_80ではObject型を受け取るメソッドが実行される
* Java1.8.0_162では配列型を受け取るメソッドが実行される


------
## 【補足】曖昧なオーバロード

`int[]` or `char[]`を受け取るオーバロードされたメソッドを呼び出すと、「メソッド overload(Object) は型 MainOverload であいまいです」というコンパイルエラーが発生する

```java
        //コンパイルエラー
	static void overload(int[] arg) {}
	static void overload(char[] arg) {}
```


------
## 今回の問題と関係ありそうな記事


* [String.valueOf gives ClassCastException](https://github.com/gwtproject/gwt/issues/9439)

>So, whereas before Java 8 the method argument site did not receive any inference, defaulting to Object, in Java 8 the most specific applicable type is inferred, in this case String. 



* [Why does the Java 8 generic type inference pick this overload?
](https://stackoverflow.com/questions/30521974/why-does-the-java-8-generic-type-inference-pick-this-overload)

* [Why is this method overloading ambiguous?](https://stackoverflow.com/questions/23020493/why-is-this-method-overloading-ambiguous)



-----
## 【補足】Java8の`javap -verbose`で逆アセンブルしてみた

```txt
Classfile /C:/Users/yuji3/Desktop/java-test/java8/Main.class
  Last modified 2018/04/14; size 476 bytes
  MD5 checksum 409af5d1b1986da6e0c4cd431dc50b59
  Compiled from "Main.java"
public class Main
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #7.#20         // java/lang/Object."<init>":()V
   #2 = Methodref          #6.#21         // Main.hoge:()Ljava/lang/Object;
   #3 = Class              #22            // "[C"
   #4 = Methodref          #23.#24        // java/lang/String.valueOf:([C)Ljava/lang/String;
   #5 = String             #14            // hoge
   #6 = Class              #25            // Main
   #7 = Class              #26            // java/lang/Object
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               main
  #13 = Utf8               ([Ljava/lang/String;)V
  #14 = Utf8               hoge
  #15 = Utf8               ()Ljava/lang/Object;
  #16 = Utf8               Signature
  #17 = Utf8               <E:Ljava/lang/Object;>()TE;
  #18 = Utf8               SourceFile
  #19 = Utf8               Main.java
  #20 = NameAndType        #8:#9          // "<init>":()V
  #21 = NameAndType        #14:#15        // hoge:()Ljava/lang/Object;
  #22 = Utf8               [C
  #23 = Class              #27            // java/lang/String
  #24 = NameAndType        #28:#29        // valueOf:([C)Ljava/lang/String;
  #25 = Utf8               Main
  #26 = Utf8               java/lang/Object
  #27 = Utf8               java/lang/String
  #28 = Utf8               valueOf
  #29 = Utf8               ([C)Ljava/lang/String;
{
  public Main();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 2: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=1, args_size=1
         0: invokestatic  #2                  // Method hoge:()Ljava/lang/Object;
         3: checkcast     #3                  // class "[C"
         6: invokestatic  #4                  // Method java/lang/String.valueOf:([C)Ljava/lang/String;
         9: pop
        10: return
      LineNumberTable:
        line 4: 0
        line 5: 10

  static <E extends java.lang.Object> E hoge();
    descriptor: ()Ljava/lang/Object;
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: ldc           #5                  // String hoge
         2: areturn
      LineNumberTable:
        line 8: 0
    Signature: #17                          // <E:Ljava/lang/Object;>()TE;
}
SourceFile: "Main.java"
```


-----
## 【補足】Java7の`javap -verbose`で逆アセンブルしてみた

```txt
Classfile /C:/Users/yuji3/Desktop/java-test/java7/Main.class
  Last modified 2018/04/14; size 481 bytes
  MD5 checksum 72c2eee8d5b013579f81dc56f53458d0
  Compiled from "Main.java"
public class Main
  SourceFile: "Main.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER

Constant pool:
   #1 = Methodref          #6.#19         //  java/lang/Object."<init>":()V
   #2 = Methodref          #5.#20         //  Main.hoge:()Ljava/lang/Object;
   #3 = Methodref          #21.#22        //  java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
   #4 = String             #13            //  hoge
   #5 = Class              #23            //  Main
   #6 = Class              #24            //  java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               main
  #12 = Utf8               ([Ljava/lang/String;)V
  #13 = Utf8               hoge
  #14 = Utf8               ()Ljava/lang/Object;
  #15 = Utf8               Signature
  #16 = Utf8               <E:Ljava/lang/Object;>()TE;
  #17 = Utf8               SourceFile
  #18 = Utf8               Main.java
  #19 = NameAndType        #7:#8          //  "<init>":()V
  #20 = NameAndType        #13:#14        //  hoge:()Ljava/lang/Object;
  #21 = Class              #25            //  java/lang/String
  #22 = NameAndType        #26:#27        //  valueOf:(Ljava/lang/Object;)Ljava/lang/String;
  #23 = Utf8               Main
  #24 = Utf8               java/lang/Object
  #25 = Utf8               java/lang/String
  #26 = Utf8               valueOf
  #27 = Utf8               (Ljava/lang/Object;)Ljava/lang/String;
{
  public Main();
    flags: ACC_PUBLIC

    Code:
      stack=1, locals=1, args_size=1
         0: aload_0       
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return        
      LineNumberTable:
        line 2: 0

  public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC

    Code:
      stack=1, locals=1, args_size=1
         0: invokestatic  #2                  // Method hoge:()Ljava/lang/Object;
         3: invokestatic  #3                  // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
         6: pop           
         7: return        
      LineNumberTable:
        line 4: 0
        line 5: 7

  static <E extends java/lang/Object> E hoge();
    flags: ACC_STATIC

    Code:
      stack=1, locals=0, args_size=0
         0: ldc           #4                  // String hoge
         2: areturn       
      LineNumberTable:
        line 8: 0
    Signature: #16                          // <E:Ljava/lang/Object;>()TE;
}
```