第8章 泛型

图片 8

泛型编程是一种编程风格,其中算法以尽可能抽象的方式编写,而不依赖于将在其上执行这些算法的数据形式。

泛型

通常情况的类和函数,我们只需要使用具体的类型即可:要么是基本类型,要么是自定义的类。但是在集合类的场景下,我们通常需要编写可以应用于多种类型的代码,我们最简单原始的做法是,针对每一种类型,写一套刻板的代码。这样做,代码复用率会很低,抽象也没有做好。我们能不能把“类型”也抽象成参数呢?是的,当然可以。

图片 1泛型编程的提出者

泛型(Generic Type)简介

通常情况的类和函数,我们只需要使用具体的类型即可:要么是基本类型,要么是自定义的类。

但是尤其在集合类的场景下,我们需要编写可以应用于多种类型的代码,我们最简单原始的做法是,针对每一种类型,写一套刻板的代码。

这样做,代码复用率会很低,抽象也没有做好。

在SE 5种,Java引用了泛型。泛型,即“参数化类型”(Parameterized
Type)。顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式,我们称之为类型参数,然后在使用时传入具体的类型(类型实参)。

我们知道,在数学中泛函是以函数为自变量的函数。类比的来理解,编程中的泛型就是以类型为变量的类型,即参数化类型。这样的变量参数就叫类型参数(Type
Parameters)。

本章我们来一起学习一下Kotlin泛型的相关知识。

Java 5 中引入泛型机制,实现了“参数化类型”(Parameterized
Type)。参数化类型,顾名思义就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式,我们称之为类型参数,然后在使用时传入具体的类型。

泛型这个词并不是通用的,在不同的语言实现中,具有不同的命名。在Java/Kotlin/C#中称为泛型,在ML/Scala/Haskell中称为Parametric
Polymorphism,而在C++中被叫做模板,比如最负盛名的C++中的STL。任何编程方法的发展一定是有其目的,泛型也不例外。泛型的主要目的是加强类型安全和减少强制转换的次数。

1 为什么要有类型参数

我们先来看下没有泛型之前,我们的集合类是怎样持有对象的。

在Java中,Object类是所有类的根类。为了集合类的通用性。我们把元素的类型定义为Object,当放入具体的类型的时候,再作强制类型转换。

这是一个示例代码:

class RawArrayList {
public int length = 0;
private Object[] elements;

public RawArrayList(int length) {
    this.length = length;
    this.elements = new Object[length];
}

public Object get(int index) {
    return elements[index];
}

public void add(int index, Object element) {
    elements[index] = element;
}

@Override
public String toString() {
    return "RawArrayList{" +
        "length=" + length +
        ", elements=" + Arrays.toString(elements) +
        '}';
  }
}

一个简单的测试代码如下:

public class RawTypeDemo {

public static void main(String[] args) {
    RawArrayList rawArrayList = new RawArrayList(4);
    rawArrayList.add(0, "a");
    rawArrayList.add(1, "b");
    System.out.println(rawArrayList);

    String a = (String)rawArrayList.get(0);
    System.out.println(a);

    String b = (String)rawArrayList.get(1);
    System.out.println(b);

    rawArrayList.add(2, 200);
    rawArrayList.add(3, 300);
    System.out.println(rawArrayList);

    int c = (int)rawArrayList.get(2);
    int d = (int)rawArrayList.get(3);
    System.out.println(c);
    System.out.println(d);

    //Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    String x = (String)rawArrayList.get(2);
    System.out.println(x);

     }

}

