Java 泛型擦除 + 可变参数 = 有坑

# 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[] 就炸了。

## 总结
正常使用泛型,不带可变参数时,会强制你传递指定类型的参数,所以不会出现此问题,但是遇上可变参数,可以什么也不传,这时编译器会根据规则自动选择参数类型,这样就有可能出现泛型强制转换类型时错误。



转载于:https://juejin.im/post/5a603ec2f265da3e274537cf

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值