Kotlin 泛型之类型擦除

简介: Kotlin 泛型之类型擦除

一. Java 泛型的优点



泛型是 Java 5 的重要特性之一。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。


Java 泛型的优点包括:


  • 类型安全
  • 消除强制类型转换
  • 避免了不必要的装箱、拆箱操作,提高程序性能
  • 提高代码的重用性


下面,以我的缓存框架 RxCache 中 Memory 接口为例:

package com.safframework.rxcache.memory;
import com.safframework.rxcache.domain.CacheStatistics;
import com.safframework.rxcache.domain.Record;
import java.util.Set;
/**
 * Created by tony on 2018/9/29.
 */
public interface Memory {
    <T> Record<T> getIfPresent(String key);
    <T> void put(String key, T value);
    <T> void put(String key, T value, long expireTime);
    Set<String> keySet();
    boolean containsKey(String key);
    void evict(String key);
    void evictAll();
    CacheStatistics getCacheStatistics();
}


通过该接口的定义,能够看到使用泛型提高了代码的重用性。


二. Kotlin 泛型



Kotlin 基于 Java 6,因此 Kotlin 天生支持泛型。但是 Kotlin 的泛型有自己的特点。2


例如,对于扩展函数涉及到泛型的类,需要指定泛型参数:

fun <T : View> T.longClick(block: (T) -> Boolean) = setOnLongClickListener { block(it as T) }


三. Java 通过类型擦除支持泛型



Java 为了兼容性的考虑,采用类型擦除的机制来支持泛型。


泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,这个过程被称为类型擦除。


3.1 类型擦除


由于类型擦除,List<String> 和 List<Integer> 在编译后都会变成 List<Object>。


例如:

List<String> list1 = new ArrayList<>();
        list1.add("kotlin");
        List<Integer> list2 = new ArrayList<>();
        list2.add(1);


通过 javap -c 命令对代码进行反汇编:

Code:
       0: new           #2                  // class java/util/ArrayList
       3: dup
       4: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
       7: astore_1
       8: aload_1
       9: ldc           #4                  // String kotlin
      11: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      16: pop
      17: new           #2                  // class java/util/ArrayList
      20: dup
      21: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
      24: astore_2
      25: aload_2
      26: iconst_1
      27: invokestatic  #6                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      30: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      35: pop
      36: return


再对上述代码分别打印 list1、list2 的类型,再次验证一下类型擦除。

System.out.println(list1.getClass());
        System.out.println(list2.getClass());


执行结果:

class java.util.ArrayList
class java.util.ArrayList


因此,List <T> 在运行时并不知道泛型参数的类型。


3.2 Java 数组并没有受到类型擦除的影响


例如:

String[] array1 = new String[5];
        array1[0] = "kotlin";
        Integer[] array2 = new Integer[5];
        array2[0] = 1;


通过 javap -c 命令对代码进行反汇编:

Code:
       0: iconst_5
       1: anewarray     #2                  // class java/lang/String
       4: astore_1
       5: aload_1
       6: iconst_0
       7: ldc           #3                  // String kotlin
       9: aastore
      10: iconst_5
      11: anewarray     #4                  // class java/lang/Integer
      14: astore_2
      15: aload_2
      16: iconst_0
      17: iconst_1
      18: invokestatic  #5                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      21: aastore
      22: return

2

从反汇编的角度可以看出,Java 数组并没有受到类型擦除的影响。


再对上述代码分别打印 array1、array2 的类型,

System.out.println(array1.getClass());
        System.out.println(array2.getClass());


执行结果:

