文章目录
KotLin 相关文档
官方在线Reference
kotlin-docs.pdf
Kotlin for android Developers 中文翻译
Kotlin开发工具集成,相关平台支持指南
Kotlin开源项目与Libraries
Kotlin开源项目、资源、书籍及课程搜索平台
Google’s sample projects written in Kotlin
Kotlin and Android
前言
本文建于17年初学kotlin时。当时也看过很多文章,写过例子,包括之前多年也在使用java泛型;然后直到现在19年底了,在应用java、kotlin泛型时还是有些混乱。
可能多数人和我一样,用的最熟悉的就是 类似
List<String> list = new ArrayList<String>;
<T> T update(T t);
<T> T find(List<? extends T> list, int index) { return list.get(index); }
class Clz<T> { }
class Clz<T extends OtherClz> { }
其它,
<? super T>
、<?>
,貌似印象中没有使用实际场景使用过。
写例子时,发现,如 编译没问题,运行报错了;感觉用的没问题,编译报错了。
java 和 kotlin 的泛型型变原理是一样的吗?关键字都对得上吗?关键字能出现的地方是一样的吗?
这要是面试问题,我估计我已经挂了 (-_-。)
…
晚上两点多了,先休息了,后面慢慢修改。 updated on 2019-11-18
Java 泛型的简单示例
class Box<T> {
T t;
set(T t) {this.t = t;}
T get(String key) { return t }
}
Box 关联 T 类型,实际就是任意类型,或者说是任意的 Object 及其子类型。
Kotlin 泛型的简单示例
class Box<T>(t: T) { //构造函数有T类型的参数
var value = t
}
定义一个使用泛型参数的实例:
val box: Box<Int> = Box<Int>(1)
如果,省略泛型参数,类型会自动推测:
val box = Box(1) //T 是 Int 类型
什么时候用泛型
一个类、接口、函数、参数、返回值在要指明所关联的类型为某一特定类型或者就是任意类型,或者是 其子类或父类时,可以使用泛型。所以,泛型形参出现的位置,不是固定的。
当要写个sdk、工具类啥的,在调用(特别是其他开发人员来调用)的时候有类型的限定,以免出现错误的使用,或者在编译期直接就报错,而非运行时或产品上了线,才发现错误。
型变
型变特性,分 变和不变;变又分 协变与逆变
简单的描述 协变与逆变,它们有一个共同的前提,即出现在"有继承或实现"关系的一组类型中。
协变:父类出现的地方,可以用子类代替(符合面向对象的基本原则,里氏替换原则,或者说多态)。
逆变:子类出现的地方,可以用父类代替。
不变:声明的是什么类型,使用或传递的就要是什么类型。
注:以上只是协变与逆变的基本概念,在泛型中使用这两个特性,还有其它要注意的地方。
java中数组是协变的
- 数组在java中,默认就是协变的
如下代码,在java中允许的:
String[] iAry = new String[] {"abc"}; //这是不变的
Object[] objects = iAry; //这里是协变的
再加一句:
objects[0] = 3; //能编译,运行报错了
运行后,会发生异常。因objects[0] 实际存储的是一个String类型的元素,而这里被赋值为一个Integer型。
这个例子,说明了,数组的协变使用是可能会发生问题的
。使用数组的协变特性就要小心了,或者最好不要用这个特性。
Kotlin中的数组型变
在Kotlin中的数组,sdk定义了一个Array<T>这样的数组类型,任何kotlin数组,都继承了它。所以,Kotlin中数组型变,遵循泛型型变的特性。
泛型不变
比如,java代码:
List<String> = new ArrayList<String>();
Box<Integer> = new Box<Integer>();
声明泛型参数类型,和赋值的泛型参数类型需要相同。
若
List<String> = new ArrayList<Integer>();
Box<Integer> = new Box<String>();
这样是编译不过的,因为这是不变式的应用
java泛型协变
例如,
// Java
interface ACollection<E> {
void addAll(ACollection<E> items);
}
class Test {
void copyAll(ACollection<Object> to, ACollection<String> from) {
to.addAll(from); //error
}
}
根据泛型不变特性,这里的 to.addAll(from);
是无法编译通过的。
现在,做如下修改:
// Java
interface ACollection<E> {
void addAll(ACollection<? extends E> items);
}
这里方法中,声明泛型参数时,加上了 ? extends
这样的通配符与关键字。
直译就是,E的子类/实现类。 匹配到copyAll方法的 to.addAll
方法中,就是
ACollection<Object> {
void addAll(ACollection<String extends Object> items);
}
加上了 “? extends” 这样的通配符定义后,就为对应的泛型参数的类型,指定了上限
再来看个例子:
List<? extends Object> list = new ArrayList<>();
list.add("abc");//error
这里的list.add()
的完全语义:list.add(? extends Object)
,示例中的类型完全是符合的,但是在编译器中,直接报错了。
本例,与上一个例子的区别在哪里呢:
上例
// Java
interface ACollection<E> {
void addAll(ACollection<? extends E> items);
}
在接口中的方法定义上使用了协变语法,而在后期使用addAll()
的对象,是已经确定泛型参数类型的对象:ACollection<Object>
和ACollection<String>
而本例中的list对象,它直接就是一个协变语法定义的对象:List<? extends Object> list
;对应的,它的add()的语义:add(? extends Object)
这里的理解:list不知道它的具体泛型参数类型是Object,还是Object的某个子类型,而这样一个不知道它关联哪种具体类型的集合对象list,又要add一个不知道类型的对象,所以这是不支持的。
所以,在定义函数时,参数类型上 使用 协变式 泛型参数,
Kotlin泛型协变
Kotlin中协变语义的声明位置与java中不完全相同,可声明在 类或接口 的定义上,
如:
class Box3<E> {
fun copy(from: Array<out E>, to: Array<in E>) {
for (index in from.indices) {
to[index] = from[index]
}
}
}
kotlin中,引用了生产者-消费者的概念来对应 泛型协变与逆变。
协变,对应生产者。即相关函数调用,是产生一个与泛型类型同类型的对象。使用关键字 out
。
如果将上面to: Array<E>
定义成 to: Array<out E>
那就会报错了。
因to[index]
在等式的左边,它不是一个被直接用来产生对象的函数。
结论:协变 — out — 生产者 — 直接产生泛型类型对象
java中泛型逆变
例,
void add(List<? super Integer> foo) {
foo.add(3); //只能是integer
// foo.add(new Object());//报错, 只能是integer
}
这里理解:foo对象不知道要放什么样的Integer的超类型对象,所以只能放Integer对象。
泛型逆变,确定泛型参数类型的下限
如上例,再加一句代码:
Integer a = foo.get(0);//error, 编译 不通过
因foo的定义,它不知道泛型参数对应Integer的何种基类型,所以调用get(),就不知道返回的是哪种类型了。
Kotlin泛型逆变
例,
fun copy2(from: Array<in Any>, to: Array<Any>) {
for (index in from.indices) {
// to[index] = from[index] //error
from[index] = to[index]
}
}
这里的 <in T>
关键字,就表示 这是一个逆变的泛型声明;类中所有用到T类型的地方,都要遵循逆变的特性。即支持消费对象,而不支持生产对象
结论:逆变 — in — 消费者 — 用于消费对象或者写入对象
泛型参数定义在其它位置
- 在top级函数中使用泛型
即在kotlin-file中的,最外层使用泛型,如
文件Test.kt下:
fun <T> get(t: T): T {
return t
}
直接在top级函数中使用泛型, 这里的泛型不能 定义为 in 或 out
- 直接定义在类或接口声明中
如下,
interface AG<T> {} //这时是可以带型变关键字的
若无型变关键字,表示,对与 协变与逆变 都支持
星号类型推断
interface MyFun<in T, out U> {
fun testp(t: T): U
fun inParam(t: T)
fun outValue(): U
}
fun test11(m: MyFun<*, String>) {//in 声明成*, 表示 in Nothing 即不能写入任何东西,因为此时不知道*是什么类型的
// m.testp()
// m.inParam()
m.outValue()
}
fun test12(m: MyFun<Int, *>) {//out 声明成*,表示 out U
val result = m.testp(33)
println("test12: " + result)
m.inParam(34)
m.outValue()
}
fun test13(m: MyFun<*, *>) {// <in Nothing, out U>
// m.testp()
// m.inParam()
m.outValue()
}
当不知道in的类型时,用 *
表示 in类型, 可以安全地 防止 写入
out是读取的动作,用*
表示,与原意一样
泛型函数
如下定义一个普通的泛型函数:
fun <T> singletonList(item: T): List<T> {
// ...
}
利用泛型参数,进行函数扩展:
fun <T> T.basicToString() : String { // extension function
// ...
}
调用泛型函数:
val l = singletonList<Int>(1)
val s = singletonList(1)//泛型类型自动推断
泛型类型约束
- 最常见的一种约束是一个上边界约束, 对应于Java的extends关键字:
fun <T : Comparable<T>> sort(list: List<T>) {
// ...
}
<T : Comparable<T>>
表示T要继承或实现自Comparable<T>
sort函数调用:
sort(listOf(1, 2, 3)) // OK. Int is a subtype of Comparable<Int> sort(listOf(HashMap<Int, String>())) // Error: HashMap<Int, String> is not a subtype of Comparable<HashMap<Int, String>>
- 指定空类型上边界
fun <T: Any?> test():T? {
return null //即使上边界为Any?,在使用时也要加上 ?
}
- 对同一个泛型参数,指定多个上边界
在声明泛型参数时,只能指定一个上边界,若要指定多个,需要用where关键字:
fun <T> cloneWhenGreater(list: List<T>, threshold: T): List<T>
where T : Comparable,
T : Cloneable {
return list.filter { it > threshold }.map { it.clone() }
}