Kotlin 的泛型,reified泛型实化
Java泛型
那么先得从最简单的讲起 JAVA泛型使用开始讲起
在JAVA1.5后加入泛型 在1.5前是没有泛型的 我们泛型用的比较多的就是集合了
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("1");
arrayList.add("2");
arrayList.add("3");
arrayList.add(4);
for (Object s : arrayList) {
System.out.println((String) s);
}
}
//输出
1
2
3
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at TestDemo.main(TestDemo.java:16)
首先先创建一个集合 这个集合是Object类型 可以插入任何类型
上面这段代码本来是没有问题的 但是又在最后集合中插入一个int类型 那么在强转的时候 就会发送类型转换异常了 使用没有泛型的集合中很容易在一个集合中 插入2个以上的类型 在类型转换的时候 如果类型不一致 就会异常了。这样是集合是类型不安全的
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList();
arrayList.add("1");
arrayList.add("2");
arrayList.add("3");
arrayList.add(4);//这里会发送错误 因为没有 .add(int o);的方法
for (String s : arrayList) {
System.out.println(s);
}
}
可以看到 加入泛型后 我们在编译期 就可以发现错误 及时更改 防止在运行期异常奔溃。
泛型为我们解决了什么?
- 多种类型的相同执行代码得到了更好的复用
- 类型安全 使用泛型提高了我们代码的稳定性 不用担心强制转换类型发现异常奔溃
泛型的使用
泛型类
class Box<T> {
private T t;
public T getBox() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
泛型接口
//定义泛型接口
interface Base<T>{
T get();
}
//实现泛型接口1
class View<T> implements TestDemo.Base<T> {
@Override
public T get() {
return null;
}
}
//实现泛型接口2
class View implements TestDemo.Base<String> {
@Override
public String get() {
return "";
}
}
泛型方法
public <T> T getEntity(T t) {
return t;
}
以上都是都是泛型的基本操作和使用 泛型协变和逆变就留给后面的Kotlin 还有就是用Kotlin讲更容易理解。
泛型类型擦除
关于Java的泛型我们最后还要讲一点 泛型类型擦除
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
List<Integer> integers = new ArrayList<>();
System.out.println(strings.getClass().equals(integers.getClass()));
}
在不了解泛型类型擦除之前 你应该觉得输出是 false 吧 然后结果为 ture 。
泛型在编译期进行擦除 都会变成 Object 所以为什么结果会为 ture 这下你明白了吧。相当等于
List<Object> objects = new ArrayList<>();
List<Object> objects2 = new ArrayList<>();
objects.equals(objects2)
我们可以用数组验证下
public static void main(String[] args) {
String[] strings1 = new String[0];
int[] ints = new int[0];
System.out.println(ints.getClass().equals(strings1.getClass()));
}
结果为 false 数组是没有类型擦除的
那为什么 java 要进行类型擦除呢? 前面我们点到了 在java1.5之前是没有泛型的 1.5之后引入泛型 那为什么兼容早期1.5的版本 所以使用了类型擦除 。然而类型擦除也带来了一堆毛病。所有基于JVM的语言 泛型都是通过类型擦除实现的。而Kotlin 通过一些小技巧 可以解决泛型擦除的问题。
Kotlin 中的泛型
java的泛型就到这里了 接下来 看看Kotlin
kotlin号称100%兼容java 那么Java的性质Kotlin肯定是都有的 由于也是基于JVM语言 类型擦除 Kotlin也不例外。
fun <T> printNum(t: T) {
println(t)
}
fun main() {
printNum("11")
printNum(1)
printNum(1.234)ko
}
可以发现泛型和使用Java 差不多 接下来我们要开始进阶了
泛型实体化
inline
泛型实体化 听起来很神奇啊 JVM不是会在编译器进行类型擦除吗? 接下来我们借助Kotlin中的 inline 实现泛型实化 由于本章主要讲的是泛型 对 inline 不进行过多的讲解。
官方描述
使用高阶函数会带来一些运行时的效率损失:每一个函数都是一个对象,并且会捕获一个闭
包。 即那些在函数体内会访问到的变量。 内存分配(对于函数对象和类)和虚拟调用会引入
运行时间开销。
”
Kotlin的函数 通过一个 inline 的修饰符 就可以申明为内联函数。这个函数会在编译器直接内联到源码中
fun main() {
hello()
println("world")
}
private inline fun hello() {
println("Hello")
}
上面是个简单的内联函数 我们通过字节码反编译下生成的java代码
这是内联函数生成的代码
public static void main() {
String var1 = "Hello";
System.out.println(var1);
String var3 = "world";
System.out.println(var3);
}
这是普通函数生成的代码
public static void main() {
hello();
String var0 = "world";
System.out.println(var0);
}
private static final void hello() {
String var0 = "Hello";
System.out.println(var0);
}
reified
在使用内联函数时 在声明的泛型前面加上 reified 修饰符 可以发现 泛型实化
fun main() {
isString(1)
isString("")
}
private inline fun <reified T> isString(t: T) {
println("" is T)
}
false
true
可以使泛型实化 并且可用 is 、 T:class:java 这样的语法了 这几乎是java不可能做到的 那么这个泛型实化有什么好处吗?
- 我们可以得到泛型的类型了
- 语法更加简洁了
比如在Android开发过程中 我们启动另一个activity时 正常语法如下
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
现在改造下
startActivity<MainActivity>(this)
inline fun <reified T> startActivity(context: Context) {
val intent = Intent(context, T::class.java)
context.startActivity(intent)
}
是不是让代码更优雅了? 没错 现在Gson Retrofit你都可以这么加以改造下 让代码更加优雅。
泛型 协变
泛型中的协变 和 逆变 虽然不常用 但是在Kotlin中用到了很多协变和逆变的特性 我们常用的List就是一个天生支持协变的,接下来了解下协变。

