ClassCastException Java8 Generics

Java7以前版本開發升至Java8之注意事項 - 泛型造成ClassCastException的RunTimeException

陳致豪 2019/07/01 09:55:54
234

I. 發生問題的程式結構

下面是發生問題的程式,寫成簡短的例子

import org.junit.Test;

public class Demo {
	
	@Test
	public void callMethod() {
		System.out.print(String.valueOf(getElements()));
	}
	
	static <E> E getElements() {
		return (E) "element";
	}
}

該段程式無論在Java7或是在Java8皆可進行build,但是在執行該段方法時,當是在Java7的環境會正常執行,反觀Java8卻拋出ClassCastException,如下圖

此問題為何會產生?

II. 發生問題的原因

1. 回傳值確認

先來看在String.valueOf方法中,static generic method 在Java7與Java8所被認定的回傳值型態

Java7

在Java7中該泛型回傳型態認定是Object,再看看Java8

Java8

在Java8中,回傳值型態被認定為char array

2. 方法確認

String類別,valueOf方法所能接受的傳入值如下圖

Java7

Java8

無論是Java7還是Java8皆有支援接取的輸入值型態,再來檢查static的return值,利用下面程式檢查generic的回傳值type

import org.junit.Test;

public class Demo_GetClass {

	@Test
	public void getType() {
		System.out.print(getElements().getClass() );
	}
	
	<E> E getElements() {
		return (E) "element";
	}
}

Java7

回傳的是 class java.lang.String

Java8

同樣也是回傳 class java.lang.String

在Java8竟然是回傳String型態而非char array型態

確認問題

當String要轉為char array時無法強制轉型,必須呼叫toCharArray()方法才可轉成char array

III. 問題的處理方式

在上面的例子Java8中String的方法自動認定其傳入值型態為char array,卻又因為實際回傳的型態是String而造成ClassCastException,那就讓其方法的傳入值改成Object型態如下面程式

import org.junit.Test;

public class Demo_Fix {

		@Test
		public void callMethod() {
			System.out.println(String.valueOf((Object)getElements()) );
			checkType();
		}

		<E> E getElements() {
			return (E) "element" ;
		}
		
		public void checkType() {
			System.out.println("Return type is " + getElements().getClass().getName());
		}
		
}

執行結果如下

確認執行無誤

IV. 結語

於Java8以後,當在呼叫generic時,其回傳值的型態會被直接認定而非以前的Object型態,因此在開發時,應盡量避免於generic方法內直接回傳一個特定值,若為特定值則應直接回傳該型態,避免產生ClassCastException

參考來源:

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

String.valueOf gives ClassCastException #9439

陳致豪