范型--Java与kotlin
缘起(为解决什么问题而生)
- 加强类型安全
- 减少类型强转次数
- 限定参数或返回值范围
类型安全
- 类型赋值检查
- 类型调用检查
协变与逆变
Java实现
先看一个常见的例子
TextView tv = new Button(context);
List<Button> buttons = new ArrayList<Button>();
List<TextView> textViews = buttons;// 报错
Java的范型类型会在编译过程进行类型擦除,为了保证类型安全,不允许这样赋值。
但在实际应用中,我们经常需要这种赋值,Java中提供范型通配符? extends
和 ? super
来解决这个问题
-
上界通配符
? extends
List<Button> buttons = new ArrayList<Button>(); List<? extends TextView> textViews = buttons;//合法 TextView textView = textViews.get(0);// 合法 textViews.add(textView)//报错
add报错的理解:
List<? extends TextView>
类型未知,他可能是List<TextView>
,也可能是List<Button>
。对于List<Button>
,调用add
显然是不可以的,实际情况是,编译器无法确定究竟属于哪一种,因此无法执行下去,就报错了。
-
下界通配符
? super
List<? super Button> buttons = new AyyayList<Button>()//合法 List<? super Button> buttons = new AyyayList<TextView>()//合法 List<? super Button> buttons = new AyyayList<Object>()//合法 Object object = buttons.get(0)//get 出来的都是Object类型 Button button = new Button(context); buttons.add(button)// 合法
?
表示未知类型,所以获取出来的数据可以用Object
来接,虽然不知道List
中存放的类型究竟是哪种类型,但是Button
一定是这些类型中的子类型,因此可以调用add
PECS 法则:「Producer-Extends, Consumer-Super」。
使用范型通配符? extends
来使范型支持协变,但是只能读不能修改(不能使用add方法)
使用范型通配符? super
来使范型支持逆变,但是只能写不能读(获取到的值都是Object类型)
kotlin实现
使用关键字 out
来支持协变,相当于Java中的 ? extends
使用关键字 in
来支持逆变,相当于Java中的? super
*
Java
中 ?
单独使用,相当于 ? extends Object
kotlin
中等效于 *
,相当于 out Any
多边界
Java中用 &
连接多个边界
class Monster <T extends Animal & Food>{
}
kotlin 中使用 where
关键字
class Monster <T> where T :Animal, T :Food
类型擦除
Kotlin java
为泛型声明用法执行的类型安全检测仅在编译期进行。 运行时泛型类型的实例不保留关于其类型实参的任何信息。 其类型信息称为被擦除。例如,Foo<Bar>
的实例会被擦除为 Foo<*>
。
-
自动类型转换
编译阶段编译器进行类型强转 -
多态冲突
例父类class Parent<T>{ private T valte; public T getValue(){ return value; } public void setValue(T value){ this.value = value; } }
子类
class Child extends Parent<String> {
@override
public void setValue(String value){
super.setValue(value);
}
@overide
public String getValue(){
return super.getValue();
}
}
子类生成的字节码反编译过来,有四个方法
setValue(String value)//重写的方法
setValue(Object value)//编译器生成的桥方法
String getValue()//重写的方法
Object getValue()//编译器生成的桥方法
编译器生成的桥方法的内部实现,就只是去调用我们自己重写的那两个方法
范型实例化
Java实现方式
new T()无法实现,部分原因是因为擦除,而另一部分原因是因为编译器不能验证T是否具有默认构造器。
直接传入T 对应的Class或者Type,类内部直接通过反射创建
class. newInstance()
kotlin实现方式
在kotlin中,由于范型的强化以及阻止才出等特性的存在,使得范型实例化成为可能。
通过 inline 和 reifiied 可以保证泛型类型被实化
inline fun <reified T: Any> new(): T {
val clz = T::class.java
val mCreate = clz.getDeclaredConstructor()
mCreate. isAccessible = true
return mCreate. newInstance()
}
带参范型数实例化
inline fun <reified T: Any> new(vararg params: Any): T {
val clz = T::class.java
val paramTypes = params.map { it::class.java }.toTypedArray()
val mCreate = clz.getDeclaredConstructor(*paramTypes)
mCreate. isAccessible = true
return mCreate. newInstance(* params)
}