泛型

  • 泛型又叫参数化类型,可以理解是一种类型参数(一种表征类型的参数类型)
  • 泛型主要起到的是规范作用,其行为主要是在编译期报错来避免一些潜在的类型安全问题
  • kotlin泛型按种类可分为以下3类
    • 函数泛型
    • 类泛型
    • 接口泛型
    • 真泛型类型reified

函数泛型

作为函数参数传入

1
2
3
fun <T> get(t: T) {
println(t.toString())
}

字节码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public final static get(Ljava/lang/Object;)V      
#这也就是著名的类型擦除,泛型类型最后会被会被编译成Object而没有真正生成具体的类
// annotable parameter count: 1 (visible)
// annotable parameter count: 1 (invisible)
L0
LINENUMBER 8 L0 #源代码行号
ALOAD 0 #局部变量表0位置的引用类型存入操作数栈
INVOKESTATIC java/lang/String.valueOf (Ljava/lang/Object;)Ljava/lang/String;
#调用静态方法String.valueOf()
ASTORE 1 #调用结果存入局部变量表index为1的位置
L1
#熟悉的sout
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ALOAD 1
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
L2
L3
LINENUMBER 9 L3
RETURN #return
L4
LOCALVARIABLE t Ljava/lang/Object; L0 L4 0 #局部变量表
MAXSTACK = 2
MAXLOCALS = 2

可以发现如果泛型作为函数的参数是没有任何的运行时提示的.

等价

1
2
3
4
fun get(t:Any){
println(t)
}

作为函数的返回值

