频道栏目
首页 > 资讯 > Java > 正文

Kotlin语法(十二)-泛型(Generics)

16-09-05        来源:[db:作者]  
收藏   我要投稿

泛型类

跟Java一样,Kotlin也支持泛型类:

classBox(t: T) {

var value = t

}

在具体使用的时候,需要传入具体的类型:

valbox: Box = Box(1)

另:通过值可以推断出类型,也可以省略类型参数:

// 1 has type Int, so the compiler figures out that we are talking about Box
val box = Box(1)

 

变异(Variance)

Java通配符(wildcard types)

该部分都是讲的Java泛型中的通配符。

在java泛型使用中,提供类型通配符“?”,这块只是简单介绍java通配符的设计目的及基础,详细可以自行去了解下“Java 泛型通配符?”,如:

//Java

voidparseList(ListdataList) {

//… …

}

EffectiveJava》中解析,使用通配符为了提高API的使用灵活性(Use bounded wildcards to increase APIflexibility)。

因为在java中,泛型类型是不可变的,比如:“List”不是“List”的子类型,而是两个独立的类型,如下:

//Java

Liststrs = new ArrayList();

Listobjs = strs; //编译错误,类型不匹配(imcompatible type)
假设前面的方式可行的话,会带来更多的新问题,导致在使用时类型不匹配问题:
//Java
objs.add(1); // Here we put an Integer into a list of Strings
String s = strs.get(0); // !!! ClassCastException: Cannot cast Integer to String

 

所以,为了确保在运行时类型安全,Java的泛型设计成了不可变。

但是这种不可变的泛型类型,又会带来下面的问题:

现在定义一个泛型,是为“Collection”增加“addAll()”方法,应该是下面实现方式:

// Java

interfaceCollection ... {

void addAll(Collection items);

}
那么在使用的时候,往“Collection”中添加“Collection”,该方法就没法使用了;而理论上应该是合法的,String是Object的子类,String实例是可以添加到Object的集合中的:

// Java

voidcopyAll(Collectionto, Collection from) {

to.addAll(from); // !!! Would not compilewith the naive declaration of addAll:

//Collection is not a subtype ofCollection

}

为了解决上面的问题,Java中使用了类型通配符方式,如“? extends T”表示T 及T的子类参数都可以使用,实现如下:

// Java

interfaceCollection ... {

void addAll(Collectionitems);

}

 

通配符的上界

通配符类型参数(wildcard type argument):“? extends T”(T表示通配符的上界),表示该方法可以接收T及T的子类参数。意味着可以安全的读“T” (所有的实例都是T的子类)的实例;但不能向其添加元素,因为没法确定添加的实例类型跟定义的类型是否匹配,无法检验这个操作的安全性:

//Java

//Bird Cat都是Animal的子类

public voidtestAdd(Listlist) {

    //下面都会报编译错误的

//因为testAdd方法传入的list的类型不确定的,就没法确保它可以添加下面的全部类型

//若传入“List”,只能add(bird),只有②可以添加

//若传入“List”,都不能添加

list.add(new Animal("animal")); //①

list.add(new Bird("bird"));  //②

list.add(new Cat("cat"));//③

}

 

可以理解成:“Collection”是“Collection”的子类型。

这种通配符(wildcard)是通过继承一个范围类(通配符上界,upper bound)来实现类型协变。

 

通配符的下界

通配符类型参数(wildcard type argument):“? super T”(T表示通配符的下界)。

如:“Collection”是“Collection”的父类型;可以调用集合将String作为参数的方法(如:add(String)orset(int, String));但当从集合中获取元素时,得到是Objec对象而不是String对象。

 

PECS

PECS stands for Producer-Extends,Consumer-Super

