什么是泛型
泛型指的是,在定义类,方法,接口时将类型作为参数,从而使同一份代码能够在不同类型间复用的能力。
为什么要使用泛型
使用泛型的代码比非泛型的代码有许多优点:
- 在编译时进行更强的类型检查,编译器对泛型代码应用强类型检查,如果代码违反了类型安全,则发出错误。修复编译时错误比修复运行时错误更容易
- 使程序员能够实现泛型算法。通过使用泛型,程序员可以实现适用于不同类型集合的泛型算法,可以进行定制,并且类型安全且易于阅读
public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.flatMapTo(destination: C, transform: (T) -> Iterable<R>): C {
for (element in this) {
val list = transform(element)
destination.addAll(list)
}
return destination
}
- Java中,如果不使用类型,会有强制转换的风险,但kotlin强制要求传递类型``
// 未声明类型,默认为Object
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);
List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0); // no cast
Generic Types
Box
泛型类型是通过类型参数化的泛型类或接口。下面的 Box 类将被修改以演示这个概念。
public class Box {
private Object object;
public void set(Object object) { this.object = object; }
public Object get() { return object; }
}
因为它的方法接受或返回一个 Object,所以您可以自由地传入任何您想要的内容,只要它不是基本类型。但是这种写法没有办法在编译时验证类的使用方式。代码的一部分可能会在Box放置一个 Integer 并期望从中获得 Integer 类型的对象,而另一部分代码可能会错误地传入一个 String,导致运行时错误。
Box的泛型版本
泛型类的定义格式如下:
/**
* Generic version of the Box class.
* @param <T> the type of the value being boxed
*/
public class Box<T> {
// T stands for "Type"
private T t;
public void set(T t) { this.t = t; }
public T get() { return t; }
}
Bounded Type
有时可能希望限制可用作参数化类型中的类型参数的类型。例如,对数字进行操作的方法可能只希望接受 Number 或其子类的实例。 若要声明有界类型参数,需要列出类型参数的名称,然后是 extends 关键字,最后是它的上界,在本例中为 Number。注意,在这个上下文中,extends 在一般意义上被用来表示“ extends”(如类)或“ implements”(如接口)。
public class Box<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
public <U extends Number> void inspect(U u){
System.out.println("T: " + t.getClass().getName());
System.out.println("U: " + u.getClass().getName());
}
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
integerBox.set(new Integer(10));
integerBox.inspect("some text"); // error: this is still String!
}
}
除了限制可用于实例化泛型类型的类型之外,有界类型参数还允许调用边界中定义的方法:
// 在kotlin中 extends对应out in对应super
public class NaturalNumber<T extends Integer> {
private T n;
public NaturalNumber(T n) { this.n = n; }
// n是Integer或者其子类,所以存在intValue的方法
public boolean isEven() {
return n.intValue() % 2 == 0;
}
}
多个类型约束
前面的例子演示了一个类型参数的使用,它只有一个边界,但是一个类型参数可以有多个边界:
Class A { /* ... */ }
interface B { /* ... */ }
interface C { /* ... */ }
// kotlin中也可以,需要where关键字
// fun <T> test2() where T : List<String>, T :Comparable<String> {}
class D <T extends A & B & C> { /* ... */ }
泛型与继承
与继承一样,子类的泛型可以更加细化
interface GenericBase<T:Person>{
fun set(collection: List<T>)
fun get(): Collection<T>
}
interface GenericSub<T:Teacher>: GenericBase<T>{
override fun get(): List<T>
}
interface GenericSub2<T:Doctor>: GenericBase<T>{
override fun get(): ArrayList<T>
}
逆变与型变
一般原则是:当一个类 C 的类型参数 T 被声明为 out 时,它就只能出现在 C 的成员的输出-位置(方法的返回值),但回报是 C 可以安全地作为 C的超类。 简而言之,他们说类 C 是在参数 T 上是协变的,或者说 T 是一个协变的类型参数。 你可以认为 C 是 T 的生产者,而不是 T 的消费者。 out修饰符称为型变注解,并且由于它在类型参数声明处提供,所以我们称之为声明处型变。 这与 Java 的使用处型变相反,其类型用途通配符使得类型协变。 另外除了 out,Kotlin 又补充了一个型变注释:in。它使得一个类型参数逆变:只可以被消费而不可以被生产,即只可以出现在方法的输入-位置(方法的入参)
Joshua Bloch 称那些你只能从中读取的对象为生产者,并称那些你只能写入的对象为消费者。他建议:“为了灵活性最大化,在表示生产者或消费者的输入参数上使用通配符类型”,并提出了以下助记符:
PECS 代表生产者-Extends、消费者-Super(Producer-Extends, Consumer-Super)。
// 测试类型的上下界
fun testBound() {
// out 表示 存入的值只能是Teacher或者Teacher的子类,但是不知道其具体类型
// out 同时也表示producer 即被范性约束的类的与类范型相同的方法的返回值的类型必须为Teacher
val outTeachers: MutableList<out Teacher> = arrayListOf(Teacher())
//所以不能加人任何元素
// teachers.add(Person()) // compile error
// teachers.add(Teacher()) // compile error
// teachers.add(Student()) // compile error
// 但是取出的元素必然为Teach类型或者其子类
val teacher = outTeachers[0] // compile success
// 必然为true
assert(teacher is Teacher)
// in 表示存入的元素是Teacher或者Teacher的子类
// 同时也是消费者,即被范性约束的类的与类范型相同的方法入参的类型必须为Teacher
val inTeachers: MutableList<in Teacher> = ArrayList()
inTeachers.add(Doctor())
inTeachers.add(Teacher())
// 不知道具体的类型,所有范型约束的方法的返回值都变为Any
val get: Any? = inTeachers.get(0)
val personList = listOf<Person>()
val teacherList = listOf<Teacher>()
println(teacherList)
val map: MutableMap<in Number, out Any> = mutableMapOf()
// 应为V 为out 所以只能读
//Type mismatch.
//Required: CapturedType(out Any)
//Found: String
//map["sample"] = "sample"
val values: MutableCollection<out Any> = map.values
val keys: MutableSet<in Number> = map.keys
keys.add(1)
keys.add(1.0)
通配符
有时你想说,你对类型参数一无所知,但仍然希望以安全的方式使用它。 这里的安全方式是定义泛型类型的这种投影,该泛型类型的每个具体实例化将是该投影的子类型。 Kotlin 为此提供了所谓的星投影语法:
- 对于 Foo ,其中 T 是一个具有上界 TUpper 的协变类型参数,Foo <> 等价于 Foo 。 这意味着当 T 未知时,你可以安全地从 Foo <> 读取 TUpper 的值。
- 对于 Foo ,其中 T 是一个逆变类型参数,Foo <> 等价于 Foo 。 这意味着当 T 未知时,没有什么可以以安全的方式写入 Foo <>。
- 对于 Foo ,其中 T 是一个具有上界 TUpper 的不型变类型参数,Foo<*> 对于读取值时等价于 Foo 而对于写值时等价于 Foo。
如果泛型类型具有多个类型参数,则每个类型参数都可以单独投影。 例如,如果类型被声明为 interface Function ,我们可以想象以下星投影:
- Function<*, String> 表示 Function;
- Function 表示 Function;
- Function<*, *> 表示 Function。
注意:星投影非常像 Java 的原始类型,但是安全。
上面摘抄自kotlin官网,没看懂可以直接看代码 @UnsafeVariance 这个注解,可以绕过in只能作为入参,out只能作为返回值的限制
class Test<out T:Number>{
fun set(t: @UnsafeVariance T){
}
fun get():T?{
return null
}
}
class Test1<in T:Number>{
fun set(t: T){
}
fun get(): @UnsafeVariance T?{
return null
}
}
class Test3<T : Number>{
fun set(t: T){
}
fun get(): T?{
return null
}
}
fun testStar1() {
val test: Test<*> = Test<Int>()
test.set(1)
val get: Number? = test.get()
val test1: Test1<*> = Test1<Int>()
// The integer literal does not conform to the expected type Nothing
// test1.set(1)
val number: Number? = test1.get()
val test3: Test3<*> = Test3<Int>()
// // The integer literal does not conform to the expected type Nothing
// test3.set(1)
val get3: Number? = test3.get()
}
总结,当以*作为泛型参数时,读取都是正常的,但是写入在非out情况下,都无法编译成功。
泛型的缺点
无法在静态字段上使用类型参数
类的静态字段是一个类级别变量,由类的所有非静态对象共享。因此,类型参数的静态字段是不允许的
class MobileDevice<T> {
companion object {
// 类的静态字段是一个类级别变量,由类的所有非静态对象共享。因此,类型参数的静态字段是不允许的
// private val os: T? = null
}
}
无法创建、捕获或抛出参数化类型的对象
fun <E : Throwable> testError() {
// try {
//
// // Type parameter is forbidden for catch parameter
// }catch (e:E){
// e.printStackTrace()
// }
}
// Extends Throwable indirectly
//class MathException<T> : Exception() { /* ... */ } // compile-time error
// Extends Throwable directly
//class QueueFullException<T> : Throwable() { /* ... */ // compile-time error
// kotlin中无法抛出泛型异常,但是java是可以的
internal class Parser<out T : Exception> {
// @Throws(T::class) compile-time error
fun parse(file: File?) { // OK
// ...
}
}
// class Parser<T extends Exception> {
// public void parse(File file) throws T { // OK
// // ...
// }
//}
无法重载两个泛型擦出后具有相同签名的方法
class Example {
// 虽然编译器会擦出泛型信息,但是只要具体的type为ParameterizedType,运行时仍然可以获取到泛型信息
// Example::class.java.methods[0].parameters[0].parameterizedType
// rawType Set typeArguments String
fun print(strSet: Set<String>) {}
// 编译错误
// fun print(intSet: Set<Int>) {}
}
is无法作用与泛型类型
// 因为 Java 编译器会擦除泛型代码中的所有类型参数,所以无法验证在运行时使用的泛型类型的参数化类型:
fun <E> rtti(list: List<E>?) {
if (list is ArrayList<*>) { // OK; instanceof requires a reifiable type
// ...
}
// if (list is ArrayList<Int?>) { // compile-time error
// }
}
无法创建类型参数的实例 (C# 和typescript可以)
fun <T> create(list: List<T>) {
// val a =T()
}
无法在注解中使用泛型
//annotation class Test<St> (val value: St)
本文暂时没有评论,来添加一个吧(●'◡'●)