Java 与 Kotlin 中的泛型
泛型的意义
泛型的提出是为了编写重用性更好的代码
- JDK 1.5 之前 想提高代码重用性,可以使用 Object 作为属性或参数。但由于 Object 是所有类型的父类,导致获取的时候需要强转为指定的类型,这就导致了以下两个问题:
- 每次获取时都需要手动强转,降低了代码可读性。
- 强转操作无法在编译器确认是否异常,只能运行时确定,降低了代码稳定性。
- JDK 1.5 之后 针对上面问题,JDK1.5开始支持泛型。带来了以下好处:
- 编译器可确定类型转化是否有异常,有异常会报 ClassCastException,做到了早发现早治疗。
- 在获取时已确定数据类型,不需要做强转操作,保证了代码可读性。
泛型通配符
泛型通配符有三种:
- <?> 无限制通配符
- <? extends E> extends 关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类。
- <? super E> super 关键字声明了类型的下界,表示参数化的类型可能是指定的类型,或者是此类型的父类。
<?> 无限制通配符
使用场景: 在不关心实际操作的时候使用。但如果有数据操作就会报错,例如 get/set
举例:
public void test1(List<?> list){
String str = list.get(0); //编译期会提示,这里是 ? 无法转成指定 String 类
//但用 Object 接收就可以,不过也就无法保证代码可读性了
Object str = list.get(0);
}
public void test1(List<?> list){
Object str = list.get(0);
list.add(str); //编译器会提示,这里接收的是 ? 无法接收指定类型 Object
}
<? extends E> 上界通配符
使用场景:指定未知类型的属性或参数类型为 E 或 E的子类。只能用在 get 操作。
public void test(List<? extends Integer> list){
Integer str = list.get(0);
list.add(0);
}
<? super E> 下界通配符
使用场景:指定未知类型的属性或参数类型为 E 或 E的父类。只能用在 set 操作。
public void test(List<? super Integer> list){
Integer str = list.get(0);
list.add(0);
}
泛型类型参数
类型参数 与 通配符 区分:
- 类型参数 <T> / <T extends Number> / <T super Integer>
类型参数为指定确认类型,可放在泛型类定义中类名后面、泛型方法返回值前面、泛型方法参数
public class Test<T>{
public <T> T test(E t){}
}
- 通配符 <?> / <? extends Number> / <? super Integer>
通配符为未知的类型
public void addAll(List<? extends E> list){}
实际上通配符能做的事,类型参数也能实现
通配符形式可以减少类型参数,形式上往往更为简单,可读性也更好
//类型参数实现方式
public <T> void test(List<T> list){}
//通配符实现方式
public void test(List<?> list)
- 类型参数之间有依赖关系,或者返回值依赖类型参数,或者需要写操作,则只能用类型参数。
public <T> T back(T e){}
不变型
java 和 kotlin 都是不变型的。例如 List<String> 并不是 List<Object> 的子类。
List<String> strs = new ArrayList<String>();
List<Object> objs = strs; // !!!此处的编译器错误让我们避免了之后的运行时异常
objs.add(1); // 这里我们把一个整数放入一个字符串列表
String s = strs.get(0); // !!! ClassCastException:无法将整数转换为字符串
协变/上界
java 中是 <? extends Number>,kotlin 中是 <out Number>。只能读不能写。因为读的时候可以用上界接收,但写的话无法保证数据类型。
val list: ArrayList<out Number> = ArrayList<>()
list.add(0) //Error
list.add(1f) //Error
val number: Number = list.get(0) //Success
逆变/下界
java 中是 <? super Integer>, kotlin 中是 <in Integer>。只能写不能读。因为只能保证写入的数据类,无法保证读取类型是什么。
val list: ArrayList<in Integer> = ArrayList<>()
list.add(0) //Success
list.add(1f) //Success
val number: Integer = list.get(1) //Error
星投影/无限制通配符
java 中是 <?> ,kotlin 中是 <*> 。不能写,读只能读到顶层 Object/Any。
<?>/<*> 与 <Object>/<Any?>的区别:
前者是未知类型,约等于<? extends Object>/<out Any?>;
后者是确定的泛型类型。
类型擦除
原理
Java 编辑器会将泛型代码中的类型完全擦除,使其变成原始类型。
当然,这时的代码类型和我们想要的还有距离,接着 Java 编译器会在这些代码中加入类型转换,将原始类型转换成想要的类型。这些操作都是编译器后台进行,可以保证类型安全。
总之泛型就是一个语法糖,它运行时没有存储任何类型信息。
List<String> strings = ArrayList<>()
List<Number> numbers = ArrayList<>()
strings.getClass() == numbers.getClass() // true
针对擦除问题的解决方法
- 类型擦除时会寻找第一个上界,如果未指定上界则为 Object,所以可以通过 <? extends E> 来指定上界。
public class Test <T extends Number & Custom>{
T number;
}
//类型擦除后,number 指定为上界 Number
public class Test{
Number number;
}
//未指定上界时
public class Test <T>{
T number;
}
//类型擦除后,number 未找到上界,则为 Object
public class Test{
Object number;
}
- 通过 getGenericReturnType() 获取泛型返回类型
//例如,泛型返回参数
fun getDeviceMode(): Call<BaseResp>
//通过 getGenericReturnType() 可以获取泛型返回类型
@Test
fun test(){
println("${TestCls::class.java.getMethod("testReturn").genericReturnType}") //java.util.List<java.lang.String>
println("${TestCls::class.java.getMethod("testReturn").genericReturnType is ParameterizedType}") //true
println("${(TestCls::class.java.getMethod("testReturn").genericReturnType as ParameterizedType).rawType}") //interface java.util.List
println("${(TestCls::class.java.getMethod("testReturn").genericReturnType as ParameterizedType).actualTypeArguments[0]}") //class java.lang.String
}
class TestCls{
fun testReturn(): List<String>{
return listOf()
}
}
- 添加包装类,通过 getGenericSuperclass() 获取泛型参数类型
@Test
fun test(){
println("${A::class.java.genericSuperclass}") //TestCls<java.lang.String>
println("${A::class.java.genericSuperclass is ParameterizedType}") //true
println("${(A::class.java.genericSuperclass as ParameterizedType).actualTypeArguments[0]}") // class java.lang.String
}
class A : TestCls<String>()
open class TestCls<T>
参考资料