1. Java中泛型擦除
Java的泛型并不是真正的范型,而是编译期泛型,在编译完成之后泛型就不存在了,签名中的范型
如:
public T first(List list){
return list.get(0);
}
会变成
public Object first(List list){
return list.get(0);
}
范型擦除会导致的问题
1.泛型擦除导致的转型问题
由于在运行时,类型信息已经被擦除了,因此无法获得运行时的类型,导致某些框架如Gson,传入Class才可以正确的转型。
编译之后的Gson.fromJson的方法体中,范型
public T fromJson(String json, Class classOfT) throws JsonSyntaxException {
Object object = fromJson(json, (Type) classOfT);
return Primitives.wrap(classOfT).cast(object);
}
而在其他语言中,如C#可以直接在带有泛型的方法体中直接转型,因为他们的泛型是真正的泛型,在编译之后,会将泛型方法,编译成对应的具体类型方法,好处就是真泛型,操作方便,坏处就是使得编译后的代码量增多(因为传入多少种真实的类型,就会编译出多少个方法)
同为JVM平台下的kotlin,kotlin可以通过內联机制,绕过因泛型擦除而导致的无法在运行时直接获取类型的问题,因为具体类型已经被编译內联到调用处的代码中了。
如:Java
public boolean isTypeOf(Object o) {
// 返回任意对象是否是T类型
// 无法编译
return o instanceof T;
}
在kotlin中:
inline fun isTypeOf(any: Any): Boolean {
// 返回任意对象是否是T类型
// 能够正常编译
return any is T
}
//测试
println(isTypeOf(1))
println(isTypeOf(""))
//打印结果
false
true
2.泛型擦除导致的方法重载问题
由于编译之后,泛型被擦除成Object,实际上对于以下两个方法,他们的方法签名是相同的,因此无法重载。
public void printList(List list) {
System.out.println(list);
}
public void printList(List list) {
System.out.println(list);
}
3.泛型擦除导致无法使用基本数据类型
如:
List
2. Java中的泛型,不可以形变
1. 协变
协变指的是Foo<父> = Foo<子> 的这种操作
作为生产者的时候,以下在我们看来是十分安全的操作,在Java中是无法编译的。
class Father{}
class Son extends Father{}
List sons = new ArrayList();
//下面编译报错
List fathers = sons;
Father father = fathers.get(0);
为了解决上面的问题,Java引入了通配符,通配符的加入使得这种操作变为可能,虽然看上去不太优雅。
List sons = new ArrayList();
List extends Father> fathers = sons;
Father father = fathers.get(0);
extends Father>
这种表达方式看起非常的奇怪。
其实在其他语言中,如C#与kotlin上述行为叫做协变
在kotlin中,这样是可以通过编译的。
class Son: Father()
open class Father
val sons = listOf()
//可以通过编译
val fathers: List = sons
Kotlin是如何实现以上协变的?看一下对List的定义
public interface List : Collection
在定义List的泛型参数前面加了一个 out 代表了 List类在声明处发生了协变。
但是声明处协变是有限制的,限制就是该泛型只能用于生产者,也就是说泛型只能出现在,返回值,而不能出现在参数里。
如果泛型要强行出现在参数里需要增加@UnsafeVariance的注解
public interface List : Collection{
...
public fun indexOf(element: @UnsafeVariance E): Int
...
}
好处就是在声明的时候就已经确定该类是可以进行协变的了,因而可以直接进行协变的转换。
2. 逆变
逆变指的是Foo<子> = Foo<父> 的这种操作
作为消费者的时候,以下在我们看来是十分安全的操作,在Java中是无法编译的。
// Double 是 Number的子类
public void demo(Comparable x){
//这一步编译就报错
Comparable y = x;
y.compareTo(1.0)
}
同样的java可以通过通配符可以进行逆变
public void demo(Comparable x){
//编译通过
Comparable super Double> y = x;
y.compareTo(1.0);
}
而在kotlin中完全不需要这么麻烦
interface Comparable {
operator fun compareTo(other: T): Int
}
fun demo(x: Comparable) {
// 因此,我们可以将 x 赋给类型为 Comparable 的变量
val y: Comparable = x // OK!
y.compareTo(1.0) // 1.0 拥有类型 Double,它是 Number 的子类型
}
可以看出kotlin在接口的声明处使用 in 关键字进行了逆变。
同样在声明处逆变是有限制的,限制就是该泛型只能用于消费者,也就是说泛型只能出现在,参数中,而不能出现在返回值里。
如果泛型要强行出现在返回值上需要增加@UnsafeVariance的注解