1) 通配符的上界方式,只能从中读取元素,不能添加元素,形象的称为生产者(Producers

2) 通配符的下界方式,只能添加元素,没法直接读取下界类型的元素,形象的称为消费者(Consumers

 

Kotlin泛型

Kotlin没有提供相关的类型通配机制,而是通过下面两种方式:

? 声明位置变异(declaration-site variance)

? 类型推测(type projections)

 

声明位置变异(declaration-site variance)

在Java中,假设有一个泛型类“Source”,只有一个方法,返回参数T,没有使用T作为参数的方法:

 

// Java

interfaceSource {

T nextT();

}

 

 

现在,“Source”实例作为一个“Source”参数应该是类型安全的,因为“Source”类没有消费者方法(consumer-methods,见PECS部分的解析);但是Java编译器不清楚这点,所以判定为非法:

 

// Java

voiddemo(Source strs) {

Sourceobjects = strs; // !!! Not allowed in Java

// ...

// Sourceobjs = strs; //valid

}

 

在Java中,为解决这种情况,需要声明为“Source”类型;实际上这种方式没什么意义,新实例调用的还是原来实例一样的方法,定义的更复杂类型并没有添加新值。但是java的编译器不知道。

 

在Kotlin中,使用声明位置变异(declaration-site variance)方式来处理这种问题。

声明位置变异:通过将参数T注解成只能作为返回值而不是作为传入参数;使用“out”关键字标识。

abstract class Source {
abstract fun nextT(): T
//下面的函数编译错误: type parameter T is declared as 'out'
// abstract fun add(value: T)
}


fun demo(strs: Source) {
val objects: Source = strs // This is OK, since T is an out-parameter
// ...
}

通用规则:当一个类C的参数化类型使用“out”关键字修饰,那么该参数化类型只能作为类C中函数的返回值;“C

”可以作为“C”的父类型返回。

即,类C在参数T上是协变的,T是一个协变类型参数;可以理解C是参数T的生产者(producer)而非消费者(consumer)。

 

out”修饰符称为异变注解;当在类型参数的声明位置使用它,称之为声明位置变异(declaration-site variance)。跟Java使用位置变异(use-site variance)对比,Java是在使用位置使类型协变。

 

有“out”对应,Kotlin还提供一个互补的变异注解:“in”,它使类型参数逆变(contravariant),即修饰的类型参数只能作为一个消费品(consumed)而不能作为一个生产品(produced)。“Comparable”类就是一个很好使用“in”的逆变实例:

 

abstract class Comparable {
abstract fun compareTo(other: T): Int
}


fun demo(x: Comparable) {
x.compareTo(1.0) // 1.0 has type Double, which is a subtype of Number
// Thus, we can assign x to a variable of type Comparable
val y: Comparable = x // OK!
}

 

 

类型推测(Type projections)

使用位置变异:类型推测

将类型参数T使用“out”修饰,可以非常方便解决使用位置泛型子类问题。但是向下面这种,既有做返回参数又有做函数入参的:

 

class Array(val size: Int) {
fun get(index: Int): T { /* ... */ }
fun set(index: Int, value: T) { /* ... */ }
}

 

 

该类不能对类型参数T做变异或逆变;考虑到有下面一个函数:

 

fun copy(from: Array, to: Array) {
assert(from.size == to.size)
for (i in from.indices)
to[i] = from[i]
}

 

 

该函数将一个数组中的元素复制到另外一个数组,现在来具体使用下:

 

val ints: Array= arrayOf(1, 2, 3)

val any =Array(3)

copy(ints, any) // Error: expects(Array, Array)

 

 

现在出现了前面java部分关于泛型参数传递的问题。

泛型类型参数是不可变的,那么“Array”与“Array”不是子父类关系,故无法将“Array”的实例当做“Array”使用。

 

只需要确保“copy()”能正常即可;那么需要禁止“from”不能添加,可以这样:

 

fun copy(from:Array<out Any>, to:Array) {

// ...

}

 

 

这种实现方式,称为类型推测(type projection):参数“from”不是一个简单的数组,而是受限制的(限制为一个生产者,projected),它只能调用返回参数为T的方法,意味着只能调用“get()”函数。

就是一种使用位置变异(use-site variance),类似于Java中的“Array”,实现起来较为简单些。

 

另外与“out”对应的是“in”,用法如下:

 

fun fill(dest:Array, value: String) {

// ...

}
“Array”类似于Java中的“Array”。

 

 

 

主角推测(Star-projections)

有时候,对类型参数没有任何了解,但又需要安全的使用。一种安全的方式是,定义一个该泛型类型的推测(projection)类型,使泛型类型的每一个具体实例应该是推测(projection)类型的子类型。

 

在Kotlin中,称之为主角推测(star-projection),语法规则:

? 对于“Foo”,“T”是一个协变类型参数;“TUpper”表示类型参数上界(upper bound);那么“Foo<*>”等同于“Foo”。当无法知道“T”时,可以从“Foo<*>”安全的读取得到“TUpper”。

? 对于“Foo”,“T”是一个逆变类型参数;“Foo<*>”等同于“Foo”;意味着当“T”不可知时,不能往“Foo<*>”添加任何元素。

? 对于“Foo”,“T”是一个不可变的类型参数;“TUpper”表示类型参数上限;当从中读取数据时“Foo<*>”同等于“Foo”;当向其添加数据时,“Foo<*>”等同于“Foo”。

 

若一个泛型类型,有几个类型参数,每个类型参数都可以单独进行推测(projected)。比如,一个泛型定义“interface Function”,那么可以得到下面的主角推测(Star-projections):

? “Function<*, String>”等同于“Function

? “Function”等同于“Function

? “Function<*, *>”等同于“Function

 

注:主角推测(Star-projections)跟Java中的原始类型(raw types)非常相似,但是更安全。

 

泛型函数(Genericfunctions)

不仅类可以有类型参数,函数也可以。下面是一个使用类型参数的函数使用:
fun singletonList(item: T): List {
// ...
}


fun T.basicToString() : String { // extension function 扩展函数
// ...
}
要使用一个泛型函数,在函数名称后面指定具体的类型参数:

 

vall = singletonList(1)

 

 

泛型约束条件(Generic constraints)

上界(Upper bounds)

在Kotlin中,泛型约束大部分是类型上界限制,对应于Java中的“extends”,如:

fun > sort(list: List) {

// ...

}
该函数限制只有“Comparable”的子类才能够作为“T”:

 

sort(listOf(1, 2, 3)) // OK. Int is a subtype of Comparable


// Error: HashMap is not a subtype of Comparable<>>
sort(listOf(HashMap()))

 

默认的类型上界(未明确定义上界)是“Any?”。在“<>”一次只能定义一个上界,如果相同类型参数需要定义多个上界,可以通过使用“where”关键字来实现:

fun cloneWhenGreater(list: List, threshold: T): List

whereT : Comparable,

T : Cloneable {

return list.filter { it > threshold }.map{ it.clone() }

}

 

相关TAG标签
上一篇:Android -- Wifi热点的打开与关闭流程简介
下一篇:java集合类
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站