1
2
3
fun <T> set(any: Any): T {
return any as T
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public final static set(Ljava/lang/Object;)Ljava/lang/Object;
#依然类型擦除,返回值直接被替换为了obj
// annotable parameter count: 1 (visible)
// annotable parameter count: 1 (invisible)
@Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
L0
ALOAD 0 #局部变量入栈(方法参数)
LDC "any" #常量池any常量入栈
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter (Ljava/lang/Object;Ljava/lang/String;)V
#类型检测(定义的时候使用的kotlin的不可空类型,所以生成了check)
L1
LINENUMBER 12 L1
ALOAD 0 #invokestatic消耗了栈顶两个元素,目前栈顶为空,继续压入set方法的方法参数
ARETURN #直接return
L2
LOCALVARIABLE any Ljava/lang/Object; L0 L2 0
MAXSTACK = 2
MAXLOCALS = 1

有些惊讶,显式地调用as进行类型强制,但是这个as只确保了编译通过,没有生成任何额外地字节码

函数等价于

1
fun a(any:Any):Any = any

类型限定

1
2
3
4
5
6
7
8
9
10
11
12
13
class A {
fun sayHello() {
println("Hello I am A")
}
}

fun <T : A> constrains1(t: T) {
t.sayHello()
}

fun <T : A> constrains2(obj: Any): T {
return obj as T
}

constrain1分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public final static constrains1(LA;)V
#说这是类型擦除吧也是,但是呢你说他不是好像确实也不是.
#总共不是obj了
// annotable parameter count: 1 (visible)
// annotable parameter count: 1 (invisible)
@Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
L0
#等价Intrinsics.checkNotNullParameter(t);
ALOAD 0
LDC "t"
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter (Ljava/lang/Object;Ljava/lang/String;)V
L1
LINENUMBER 22 L1
#等价于t.sayHello()
ALOAD 0
INVOKEVIRTUAL A.sayHello ()V
L2
LINENUMBER 23 L2
RETURN
L3
LOCALVARIABLE t LA; L0 L3 0
MAXSTACK = 2
MAXLOCALS = 1

可以发现加入了类型限定以后类型擦除不是obj,而是限定的那个类

constraint2分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public final static constrains2(Ljava/lang/Object;)LA;
@Lorg/jetbrains/annotations/NotNull;() // invisible
// annotable parameter count: 1 (visible)
// annotable parameter count: 1 (invisible)
@Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
L0
ALOAD 0
LDC "obj"
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter (Ljava/lang/Object;Ljava/lang/String;)V
L1
LINENUMBER 26 L1
ALOAD 0
L2
CHECKCAST A #与前面的类似只是多了一条cast指令
ARETURN
L3
LOCALVARIABLE obj Ljava/lang/Object; L0 L3 0
MAXSTACK = 2
MAXLOCALS = 1

小结

  • 对于函数泛型如果没有类型限制那么最后会被擦除为Object
  • 如果有类型限制,那么就会将类型擦除为限制的类型,这时候就会生成cast指令,不留神就会报ClassCastException

类泛型

1
class B<T>(val t: T)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public final class B {

private final Ljava/lang/Object; t #类型擦除为了Object,后续的也没必要看了一个样

public final getT()Ljava/lang/Object;
L0
LINENUMBER 30 L0
ALOAD 0
GETFIELD B.t : Ljava/lang/Object;
ARETURN

public <init>(Ljava/lang/Object;)V
L0
LINENUMBER 30 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
ALOAD 0
ALOAD 1
PUTFIELD B.t : Ljava/lang/Object;
RETURN
}

in,out

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class A {
fun sayHello() {
println("Hello I am A")
}
}

class C<in Generics : A> {
private var value: Generics? = null

fun a(a: A) {
a.sayHello()
this.value = a as Generics
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public final class C {


// access flags 0x2
// signature TGenerics;
// declaration: value extends Generics
#合理的,由于加入了类型的限定,所以类型绑定为了A
private LA; value

L0
#判空
#局部变量表0为this,1为a方法的参数
ALOAD 1
LDC "a"
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter (Ljava/lang/Object;Ljava/lang/String;)V
L1
LINENUMBER 37 L1
#等价于a.sayHello()
ALOAD 1
INVOKEVIRTUAL A.sayHello ()V
L2
LINENUMBER 38 L2
#压入局部变量this和a
ALOAD 0
ALOAD 1
L3
#设置成员变量
PUTFIELD C.value : LA;
L4
LINENUMBER 39 L4
RETURN
L5
LOCALVARIABLE this LC; L0 L5 0
LOCALVARIABLE a LA; L0 L5 1
MAXSTACK = 2
MAXLOCALS = 2
}

可以发现in关键字其实只是编译期间不允许你返回带有对应泛型的返回值,没有新增加任何指令.

类似的out只是不允许你定义任何带有该泛型类型参数的方法

1
2
3
4
5
6
7
class D<out Generics : A> {
private var value: Generics? = null

fun getV(): Generics? {
return value
}
}

接口泛型

1
2
3
interface GenericsInterface<A> {
fun m(a: A):A
}

字节码分析

1
2
3
4
public abstract interface GenericsInterface {
# 依然是类型擦除
public abstract m(Ljava/lang/Object;)Ljava/lang/Object;
}

类似的in/out 类型限定的实现原理和上述实现类似。不做赘述。

reified

reified是需要借助inline它是泛型,but它的实现是依靠的inline.
reified = generics + inline

1
2
3
4
5
6
7
8
inline fun <reified A> reifiedGenerics(a: A) {
println(a)
println(A::class.java)
}

fun main() {
reifiedGenerics<String>("")
}

当你去分析reifiedGenerics来了解reified的实现的时候.
你就走错路了.
因为他是inline,而且强制绑定inline
它的实现你得去调用处进行分析.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public final static main()V
L0
LINENUMBER 74 L0
LDC ""
ASTORE 0
L1
ICONST_0
ISTORE 1
L2
LINENUMBER 80 L2
L3
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ALOAD 0
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
L4
L5
LINENUMBER 81 L5
LDC Ljava/lang/String;.class #class常量入栈
ASTORE 2
L6
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ALOAD 2
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
L7
L8
LINENUMBER 82 L8
NOP
L9
LINENUMBER 75 L9
RETURN

代码等价于

1
2
3
4
fun main(){
println("")
println(String::class.java)
}