0. 引子
Kotlin 100% 与 Java 兼容,所以抛开语言表面上面的种种特质之外,背后的语言逻辑或者说“灵魂”与 Java 总是想通的。本文只涉及 Kotlin Jvm,Kotlin Js、Kotlin Native 的具体实现可能有差异。
最近一段时间在网上发了一套 Kotlin 的入门视频,涵盖了基础语法、面向对象、高阶函数、DSL、协程等比较有特色的知识点,不过有朋友提出了疑问:这门课为什么不专门讲讲泛型、反射和注解呢?
我最早听到这个问题的时候,反应比较懵逼,因为我居然没有感觉到 Kotlin 的反射、泛型特别是注解有专门学习的必要,因为他们跟 Java 实在是太像了。
实际上,从社区里面学习 Kotlin 的朋友的反应来看,大家大多对于函数式的写法,DSL,协程这些内容比较困惑,或者说不太适应,这与大家的知识结构是密切相关的,面向对象的东西大家很容易理解,因为就那么点儿内容,你懂了 C++ 的面向对象,Java 的也很容易理解,Kotlin 的也就不在话下了;而你没有接触过 Lua 的状态机,没有接触过 Python 的推导式,自然对于协程也就会觉得比较陌生。
所以我想说的是,泛型这东西,只要你对 Java 泛型有一定的认识,Kotlin 的泛型基本可以直接用。那我们这篇文章要干嘛呢?只是做一个简单的介绍啦,都很好理解的。
1. 真·泛型和伪·泛型
Java 的泛型大家肯定都知道了,1.5 之后才加入的,可以为类和方法分别定义泛型参数,就像下面这样:
public class Generics<T>{
private T t;
...
public <R> R getResult(){
...
}
}
Kotlin 的写法呢?完全一样:
class Generics<T>{
private val t: T
...
fun <R> getResult(): R{
...
}
}
Java/Kotlin 的泛型实现采用了类型擦除的方式,这与 C# 的实现不同,后者是真·泛型,前者是伪·泛型。当然这么说是从运行时的角度来看的,在编译期,Java 的泛型对于语法的约束也是真实存在的,所以你愿意的话,也可以管 Java 的泛型叫做编译期真·泛型。
那么什么是真·泛型呢?我们给大家看一段 C# 的代码:
using System;
public class Program{
public static void Main(String[] args){
testGeneric<string>();
}
public static void testGeneric<T>(){
Console.WriteLine(typeof(T));
}
}
testGeneric
的泛型参数 string 可以在运行时获取到,俨然一个真实可用的类型啊。下面是输出的结果:
System.String
那伪·泛型呢?如果同样的代码放到 Java 或者 Kotlin 当中,结果会怎样呢?
public static <T> void testGenerics(){
System.out.println(T.class);
}
这段代码无法编译,因为 T 是个泛型参数,你不能用它去获取 class 对象。为了更清楚地说明问题,我们看下下面的代码:
public static <T> T testGenerics(){
T t = null;
return t;
}
编译后的字节码:
public static testGenerics()Ljava/lang/Object;
L0