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