自己紹介
-
Terasoluna Server for Javaで社内Webシステム開発(Java7, Java8) : 4年間
-
Spring Bootで社内Webシステム開発:半年
-
4月に転職。Scala勉強中
名古屋Javaユーザグループ 2018年4月でLTしました!
実行環境
- Java 1.8.0_162
- Java 1.7.0_80
- Eclipse 4.7.2(Pleiades)
目次
- 発生した問題
- 原因を調査
- 調査した結果
1. 発生した問題
実行するとどうなるでしょうか?
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が発生
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と異なるが、結果は同じだった。
Java7ではhogeの型パラメータはObject
Java8ではhogeの型パラメータはchar[]
char[]
はString.valueOf
から来ている。
String.valueOf
はオーバロードされている
char配列型 or Object型の引数を受け取れる
valueOf(char[] data)
valueOf(Object obj)
状況整理
- ジェネリックスメソッドの結果を、値Aとする
- 戻り値は仮型パラメータ
- 引数がないので、実型パラメータが不明
- char配列型 or Object型の引数を受け取れるメソッドに、値Aを渡している
ジェネリックメソッドの確認
- 型パラメータが決まれば(String)、java8でも正常終了する。
public class MainGenericsMethod {
public static void main(String[] args) {
String.valueOf(fuga("fuga"));
}
/** 引数から型パラメータが決まる */
static <E> E fuga(E arg) {
return (E) arg;
}
}
オーバロードの確認
オーバロードされたメソッドの組み合わせを変えて、Java8で実行した結果。
- Objectと
char[]
: NG - Objectと
int[]
: NG - Objectと
char
: OK - Objectと
int
: OK
Object型 or 配列型を受け取るオーバロードされたメソッドが問題。
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) {} //引数の型を変える
}
3. 調査した結果
まとめ
- 型パラメータが不明な値を、配列 or Objectを受け取るオーバロードされたメソッドに渡したときの動きは、Javaのバージョンによって異なる
- Java1.7.0_80ではObject型を受け取るメソッドが実行される
- Java1.8.0_162では配列型を受け取るメソッドが実行される
【補足】曖昧なオーバロード
int[]
or char[]
を受け取るオーバロードされたメソッドを呼び出すと、「メソッド overload(Object) は型 MainOverload であいまいです」というコンパイルエラーが発生する
//コンパイルエラー
static void overload(int[] arg) {}
static void overload(char[] arg) {}
今回の問題と関係ありそうな記事
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.
【補足】Java8のjavap -verbose
で逆アセンブルしてみた
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
で逆アセンブルしてみた
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;
}