# Java 泛型擦除 + 可变参数 = 有坑
## Code
“` java
public abstract class A<T, P>{
public A<T, P> exc(P… ps) {
doTings(ps);
return null;
}
void doTings(P… ps) {
}
}
public class B<T> extends A<T, Void> {
@Override
void doTings(Void… voids) {
}
}
“`
声明A,B类,泛型作为可变参数执行
## Try Run
以下内容执行在Main类的main方法中,略去main
第一次点火,编译通过,运行炸了
“` java
B b = new B<String>();
b.exc();
“`
第二次点火,编译通过,运行炸了
“` java
B b = new B();
b.exc();
“`
第三次点火,编译通过,运行炸了
“` java
A b = new B();
b.exc();
“`
第四次点火,**编译通过,运行通过**
“` java
B<String> b = new B<String>();
b.exc();
“`
第五次点火,**编译通过,运行通过**
“` java
A<String, Void> b = new B<String>();
b.exc();
“`
## 捉鬼…
### 先看炸点, WTF!
炸在`B.doTings()`, 行数却显示在B类声明处`public class B<T> extends A<T, Void>`。
哪来的java.lang.Object[],我绝对没有传!!!
`
Caused by: java.lang.ClassCastException: java.lang.Object[] cannot be cast to java.lang.Void[]
at com.example.garra.myapplication.B.doTings(B.java:7)
at com.example.garra.myapplication.A.exc(A.java:12)
at com.example.garra.myapplication.MainActivity.onCreate(MainActivity.java:14)
at android.app.Activity.performCreate(Activity.java:7057)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1214)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2789)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2911)
at android.app.ActivityThread.-wrap11(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1608)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6665)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:781) `
### 猜想
定是泛型类型编译擦除搞得鬼,没准和可变参数的不定长特性也有关系?
### 反编译.class
“`
@dalvik.annotation.Signature
public abstract class A extends Object
{
//======================== F I E L D S ==================
//======================== C O N S T R U C T O R S ======
public A() { … }
//======================== M E T H O D S ================
@dalvik.annotation.Signature
transient void doTings(Object[]) { … }
@dalvik.annotation.Signature
public transient A exc(Object[]) { … }
}
@dalvik.annotation.Signature
public class B extends A
{
//======================== F I E L D S ==================
//======================== C O N S T R U C T O R S ======
public B() { … }
//======================== M E T H O D S ================
volatile void doTings(Object[]) { … }
transient void doTings(Void[]) { … }
}
编译通过,运行通过
B b = new B();
b.exc(new Void[0]);
编译通过,运行炸了
B b = new B();
b.exc(new Object[0]);
“`
可见编译后泛型全部擦除,替换成了实际的类型,注意到B类存在两个doTings()方法,Object[]和Void[]做为参数。
惊喜发现,`b.exc()`编译后出现了差异,我虽然没有传参数,但是编译器替我做了…
至此可以明白声明对象句柄时带不带泛型,会影响该类选择哪个方法执行。
But,Object[] cast to Void[] 又是从哪里执行的?
### 反编译.smali
A 类
“` smali
.class public abstract Lcom/example/garra/myapplication/A;
.super Ljava/lang/Object;
.source “A.java”
.annotation system Ldalvik/annotation/Signature;
value = {
“<T:”,
“Ljava/lang/Object;”,
“P:”,
“Ljava/lang/Object;”,
“>”,
“Ljava/lang/Object;”
}
.end annotation
.method public constructor <init>()V
.registers 1
.local p0, this:Lcom/example/garra/myapplication/A;, “Lcom/example/garra/myapplication/A<TT;TP;>;”
.prologue
.line 9
invoke-direct { p0 }, Ljava/lang/Object;-><init>()V
return-void
.end method
.method varargs doTings([Ljava/lang/Object;)V
.annotation system Ldalvik/annotation/Signature;
value = {
“([TP;)V”
}
.end annotation
.registers 5
.local p0, this:Lcom/example/garra/myapplication/A;, “Lcom/example/garra/myapplication/A<TT;TP;>;”
.local p1, ps:[Ljava/lang/Object;, “[TP;”
.prologue
.line 18
const-string/jumbo v0, “h”
new-instance v1, Ljava/lang/StringBuilder;
invoke-direct { v1 }, Ljava/lang/StringBuilder;-><init>()V
const-string/jumbo v2, “dotings “
invoke-virtual { v1, v2 }, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
move-result-object v1
invoke-virtual { p1 }, Ljava/lang/Object;->getClass()Ljava/lang/Class;
move-result-object v2
invoke-virtual { v1, v2 }, Ljava/lang/StringBuilder;->append(Ljava/lang/Object;)Ljava/lang/StringBuilder;
move-result-object v1
const-string/jumbo v2, “”
invoke-virtual { v1, v2 }, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
move-result-object v1
invoke-virtual { v1 }, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
move-result-object v1
invoke-static { v0, v1 }, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
.line 19
return-void
.end method
.method public varargs exc([Ljava/lang/Object;)Lcom/example/garra/myapplication/A;
.annotation system Ldalvik/annotation/Signature;
value = {
“([TP;)”,
“Lcom/example/garra/myapplication/A”,
“<TT;TP;>;”
}
.end annotation
.registers 5
.local p0, this:Lcom/example/garra/myapplication/A;, “Lcom/example/garra/myapplication/A<TT;TP;>;”
.local p1, ps:[Ljava/lang/Object;, “[TP;”
.prologue
.line 12
const-string/jumbo v0, “h”
new-instance v1, Ljava/lang/StringBuilder;
invoke-direct { v1 }, Ljava/lang/StringBuilder;-><init>()V
const-string/jumbo v2, “exc “
invoke-virtual { v1, v2 }, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
move-result-object v1
invoke-virtual { p1 }, Ljava/lang/Object;->getClass()Ljava/lang/Class;
move-result-object v2
invoke-virtual { v1, v2 }, Ljava/lang/StringBuilder;->append(Ljava/lang/Object;)Ljava/lang/StringBuilder;
move-result-object v1
const-string/jumbo v2, “”
invoke-virtual { v1, v2 }, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
move-result-object v1
invoke-virtual { v1 }, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
move-result-object v1
invoke-static { v0, v1 }, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
.line 13
invoke-virtual { p0, p1 }, Lcom/example/garra/myapplication/A;->doTings([Ljava/lang/Object;)V
.line 14
const/4 v0, 0
return-object v0
.end method
“`
B 类
“` smali
.class public Lcom/example/garra/myapplication/B;
.super Lcom/example/garra/myapplication/A;
.source “B.java”
.annotation system Ldalvik/annotation/Signature;
value = {
“<T:”,
“Ljava/lang/Object;”,
“>”,
“Lcom/example/garra/myapplication/A”,
“<TT;”,
“Ljava/lang/Void;”,
“>;”
}
.end annotation
.method public constructor <init>()V
.registers 1
.local p0, this:Lcom/example/garra/myapplication/B;, “Lcom/example/garra/myapplication/B<TT;>;”
.prologue
.line 7
invoke-direct { p0 }, Lcom/example/garra/myapplication/A;-><init>()V
return-void
.end method
.method bridge synthetic doTings([Ljava/lang/Object;)V
.registers 2
.local p0, this:Lcom/example/garra/myapplication/B;, “Lcom/example/garra/myapplication/B<TT;>;”
.prologue
.line 7
check-cast p1, [Ljava/lang/Void;
invoke-virtual { p0, p1 }, Lcom/example/garra/myapplication/B;->doTings([Ljava/lang/Void;)V
return-void
.end method
.method varargs doTings([Ljava/lang/Void;)V
.registers 2
.local p0, this:Lcom/example/garra/myapplication/B;, “Lcom/example/garra/myapplication/B<TT;>;”
.prologue
.line 12
return-void
.end method
“`
重点看B类该方法
“` smali
.method bridge synthetic doTings([Ljava/lang/Object;)V
.registers 2
.local p0, this:Lcom/example/garra/myapplication/B;, “Lcom/example/garra/myapplication/B<TT;>;”
.prologue
.line 7
check-cast p1, [Ljava/lang/Void;
invoke-virtual { p0, p1 }, Lcom/example/garra/myapplication/B;->doTings([Ljava/lang/Void;)V
return-void
.end method
“`
这是个桥连方法,做了强制类型转换`check-cast p1`,这正是泛型擦除的核心实现,此时Object[] cast to Void[] 就炸了。
## 总结
正常使用泛型,不带可变参数时,会强制你传递指定类型的参数,所以不会出现此问题,但是遇上可变参数,可以什么也不传,这时编译器会根据规则自动选择参数类型,这样就有可能出现泛型强制转换类型时错误。