class [Ljava.lang.String;
class [Ljava.lang.Integer;


由此可见,数组在运行时可以获得它的类型。因为,Java 数组是协变的,所以 Java 数组不支持泛型。


协变是在计算机科学中,描述具有父/子型别关系的多个型别通过型别构造器、构造出的多个复杂型别之间是否有父/子型别关系的用语。


四. Kotlin 如何获得声明的泛型类型



跟 Java 一样,Kotlin 也是通过类型擦除支持泛型。


但是 Kotlin 的数组支持泛型,因此它们并不会协变。例如:

val array1 = arrayOf<Int>(1, 2, 3, 4)
    val array2 = arrayOf<String>("1", "2", "3", "4")


Kotlin的泛型在运行时被擦除了,会带来一些影响。如何解决 Kotlin 类型擦除带来的影响呢?办法肯定是有的。


4.1 声明内联函数,使其类型不被擦除


刚才定义的两个数组使用了 arrayOf ,通过查看 arrayOf 的源码:

/**
 * Returns an array containing the specified elements.
 */
public inline fun <reified @PureReifiable T> arrayOf(vararg elements: T): Array<T>


它使用inline, 并且使用reified标记类型参数。


打印  array1、array2 的类型:

println(array1.javaClass)
    println(array2.javaClass)


执行结果:

class [Ljava.lang.Integer;
class [Ljava.lang.String;


4.2 实例化类型参数代替类引用


再举一个 Kotlin 使用 Gson 的反序列化的例子,可以使用实例化类型参数 T::class.java

inline fun <reified T : Any> Gson.fromJson(json: String): T = Gson().fromJson(json, T::class.java)


总结:



本文介绍了 Java 和 Kotlin 的泛型以及类型擦除,并介绍了如何获得声明的泛型类型。


Kotlin 的泛型远不止这些,后续的文章会进一步介绍泛型的协变和逆变。

相关文章
|
5月前
|
安全 Java Kotlin
Kotlin泛型:灵活的类型参数化
Kotlin泛型:灵活的类型参数化
|
3月前
|
缓存 安全 Android开发
Android经典实战之用Kotlin泛型实现键值对缓存
本文介绍了Kotlin中泛型的基础知识与实际应用。泛型能提升代码的重用性、类型安全及可读性。文中详细解释了泛型的基本语法、泛型函数、泛型约束以及协变和逆变的概念,并通过一个数据缓存系统的实例展示了泛型的强大功能。
41 2
|
1月前
|
存储 安全 Java
Kotlin教程笔记(30) - 泛型详解
Kotlin教程笔记(30) - 泛型详解
30 3
|
1月前
|
存储 安全 Java
Kotlin教程笔记(30) - 泛型详解
Kotlin教程笔记(30) - 泛型详解
|
28天前
|
存储 安全 Java
Kotlin教程笔记(30) - 泛型详解
Kotlin教程笔记(30) - 泛型详解
22 0
|
1月前
|
存储 安全 Java
Kotlin教程笔记(30) - 泛型详解
本教程详细讲解了Kotlin中的泛型概念,包括协变、逆变、类型投影及泛型函数等内容。适合已有Java泛型基础的学习者,深入理解Kotlin泛型机制。快速学习者可参考“简洁”系列教程。
28 0
|
5月前
|
安全 Java 编译器
Android面试题之Java 泛型和Kotlin泛型
**Java泛型是JDK5引入的特性,用于编译时类型检查和安全。泛型擦除会在运行时移除类型参数,用Object或边界类型替换。这导致几个限制:不能直接创建泛型实例,不能使用instanceof,泛型数组与协变冲突,以及在静态上下文中的限制。通配符如<?>用于增强灵活性,<? extends T>只读,<? super T>只写。面试题涉及泛型原理和擦除机制。
39 3
Android面试题之Java 泛型和Kotlin泛型
|
Kotlin
Kotlin中接口、抽象类、泛型、out(协变)、in(逆变)、reified关键字的详解
Kotlin中接口、抽象类、泛型、out(协变)、in(逆变)、reified关键字的详解
100 0
|
安全 Java 编译器
Kotlin 泛型 VS Java 泛型
Kotlin 泛型 VS Java 泛型
84 0
|
存储 JSON 安全
From Java To Kotlin 2:Kotlin 类型系统与泛型终于懂了
上期主要分享了 From Java To Kotlin 1 :空安全、扩展、函数、Lambda。 这是 From Java to Kotlin 第二期。 带来 表达式思维、子类型化、类型系统、泛型。
228 0
From Java To Kotlin 2:Kotlin 类型系统与泛型终于懂了
下一篇
无影云桌面