Kotlin从入门到进阶实战
为何引入泛型,最引人注意的一个原因是为了创建容器类。集合类可以说是最常用的类之一,在没有泛型前,集合类是怎样持有对象的呢。在java中Object是所有类的根类。为了集合类的通用性,把元素类型定义为Object,当放入具体类型时,再进行相应的强制类型转换。在使用原生态类型实现的集合类中,使用Object[]数组。这种方式常见的问题有两个:
1.像集合中添加元素的时候没有对元素类型进行检查。
2.从集合中取元素的时候,不能都使用Object类型,需要进行强制类型转换。而转换过程由于添加的时候没有任何限值和检查,所以容易出错。(java.lang.ClassCastException):java.lang.Integer cannot be cast to java.lang.string
对于这样的代码,编译器不会报错,但是运行时可能会发生类型转换错误。
泛型最主要的优点就是让编译器追踪参数类型。执行检查和类型转换。
interface MyInterface {//类型参数放在接口名称后面:
operator fun next(): T //接口参数中直接使用类型T
}
使用如下,使用object关键字声明一个MyInterface实现类,并调用next()函数
fun test() {
val inter = object : MyInterface {
override fun next(): Int {
return Random().nextInt(100)
}
}
println(inter.next())
}
Kotlin中Map和MutableMap接口的定义也是一个典型的泛型接口的例子。
public interface Map {
//.........
public fun containsKey(key: K): Boolean
public fun containsValue(value: @UnsafeVariance V): Boolean
public operator fun get(key: K): V?
@SinceKotlin("1.1")
@PlatformDependent
public fun getOrDefault(key: K, defaultValue: @UnsafeVariance V): V {
// See default implementation in JDK sources
return null as V
}
public val keys: Set
public val values: Collection
public val entries: Set>
public interface Entry {
/**
* Returns the key of this key/value pair.
*/
public val key: K
/**
* Returns the value of this key/value pair.
*/
public val value: V
}
}
使用mutableMapOf初始化一个Map
mutableMapOf(1 to "A", 2 to "B", 3 to "C")
mutableMapOf函数如下:
public fun mutableMapOf(vararg pairs: Pair): MutableMap
= LinkedHashMap(mapCapacity(pairs.size)).apply { putAll(pairs) }
这里的K、V在泛型类型被实例化和使用时,将被实际的类型所替代。
泛型可以用来限制集合类持有的对象类型,这样使得类型更加安全。我们在集合里放入错误的类型编译器会报错。
Kotlin中有类型推断功能,有些类型参数可以省略不写。
mutableMapOf(1 to "A", 2 to "B", 3 to "C")
class Container(var key: K, var value: V) {
override fun toString(): String {
return "key = $key value = $value"
}
}
测试
fun test() {
val container = Container(1, "A")
println(container)
}
输出如下
key = 1 value = A
fun test(t: T) {
println(t)
}
interface MyInter {
fun go(t: T)
}
fun > gt(x: T, y: T): Boolean {
return x > y
}
在下面这函数中
fun > gt(x: T, y: T): Boolean {
return x > y
}
T : Comparable ,这里的Comparable是类型T的上界。也就是说类型T代表的都是实现了Comparable接口的类。这样等于告诉编译器它们都是事先了CompareTo方法。如果没有声明上界,就无法使用>操作。
我们来看一下问题场景。有下面存在子父类关系的类型。
open class Food
open class Fruit : Food()
class Apple : Fruit()
class Banana : Fruit()
class Grape : Fruit()
然后有下面的两个函数
object Text {
fun addFruit(fruit: MutableList) {
}
fun getFruit(fruit: MutableList) {
}
当向里面存放Apple的时候
val apples = mutableListOf(Apple(), Apple(), Apple())
addFruit(apples)//报错 Type mismatch
fun addFruit(fruit: MutableList) {
}
fun getFruit(fruit: MutableList) {
}
fun addApple(apple: MutableList) {
}
fun getApple(apple: MutableList) {
}
这样是重复的样板代码,那么怎么避免这个问题呢,让MutableList成为其父类型。java泛型中引入了类型通配符的概念来解决这种问题。java泛型通配符大概有两种方式:
这里的 ? ,称之为类型通配符。它们为一个泛型类所指定的类型集合提供了一个有用的类型范围。
Kotlin中用 out 和 in 代表 extends 和 super 所以上面的代码可以这么写 编译器就不会报错了。
object Text {
fun addFruit(fruit: MutableList) {
}
fun getFruit(fruit: MutableList) {
}
@JvmStatic
fun main(args: Array) {
val apples = mutableListOf(Apple(), Apple(), Apple())
addFruit(apples)
}
}
public static void main(String[] args) {
Integer[] integers = new Integer[3];
integers[0] = 0;
integers[1] = 1;
integers[2] = 2;
Number[] numbers = new Number[3];
numbers = integers;
for (Number number :numbers){
System.out.println(number);
}
}
在java中Integer是Number子类型,数组Integer[]也是Number[]子类型,因此任何需要Number[]值的地方都可以提供一个Integer[]值。所以说 java中数组是协变的。
同样是报错的。为什么Number对象可以由Integer实例化。而List< Number >不能由List< Integer >实例化呢?
List integers =new ArrayList<>();
这里的类型C是 Number或者其子类(Number Integer Float等)。父类F就是上界通配符?extends Number。但是这里不能像 integers 里面添加除了 null 以外的对象。
List integers = new ArrayList();
integers.add(null);//成功
integers.add(new Integer(1));//报错
如果能添加List< Number>的子类,也可以添加List< Integer>的子类,那么集合中的元素类型会比较混乱。我们无法判断哪个类型是Integer 哪个类型是Float,为了保持类型一致,java禁止像List中添加任意Number类型的对象。不过可以添加null。
List numbers = new ArrayList
这里的子类型C是“?super Number”,父类型F是Number的父类型,逆变的意义如下
public static void main(String[] args) {
List list = new ArrayList();
List list1 = new ArrayList
在逆变类型中,我们可以向其中添加元素。例如我们可以向List< ? super Number> list中添加Number以及子类对象
什么时候使用 extends 什么时候使用 super 呢,在java.util.Collections的copy方法中诠释了PECS。
public static void copy(List dest, List src) {
int srcSize = src.size();
if (srcSize > dest.size())
throw new IndexOutOfBoundsException("Source does not fit in dest");
if (srcSize < COPY_THRESHOLD ||
(src instanceof RandomAccess && dest instanceof RandomAccess)) {
for (int i=0; i di=dest.listIterator();
ListIterator si=src.listIterator();
for (int i=0; i
上界不能往里存,只能往外取
下界可以往里存,但往外取只能放在Object对象里