Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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

by yuji38kwmt
1 / 24

自己紹介

名古屋Javaユーザグループ 2018年4月でLTしました!


実行環境

  • Java 1.8.0_162
  • Java 1.7.0_80
  • Eclipse 4.7.2(Pleiades)

目次

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

1. 発生した問題


実行するとどうなるでしょうか?

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
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


Java7ではhogeの型パラメータはObject

Java7


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

Java8

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


String.valueOfはオーバロードされている

char配列型 or Object型の引数を受け取れる

  • valueOf(char[] data)
  • valueOf(Object obj)

String.valueOfのオーバロード


状況整理

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

ジェネリックメソッドの確認

  • 型パラメータが決まれば(String)、java8でも正常終了する。
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


オーバロードの確認

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

  • Objectとchar[] : NG
  • Objectとint[] : NG
  • Objectとchar : OK
  • Objectとint : OK

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

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


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;
}
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした