Kotlin细节文章笔记整理更新进度:
Kotlin系列 - 基础类型结构细节小结(一)
Kotlin系列 - 函数与类相关细节小结(二)
Kotlin系列 - 高阶函数与标准库中的常用函数(三)
前言
本篇文章从java
开始讲泛型,后面再切换到kotlin
,重点java
的泛型掌握住,koltin
的泛型就会很快掌握。(可自行选取节段食用,码字不易看完觉得还可以的,麻烦给赞,本人能力有限,有错误或者有问题在评论区留言,感激~~)
总结
- 虚拟机没有泛型,只有普通方法和类。
- 所有的类型参数都用它们的限定类型替换。
- 桥方法被合成用于保持多态。
- 为保持类型安全性,必要时插入强制类型转换。
一、泛型基础
1. 定义
泛型,也称参数化类型。可以使代码应用多种类型。使用类型参数,用尖括号括住,放在类名后面。在使用该类的时候用实际的类型替换该类型参数。
示例:
//这里的参数T可以自由命名
ClassA{}
2. 存在的意义
如果在程序中只能使用具体的类型、具体的基本类型,或者自定义的类,在编写多种类型的代码,这种限制会对代码有很大的约束。我们需要一种在可在运行是才确定类型的一种方法来实现代码更加通用。
示例:
public class PrintClass {
static public void printInt(Integer a, Integer b) {
System.out.println("参数a=" + a + "参数b=" + b);
}
static public void printFloat(Float a, Float b) {
System.out.println("参数a=" + a + "参数b=" + b);
}
static public void printDouble(Double a, Double b) {
System.out.println("参数a=" + a + "参数b=" + b);
}
}
改成泛型函数:
public class PrintClass1 {
static public void printMultiply(T a, T b) {
System.out.println("参数a=" + a + "参数b=" + b);
}
}
使用:
public static void main(String[] args) {
PrintClass.printDouble(10.0,10.0);
PrintClass.printInt(10,10);
PrintClass.printFloat(10f,10f);
PrintClass1.printMultiply(10.0,10.0);
PrintClass1.printMultiply(10,10);
PrintClass1.printMultiply(10f,10f);
PrintClass1.printMultiply("100","100");
}
-----------------------打印的Log---------------------------
参数a=10.0参数b=10.0
参数a=10参数b=10
参数a=10.0参数b=10.0
参数a=10.0参数b=10.0
参数a=10参数b=10
参数a=10.0参数b=10.0
参数a=100参数b=100
通过上面的展示,大家对泛型有个最基本的了解。
二、java的泛型使用
1. 接口泛型
- 接口泛型定义:
interface Animal {
void name();
void cry();
void mysteryData(T t);
}
- 接口泛型实现一
public class Cat implements Animal {
@Override
public void name() {
System.out.println("猫");
}
@Override
public void cry() {
System.out.println("喵喵");
}
@Override
public void mysteryData(String s) {
System.out.println("假设它拥有一种数据类型"+ s.getClass().getName());
}
}
- 接口泛型实现二
public class Dog implements Animal {
@Override
public void name() {
System.out.println("狗");
}
@Override
public void cry() {
System.out.println("汪汪汪");
}
@Override
public void mysteryData(T t) {
System.out.println("假设它拥有一种数据类型"+t.getClass().getName());
}
}
使用:
public static void main(String[] args) {
Dog dog = new Dog();
dog.name();
dog.cry();
dog.mysteryData(10);
Cat cat =new Cat();
dog.name();
cat.cry();
cat.mysteryData("String");
}
------------------------------log日志
狗
汪汪汪
假设它拥有一种数据类型java.lang.Integer
狗
喵喵
假设它拥有一种数据类型java.lang.String
2. 类泛型
上面接口泛型实现二
中就是用了类泛型的实现了。
3. 方法泛型
public class PrintClass1 {
static public void printMultiply(T a, T b) {
System.out.println("参数a=" + a + "参数b=" + b);
}
}
三、泛型类型变量的限定
有时候类、方法需要对类型变量进行约束。
例如:增加一个类,用于专门打印各种动物的叫声
(这里泛型虽然可以替换为Animal
,但是这个演示案例我就使用泛型替代,这个不是重点)
class AnimalCry{
public static void cry(T a){
a.cry();
}
}
这里你单纯这样子写,它不会有识别到
cry
这个方法,因为这个方法是Animal
接口的Cat
、Dog
等持有的方法,所以我们要给它个类型变量的限定。
class AnimalCry{
public static void cry(T a){
a.cry();
}
}
--------------------调用
public static void main(String[] args) {
AnimalCry.cry(new Dog());
}
--------------------打印的Log
汪汪汪
格式
后面可以跟
extends 接口
或者类名
。
T
:表示为绑定类型的子类型。
多个限定类型用&
进行多个限定,例如
四、泛型类型擦拭
java
的虚拟机中没有泛型类型对象,所有的对象都是普通类,无论何时定义一个泛型类型,都会自动体用一个对应的原始类型。原始类型的名字就是擦拭后的类型参数的类型名。如果没有限定的类型变量则用Object
代替,如果有限定则以限定的为代替。
public class PrintClass1 {
public static void printMultiply(T a, T b) {
System.out.println("参数a=" + a + "参数b=" + b);
}
}
--------------编译后为
public class PrintClass1 {
public static void printMultiply(Object a, Object b) {
System.out.println("参数a=" + a + "参数b=" + b);
}
}
class AnimalCry{
public static void cry(T a){
a.cry();
}
}
--------------编译后为
class AnimalCry{
public static void cry(Animal a){
a.cry();
}
}
五、约束与局限性
大多数的限制都是由类型擦拭带来的。
- 不能用基本类型实例化类型参数。
没有Pair
只有Pair
,因为泛型擦拭后,Pair
类含有Object
类型的域,而Object
不能存储double
值。 - 运行时类型查询只适用于原始类型
虚拟机的对象总是一个非泛型类型。所以,所有的类型查询只产生原始类型。
Pair p = new Pair("str","str1");
Pair i = new Pair(10,20);
// illegal generic type for instanceof 无法使用instanceof关键字判断泛型类的类型
if (p instanceof Pair)
//比较结果都是true,因为两次调用都是返回Pair.class
if (p.getClass() == i.getClass()){}
- 不能创建参数化类型的数组
Pair[] pairs = new Pair[10];
pairs[0] = new Pair(10,20);
//虽然能赋值但是会导致类型错误。
- 不能实例化类型变量或者实例化泛型数组
不能使用类似new T(...)
、new T[]
、T.class
等表达式的变量 - 泛型类的静态上下文类型变量无效
public class Singleton{
private static T singleInstance; //ERROR
public static T getSingleInstance(){ //ERROR
if(singleInstance == null)
return singleInstance;
}
}
------------------------------------类型擦除后被替换成Object具体类
public class Singleton{
private static Object singleInstance;
public static Obejct getSingleInstance(){
if(singleInstance == null)
return singleInstance;
}
}
----------------------------------------------调用的时候
错误,返回Object类型
AType a = Singleton.getSingleInstance();
错误,这种用法是不允许的,只能在调用方法或构造方法时传递泛型参数
AType a = Singleton.getSingleInstance();
- 不能继承Exception或者Throwable,不能抛出或捕获泛型类的实例,但可以将泛型限定的异常抛出
public class Pair extends Exception{} // 报错,不能继承Exception或者Throwable
public static void doWork(Class t){
try{
....
}catch(T e){ //报错 这里不能抛出泛型类型
}
}
//正确。
public static void doWork(T t) throws T{
try{
....
}catch(Throwable t){
}
}
六、泛型类型继承规则
- 两个泛型参数是继承关系,但是对应的两个泛型没有一点关系!
interface Animal{}
public class Cat extends Animal{}
public class Dog extends Animal{}
public class Cry{}
------------------------------------------------
Cry 与 Cry 不是继承关系,也没有什么关系。
- 泛型类可以扩展或实现其他的泛型类
public class ArrayList extends AbstractList
七、通配符类型(重点!!!)
小结:
协变:
extends Class>
指定泛型类型的上限,只能读取不能修改(修改是指对泛型集合添加元素,如果是remove(int index)
以及clear
当然是可以的)逆变:
super Class>
指定泛型类型的下线,只能修改不能读取,(不能读取是指不能按照泛型类型读取,你如果按照Object
读出来再强转也可以)
>
相当于< ? extends Object>
指定没有限制的泛型类型
以上面的图为例子:
1. 协变: extends Class>
指定泛型类型的上限
它的限定范围为上限本身(上限可以是接口)以及所有直接跟间接的子类。
- 应用场景
Animal animal = new Dog();// java的多态
List dogs = new ArrayList();
List animals = dogs; //这里会报错 incompatible types: List cannot be converted to List
上面的例子因为发生了类型擦拭,为了保证类型安全所以不允许这样子赋值。
这个时候就可以使用协变的写法 extends Class>
限制参数类型的上界,也就是泛型类型必须满足这个 extends
的限制条件。
List extends Animal> animals = new ArrayList(); // 本身
List extends Animal> animals = new ArrayList(); // 直接子类
List extends Animal> animals = new ArrayList(); // 间接子类
- 限制
只能够向外提供数据被消费,类似
生产者
。
List extends Animal> animals = new ArrayList();
Animal animal= animals.get(0); //get 出来的是 Animal 类型的
animals.add(textView);//报错,no suitable method found for add(TextView)
2. 逆变: super Class> 指定泛型类型的下限
它的限定范围为下限本身(下限可以是接口)以及所有直接跟间接的父类。
- 应用场景
List super ShamoDog> shamoDogs= new ArrayList(); // 本身
List super ShamoDog> shamoDogs= new ArrayList();//直接接父类
List super ShamoDog> shamoDogs= new ArrayList();//间接父类
- 限制
只能读取到
Object
对象,通常也只拿它来添加数据,也就是消费已有的List super ShamoDog>
,往里面添加Dog
,因此这种泛型类型声明相对协变
可以称为消费者
List super ShamoDog> shamoDogs = new ArrayList();
Object object = shamoDogs.get(0); // get 出来的是 Object 类型
Dog dog = ...
shamoDogs.add(dog); // add 操作是可以的
八、Kotlin的泛型
1. 格式
与java
泛型一样的格式
interface AnimalKot { } //接口泛型
class DogKot { } //类泛型
fun TestLooperManager(t:T): Unit { }//方法泛型
2. 关键字out
、in
、*
、where
-
out
:协变、与java
的上限通配符extends BoundType>
对应 -
in
:逆变,与java
的下限通配符super BoundType>
对应 -
*
: 与java
的>
,不过java
的是extends Object>
,kotlin
的是 -
where
: 与java
的
的&
符号对应
//where的示例
//java中多个限定泛型定义
public class Cry { }
//kotlin的对应写法
class Cry where T:Animal,T:Person{ }
重点:
kotlin
提供另外一种附加功能,在声明类的时候,给泛型类型加上in
关键字,表明泛型参数T
只会用来输入,在使用的时候就不用额外加in
。对应out
,则是表明泛型参数T
只会用来输出,使用时不需要额外加out
。
例子
//koltin的 List
public interface List : Collection {
}
var animalKot:List> = ArrayList>()// 不报错
var animalKot:List> = ArrayList>()//写了out,不报错
var animalKot:List> = ArrayList>()//报错,不能写in
//定义一个 in泛型类型
class All{
fun p(t:T){
}
}
var all:All> = All()// 不报错
var all:All> = All()//写了in,不报错
var all:All> = All()//报错,不能写in
3. 关键字reified
关于java
泛型存在擦拭的情况下,在上面五、约束性与局限性
中第二点中提到的
运行时类型查询只适用于原始类型
void println(Object obj) {
if (obj instanceof T) { // IDE提示错误,illegal generic type for instanceof
}
}
kotlin也是如此---------------------------------------------------------
fun println(any: Any) {
if (any is T) { // IDE提示错误,Cannot check for instance of erased type: T
}
}
java
的解决方法:额外传递一个 Class
类型的参数,然后通过 Class#isInstance
方法来检查
void println(Object obj, Class type) {
if (type.isInstance(obj )) {
}
}
Kotlin
的解决方法,就是reified
关键字,但是 reified
只能在inline
表示的方法中使用,所以,要使用inline
方法。
inline fun println(any: Any) {
if (any is T) {
println(item)
}
}
inline
内联函数,当方法在编译时会拆解方法的调用为语句的调用,进而减少创建不必要的对象。在kotlin中一个inline
可以被具体化reified
,这意味着我们可以得到使用泛型类型的Class
。
- 项目中使用的自定义扩展Gson
//定义`Gson`的扩展函数
inline fun Gson.fromJson(json:String):T = fromJson(json,T::class.java)
4. 注解 @UnsafeVariance
public interface List : Collection {
// Query Operations
override val size: Int
override fun isEmpty(): Boolean
override fun contains(element: @UnsafeVariance E): Boolean
override fun iterator(): Iterator
// Bulk Operations
override fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean
....
上面是kotlin
的List
源码,可以看到定义类泛型限定的时候为
为协变,只用来输出,但是这里面的方法override fun contains(element: @UnsafeVariance E): Boolean
为了支持输入,则使用@UnsafeVariance
避免IDE的检查
5. Kotlin 泛型与 Java 泛型不一致的地方
Java
里的数组是支持协变的,而Kotlin
中的数组Array
不支持协变。
Kotlin
中数组是用Array
类来表示的,这个Array
类使用泛型就和集合类一样,所以不支持协变。Java
中的List
接口不支持协变,而Kotlin
中的List
接口支持协变。
在Kotlin
中,实际上MutableList
接口才相当于Java
的List
。Kotlin
中的List
接口实现了只读操作,没有写操作,所以不会有类型安全上的问题,自然可以支持协变。
感谢
Kotlin 的泛型
Java核心技术 卷一