我们可以看出,在使用原生态类型(raw
type)实现的集合类中,我们使用的是Object[]数组。这种实现方式,存在的问题有两个:

  • 向集合中添加对象元素的时候,没有对元素的类型进行检查,也就是说,我们往集合中添加任意对象,编译器都不会报错。

  • 当我们从集合中获取一个值的时候,我们不能都使用Object类型,需要进行强制类型转换。而这个转换过程由于在添加元素的时候没有作任何的类型的限制跟检查,所以容易出错。例如上面代码中的:

       String a = (St`ring)rawArrayList.get(0);
    

对于这行代码,编译时不会报错,但是运行时会抛出类型转换错误。

由于我们不能笼统地把集合类中所有的对象是视作Object,然后在使用的时候各自作强制类型转换。因此,我们引入了类型参数来解决这个类型安全使用的问题。

Java 中的泛型是在1.5
之后加入的,我们可以为类和方法分别定义泛型参数,比如说Java中的Map接口的定义:

public interface Map<K,V> {
...
boolean containsKey(Object key);
boolean containsValue(Object value);
V get(Object key);
V put(K key, V value);
V remove(Object key);
void putAll(Map<? extends K, ? extends V> m);
Set<K> keySet();
Collection<V> values();
Set<Map.Entry<K, V>> entrySet();
default V getOrDefault(Object key, V defaultValue) {
V v;
return (((v = get(key)) != null) || containsKey(key))
? v
: defaultValue;
}
}

我们在Kotlin 中的写法基本一样:

public interface Map<K, out V> {
...
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<K>
public val values: Collection<V>
public val entries: Set<Map.Entry<K, V>>

}

public interface MutableMap<K, V> : Map<K, V> {
public fun put(key: K, value: V): V?
public fun remove(key: K): V?
public fun putAll(from: Map<out K, V>): Unit
...

}

比如,在实例化一个Map时,我们使用这个函数:

fun <K, V> mapOf(vararg pairs: Pair<K, V>): Map<K, V>

类型参数K,V是一个占位符,当泛型类型被实例化和使用时,它将被一个实际的类型参数所替代。

代码示例

>>> val map = mutableMapOf<Int,String>(1 to "a", 2 to "b", 3 to "c")
>>> map
{1=a, 2=b, 3=c}
>>> map.put(4,"c")
null
>>> map
{1=a, 2=b, 3=c, 4=c}

mutableMapOf<Int,String>表示参数化类型<K , V>分别是Int 和
String,这是泛型类型集合的实例化,在这里,放置K, V 的位置被具体的Int 和
String 类型所替代。

泛型主要是用来限制集合类持有的对象类型,这样使得类型更加安全。当我们在一个集合类里面放入了错误类型的对象,编译器就会报错:

>>> map.put("5","e")
error: type mismatch: inferred type is String but Int was expected
map.put("5","e")
^

Kotlin中有类型推断的功能,有些类型参数可以直接省略不写。上面的mapOf后面的类型参数可以省掉不写:

>>> val map = mutableMapOf(1 to "a", 2 to "b", 3 to "c")
>>> map
{1=a, 2=b, 3=c}

Java和Kotlin
的泛型实现,都是采用了运行时类型擦除的方式。也就是说,在运行时,这些类型参数的信息将会被擦除。Java
和Kotlin 的泛型对于语法的约束是在编译期。

我们知道,在数学中泛函是以函数为自变量的函数。类比的来理解,编程中的泛型就是以类型为变量的类型,即参数化类型。这样的变量参数就叫类型参数(Type
Parameters)。

在Java中有泛型类和泛型方法之分,这些都是表现形式的改变,实质还是将算法尽可能地抽象化,不依赖具体的类型。

2 型变(Variance)

本章我们来一起学习一下Kotlin泛型的相关知识。

generics add a way to specify concrete types to general purposes
classes and methods that operated on Object before

2.1 Java的类型通配符

Java 泛型的通配符有两种形式。我们使用

  • 子类型上界限定符? extends T
    指定类型参数的上限(该类型必须是类型T或者它的子类型)
  • 超类型下界限定符? super T
    指定类型参数的下限(该类型必须是类型T或者它的父类型)

我们称之为类型通配符(Type Wildcard)。默认的上界(如果没有声明)是
Any?,下界是Nothing。

代码示例:

class Animal {

public void act(List<? extends Animal> list) {
    for (Animal animal : list) {
        animal.eat();
    }
}

public void aboutShepherdDog(List<? super ShepherdDog> list) {
    System.out.println("About ShepherdDog");
}

public void eat() {
    System.out.println("Eating");
}

}

class Dog extends Animal {}

class Cat extends Animal {}

class ShepherdDog extends Dog {}

我们在方法act(List<? extends Animal> list)中,
这个list可以传入以下类型的参数:

List<Animal>
List<Dog>
List<ShepherdDog>
List<Cat>

测试代码:

    List<Animal> list3 = new ArrayList<>();
    list3.add(new Dog());
    list3.add(new Cat());
    animal.act(list3);

    List<Dog> list4 = new ArrayList<>();
    list4.add(new Dog());
    list4.add(new Dog());
    animal.act(list4);

    List<Cat> list5 = new ArrayList<>();
    list5.add(new Cat());
    list5.add(new Cat());
    animal.act(list5);

为了更加简单明了说明这些类型的层次关系,我们图示如下:

对象层次类图:

图片 2

集合类泛型层次类图:

图片 3

也就是说,List<Dog>并不是List<Animal>的子类型,而是两种不存在父子关系的类型。

List<? extends Animal>List<Animal>List<Dog>等的父类型,对于任何的List<X>这里的X只要是Animal的子类型,那么List<? extends Animal>就是List<X>的父类型。

使用通配符List<? extends Animal>的引用,
我们不可以往这个List中添加Animal类型以及其子类型的元素:

    List<? extends Animal> list1 = new ArrayList<>();

    list1.add(new Dog());
    list1.add(new Animal());

这样的写法,Java编译器是不允许的。

图片 4

螢幕快照 2017-06-30 23.46.12.png

因为对于set方法,编译器无法知道具体的类型,所以会拒绝这个调用。但是,如果是get方法形式的调用,则是允许的:

List<? extends Animal> list1 = new ArrayList<>();
List<Dog> list4 = new ArrayList<>();
list4.add(new Dog());
list4.add(new Dog());
animal.act(list4);
list1 = list4;
animal.act(list1);

我们这里把引用变量List<? extends Animal> list1直接赋值List<Dog> list4,
因为编译器知道可以把返回对象转换为一个Animal类型。

相应的,? super T超类型限定符的变量类型List<? super ShepherdDog>的层次结构如下:

图片 5

螢幕快照 2017-06-30 23.56.35.png

在Java中,还有一个无界通配符,即单独一个?。如List<?>?可以代表任意类型,“任意”是未知类型。例如:

Pair<?>

参数替换后的Pair类有如下方法:

? getFirst()
void setFirst(?)

我们可以调用getFirst方法,因为编译器可以把返回值转换为Object。
但是不能调用setFirst方法,因为编译器无法确定参数类型。

通配符在类型系统中具有重要的意义,它们为一个泛型类所指定的类型集合提供了一个有用的类型范围。泛型参数表明的是在类、接口、方法的创建中,要使用一个数据类型参数来代表将来可能会用到的一种具体的数据类型。它可以是Integer类型,也可以是String类型。我们通常把它的类型定义成
E、T 、K 、V等等。

当我们在实例化对象的时候,必须声明T具体是一个什么类型。所以当我们把T定义成一个确定的泛型数据类型,参数就只能是这种数据类型。此时,我们就用到了通配符代替指定的泛型数据类型。

如果把一个对象分为声明、使用两部分的话。泛型主要是侧重于类型的声明的代码复用,通配符则侧重于使用上的代码复用。泛型用于定义内部数据类型的参数化,通配符则用于定义使用的对象类型的参数化。

使用泛型、通配符提高了代码的复用性。同时对象的类型得到了类型安全的检查,减少了类型转换过程中的错误。

《Java编程思想
》中提到:有许多原因促成了泛型的出现,而最引人注意的一个原因,就是为了创建容器类

通用的类和方法,具有代表性的就是集合类。在Java1.5之前,Java中的泛型都是通过单根继承的方式实现的。比如:

2.2 协变(covariant)与逆变(contravariant)

在Java中数组是协变的,下面的代码是可以正确编译运行的:

    Integer[] ints = new Integer[3];
    ints[0] = 0;
    ints[1] = 1;
    ints[2] = 2;
    Number[] numbers = new Number[3];
    numbers = ints;
    for (Number n : numbers) {
        System.out.println(n);
    }

在Java中,因为 Integer 是 Number 的子类型,数组类型 Integer[] 也是
Number[] 的子类型,因此在任何需要 Number[] 值的地方都可以提供一个
Integer[] 值。

而另一方面,泛型不是协变的。也就是说, List<Integer> 不是
List<Number> 的子类型,试图在要求 List<Number> 的位置提供
List<Integer> 是一个类型错误。下面的代码,编译器是会直接报错的:

    List<Integer> integerList = new ArrayList<>();
    integerList.add(0);
    integerList.add(1);
    integerList.add(2);
    List<Number> numberList = new ArrayList<>();
    numberList = integerList;

编译器报错提示如下:

图片 6

螢幕快照 2017-07-01 00.59.16.png

Java中泛型和数组的不同行为,的确引起了许多混乱。

就算我们使用通配符,这样写:

List<? extends Number> list = new ArrayList<Number>();  
list.add(new Integer(1)); //error  

仍然是报错的:

图片 7

螢幕快照 2017-07-01 01.03.54.png

为什么Number的对象可以由Integer实例化,而ArrayList<Number>的对象却不能由ArrayList<Integer>实例化?list中的<?
extends Number>声明其元素是Number或Number的派生类,为什么不能add
Integer?为了解决这些问题,需要了解Java中的逆变和协变以及泛型中通配符用法。

集合类可以说是我们在写代码过程中最最常用的类之一。我们先来看下没有泛型之前,我们的集合类是怎样持有对象的。在Java中,Object类是所有类的根类。为了集合类的通用性,把元素的类型定义为Object,当放入具体的类型的时候,再作相应的强制类型转换。

public class ArrayList // before Java SE 5.0{ public Object get public void add public boolean contains; private Object[] elementData;}

逆变与协变

Animal类型(简记为F, Father)是Dog类型(简记为C,
Child)的父类型,我们把这种父子类型关系简记为F <| C。

而List<Animal>, List<Dog>的类型,我们分别简记为f(F), f(C)。

那么我们可以这么来描述协变和逆变:

当F <| C 时, 如果有f(F) <| f(C),那么f叫做协变(Convariant);
当F <| C 时, 如果有f(C) <|
f(F),那么f叫做逆变(Contravariance)。
如果上面两种关系都不成立则叫做不可变。

协变和逆协变都是类型安全的。

Java中泛型是不变的,可有时需要实现逆变与协变,怎么办呢?这时就需要使用我们上面讲的通配符?

这是一个示例代码:

虽然算法足够通用了,但是这样会带来两个问题。一个是类型不安全,还有一个是每次使用时都得强制转化。减少类型转换次数比较容易理解,在没有泛型的时候,装进容器的数据,其类型信息丢失了,所以取出来的时候需要进行类型转换。例如:

<? extends T>实现了泛型的协变

List<? extends Number> list = new ArrayList<>();  

这里的? extends Number表示的是Number类或其子类,我们简记为C。

这里C <| Number,这个关系成立:List<C> <| List< Number >。即有:

List<? extends Number> list1 = new ArrayList<Integer>();  
List<? extends Number> list2 = new ArrayList<Float>();  

但是这里不能向list1、list2添加除null以外的任意对象。

    list1.add(null);
    list2.add(null);

    list1.add(new Integer(1)); // error

因为,List<Integer>可以添加Interger及其子类,List<Float>可以添加Float及其子类,List<Integer>、List<Float>都是List<? extends Number>的子类型,如果能将Float的子类添加到List<? extends Number>中,那么也能将Integer的子类添加到List<? extends Number>中,
那么这时候List<? extends Number>里面将会持有各种Number子类型的对象(Byte,Integer,Float,Double等等)。Java为了保护其类型一致,禁止向List<?
extends Number>添加任意对象,不过可以添加null。

图片 8

螢幕快照 2017-07-01 01.25.30.png

class RawArrayList { public int length = 0; private Object[] elements; // 把元素的类型定义为Object public RawArrayList(int length) { this.length = length; this.elements = new Object[length]; } public Object get(int index) { return elements[index]; } public void add(int index, Object element) { elements[index] = element; }}
List list = new ArrayList();list.add;assertThat(list.get, instanceOf(Integer.TYPE));assertThatlist.get); //存在强制转换

<? super T>实现了泛型的逆变

List<? super Number> list = new ArrayList<>();  

? super Number
通配符则表示的类型下界为Number。即这里的父类型F是? super Number,
子类型C是Number。即当F <| C , 有f(C) <| f(F) ,
这就是逆变。代码示例:

    List<? super Number> list3 = new ArrayList<Number>();  
    List<? super Number> list4 = new ArrayList<Object>();  
    list3.add(new Integer(3));  
    list4.add(new Integer(4));  

也就是说,我们不能往List<? super Number >中添加Number的任意父类对象。但是可以向List<?
super Number >添加Number及其子类对象。

一个简单的测试代码如下

因为这个类里只有Object的声明,所以任意类型的对象都可以加入到这个集合当中,在使用过程中就会存在强制到具体的类型失败的问题,这将丧失编译器检查的好处。

PECS

现在问题来了:我们什么时候用extends什么时候用super呢?《Effective
Java》给出了答案:

PECS: producer-extends, consumer-super

比如,一个简单的Stack API:

public class Stack<E>{  
public Stack();  
public void push(E e):  
public E pop();  
public boolean isEmpty();  
}  

要实现pushAll(Iterable<E> src)方法,将src的元素逐一入栈:

    public void pushAll(Iterable<E> src){  
    for(E e : src)  
      push(e)  
    }  

假设有一个实例化Stack<Number>的对象stack,src有Iterable<Integer>与
Iterable<Float>;

在调用pushAll方法时会发生type
mismatch错误,因为Java中泛型是不可变的,Iterable<Integer>与
Iterable<Float>都不是Iterable<Number>的子类型。

因此,应改为

// Wildcard type for parameter that serves as an E producer  
public void pushAll(Iterable<? extends E> src) {  
for (E e : src)   // out T, 从src中读取数据,producer-extends
push(e);  
}  

要实现popAll(Collection<E>
dst)方法,将Stack中的元素依次取出add到dst中,如果不用通配符实现:

        // popAll method without wildcard type - deficient!  
        public void popAll(Collection<E> dst) {  
        while (!isEmpty())  
        dst.add(pop());
        }  

同样地,假设有一个实例化Stack<Number>的对象stack,dst为Collection<Object>;

调用popAll方法是会发生type
mismatch错误,因为Collection<Object>不是Collection<Number>的子类型。

因而,应改为:

// Wildcard type for parameter that serves as an E consumer  
public void popAll(Collection<? super E> dst) {  
    while (!isEmpty())  
        dst.add(pop());   // in T, 向dst中写入数据, consumer-super
}  

Naftalin与Wadler将PECS称为 Get and Put Principle

java.util.Collectionscopy方法中(JDK1.7)完美地诠释了PECS:

public static <T> void copy(List<? super T> dest, List<?
extends T> 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<srcSize; i++)  
        dest.set(i, src.get(i));  
} else {  
    ListIterator<? super T> di=dest.listIterator();   // in T, 写入dest数据
    ListIterator<? extends T> si=src.listIterator();   // out T, 读取src数据
    for (int i=0; i<srcSize; i++) {  
        di.next();  
        di.set(si.next());  
    }  
}  
}  
public class RawTypeDemo { public static void main(String[] args) { RawArrayList rawArrayList = new RawArrayList; rawArrayList.add; rawArrayList.add; System.out.println(rawArrayList); String a = rawArrayList.get; System.out.println; String b = rawArrayList.get; System.out.println; rawArrayList.add; rawArrayList.add; System.out.println(rawArrayList); int c = rawArrayList.get; int d = rawArrayList.get; System.out.println; System.out.println; String x = rawArrayList.get; //Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String System.out.println; }}
List list = new ArrayList();list.add;list.add("any type");assertThat(list.get, instanceOf(String.class));assertThat list.get);//-> java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer

3 Kotlin的泛型特色

正如上文所讲的,在 Java
泛型里,有通配符这种东西,我们要用? extends T指定类型参数的上限,用
? super T指定类型参数的下限。

而Kotlin
抛弃了这个东西,引用了生产者和消费者的概念。也就是我们前面讲到的PECS。生产者就是我们去读取数据的对象,消费者则是我们要写入数据的对象。这两个概念理解起来有点绕。

我们用代码示例简单讲解一下:

public static <T> void copy(List<? super T> dest, List<?
extends T> src) {

ListIterator<? super T> di=dest.listIterator(); // in T,
写入dest数据
ListIterator<? extends T> si=src.listIterator(); // out T,
读取src数据

}

List<? super T> dest是消费数据的对象,这些数据会写入到该对象中,这些数据该对象被“吃掉”了(Kotlin中叫in T)。

List<? extends T> src是生产提供数据的对象。这些数据哪里来的呢?就是通过src读取获得的(Kotlin中叫out T)。

我们可以看出,在使用原生态类型实现的集合类中,我们使用的是Object[]数组。这种实现方式,存在的问题有两个:

2005 Java SE
5引入了泛型,不仅有效地提高了算法的通用程度,同时也保留强类型语言在编译期检查的好处。

3.1 out Tin T

在Kotlin中,我们把那些只能保证读取数据时类型安全的对象叫做生产者,用
out T标记;把那些只能保证写入数据安全时类型安全的对象叫做消费者,用
in T标记。

如果你觉得太晦涩难懂,就这么记吧:

out T 等价于? extends T
in T 等价于 ? super T
此外, 还有 * 等价于?

  1. 向集合中添加对象元素的时候,没有对元素的类型进行检查,也就是说,我们往集合中添加任意对象,编译器都不会报错。

  2. 当我们从集合中获取一个值的时候,我们不能都使用Object类型,需要进行强制类型转换。而这个转换过程由于在添加元素的时候没有作任何的类型的限制跟检查,所以容易出错。例如上面代码中的:

Generics This long-awaited enhancement to the type system allows a
type or method to operate on objects of various types while providing
compile-time type safety. It adds compile-time type safety to the
Collections Framework and eliminates the drudgery of casting.

3.2 声明处型变

Kotlin 泛型中添加了声明处型变。看下面的例子:

interface Source<out T> {
fun <T> nextT();
}

我们在接口的声明处用 out T 做了生产者声明以实现安全的类型协变:

fun demo(str: Source<String>) {
val obj: Source<Any> = str // 合法的类型协变
}

Kotlin 中有大量的声明处协变,比如 Iterable 接口的声明:

public interface Iterable<out T> {
public operator fun iterator(): Iterator<T>
}

因为 Collection 接口和 Map 接口都继承了 Iterable 接口,而 Iterable
接口被声明为生产者接口,所以所有的 Collection 和 Map
对象都可以实现安全的类型协变:

val c: List<Number> = listOf(1, 2, 3)

这里的 listOf() 函数返回 List<Int>类型,因为 List
接口实现了安全的类型协变,所以可以安全地把 List<Int>类型赋给
List<Number> 类型变量。

所以上述的程序会写成这样:

3.3 类型投影

将类型参数 T 声明为 out
非常方便,并且能避免使用处子类型化的麻烦,但是有些类实际上不能限制为只返回
T

一个很好的例子是 Array:

    kotlin
    class Array<T>(val size: Int) {
    fun get(index: Int): T {  }
    fun set(index: Int, value: T) {  }
    }

该类在 T
上既不能是协变的也不能是逆变的。这造成了一些不灵活性。考虑下述函数:

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

这个函数应该将项目从一个数组复制到另一个数组。如果我们采用如下方式使用这个函数:

kotlin
val ints: Array<Int> = arrayOf(1, 2, 3)
val any = Array<Any>(3) { "" } 
copy(ints, any) // 错误:期望 (Array<Any>, Array<Any>)

这里我们将遇到同样的问题:Array <T>T 上是不型变的,因此
Array <Int>Array <Any> 都不是另一个的子类型。

那么,我们唯一要确保的是 copy() 不会做任何坏事。我们阻止它写到
from,我们可以:

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

现在这个from是一个受Array<out Any>限制的(投影的)数组。在Kotlin中,称为类型投影(type
projection)。其主要作用是参数作限定,避免不安全操作。

类似的,我们也可以使用 in 投影一个类型:

kotlin
fun fill(dest: Array<in String>, value: String) {}

Array<in String> 对应于 Java 的
Array<? super String>,也就是说,我们可以传递一个 CharSequence
数组或一个 Object 数组给 fill() 函数。

类似Java中的无界类型通配符?, Kotlin 也有对应的星投影语法*

例如,如果类型被声明为
interface Function <in T, out U>,我们有以下星投影:

  • Function<*, String> 表示 Function<in Nothing, String>
  • Function<Int, *> 表示 Function<Int, out Any?>
  • Function<*, *> 表示 Function<in Nothing, out Any?>

*投影跟 Java 的原始类型类似,不过是安全的。

String x = rawArrayList.get; //Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图