接受参数的地方为 in 位置 返回值的地方为 out 位置 这两个得记住 协变和逆变的理解就靠这张图了。
open class A
class B : A()
fun main() {
val b = B()
get(b)
}
fun get(a: A) {
// ...
}
这样的代码是没问题的对吧 我们加入泛型试试
open class A
class B : A()
fun main() {
val b = B()
val mutableListOf = mutableListOf(b)
get(mutableListOf)//这里错误了
}
fun get(a: MutableList<A>) {
// ...
}
发现在调用get的时候发生错误了。为什么错了呢 在第一段代码get函数中传入B的实例 这是可以的 因为B是A的子类 第二代代码为什么错了呢 因为 MutableList 不是 MutableList 的子类 所以不能这么用。但是现在我们改一下 代码
open class A
class B : A()
fun main() {
val b = B()
val list = listOf(b)
get(list)
}
fun get(a: List<A>) {
// ...
}
可以发现代码没问题了,是不是很神奇 为什么呢 我们前面讲到了 List天生支持协变 我们看看List的源码
public interface List<out E> : Collection<E> {
...
override fun iterator(): Iterator<E>
public fun listIterator(): ListIterator<E>
public fun listIterator(index: Int): ListIterator<E>
public fun subList(fromIndex: Int, toIndex: Int): List<E>
}
List在泛型前面 加了一个 out, out 关键字是进行协变声明,那么现在的List所有关于泛型只能作为输出。这就是为什么List声明的集合只读而不可写。
我们回过来看看上面的图片 是不是能加强理解了 。
这里再总结下
- B是A的子类 但是MutableList 不是MutableList的子类
- 如果我们加上out关键字 让其变成只读 这样也不会让其类型不安全 。
- 当加上out关键字 我们只能让泛型出现在返回值 这点可以看上面的图片
泛型 逆变
和协变截然相反 逆变是只能写 而不能读 在声明泛型前面 加入 in 关键字
open class A
class B : A()
fun main() {
val a = A()
val mutableListOf = mutableListOf(a)
get(mutableListOf)
}
fun get(b: MutableList<in B>) {
// ...
}
- get函数中 接收一个MutableList的参数
- 我们传入一个MutableList的类型 (注意B是A的子类)
- 这里原本是不允许的 我们将get函数中的泛型 声明为逆变 加入 in 关键字 此时该泛型只能写 而不能读
- 泛型的协变 和 逆变 上下界完全相反 结合上图你会更好理解
泛型的协变与逆变都讲完了 虽然开发过程中用的比较少 但是在学习Kotlin和进阶的路上我们还是会用的越来越多的。
文章代码居多 理论较少 多去实验下就会明白泛型的使用。