热门IT资讯网

Scala泛型

发表于:2024-11-27 作者:热门IT资讯网编辑
编辑最后更新 2024年11月27日,简单回顾泛型java中可使用泛型进行编程,一个简单的泛型例子如下:List strList = new ArrayList();strList.add("one");strList.add("two"

简单回顾泛型

java中可使用泛型进行编程,一个简单的泛型例子如下:

List strList = new ArrayList();strList.add("one");strList.add("two");strList.add("three");String one = strList.get(0); // 泛型拿数据不必进行类型转换,不使用泛型的话需要对类型进行转换

scala的泛型

scala中的泛型称为类型参数化(type parameterlization)。语法跟java不一样,使用"[]"表示类型。

一个使用类型参数化的函数:

def position[A](xs: List[A], value: A): Int = {    xs.indexOf(value)}position(List(1,2,3), 1) // 0position(List("one", "two", "three"), "two") // 1

稍微复杂点的类型参数化,实现一个map函数,需要一个List和一个函数作为参数:

普通的map方法:

List(1,2,3) map { _ * 2 }  // List[Int] = List(2,4,6)List(1,2,3) map { _ + "2" }  // List[String] = List(12, 22, 32)

使用泛型实现的map方法:

def map[A,B](list:List[A], func: A => B) = list.map(func)map(List(1,2,3), { num: Int => num + "2" }) // List[String] = List(12, 22, 32)map(List(1,2,3), { num: Int => num * 2 }) // List[Int] = List(2, 4, 6)

上限和下限

scala跟java一样,也提供了上限(upper bounds)和下限(lower bounds)功能。

上限(upper bounds)

java中上限的使用如下:

通配符形式

scala写法:

[T <: AnyRef]通配符形式[_ <: AnyRef]

上限的一些例子:

public void upperBound(List list) {    Object obj = list.get(0); // Number是Object的子类,使用Object可以代替Number。    Number num = list.get(0);    Integet i = list.get(0); // compile error    list.add(new Integer(1)); // compile error}

上限做参数,set的话不能确定具体的类型,所以会报编译错误。get的话得到的结果的类型的下限为参数的上限。相当于使用了上限参数的话,该参数就变成了只读参数,类似生产者,只提供数据。

scala版本:

def upperBound[A <: Animal](list: ListBuffer[A]): Unit = {     list += new Animal("123") // compile error    val obj: AnyRef = list(0) // ok    val a: Animal = list(0) // ok    val a: Cat = list(0) // compile error}

这里使用ListBuffer作为集合,ListBuffer的+=方法会在列表内部添加数据,不会产生一个新的List。如果使用List的话,:+操作符会在新生成的List中自动得到符合所有元素的类型。

List[Animal](new Cat()) :+ 1 // List[Any] = List(Cat@3f23a076, 1)

生成新的List会自动根据上下文得到新的泛型类型List[AnyRef]。

下限(lower bounds)

通配符形式

scala写法:

[T >: MyClass]通配符形式[_ >: MyClass]

下限的一些例子:

public static void lowerBound(List l) {    l.add(new Integer(1));    l.add(new Float(2));    Object obj = l.get(0);    Number num = l.get(0); // compile error}

下限做参数,get方法只能用最宽泛的类型来获取数据,相当于get只提供了数据最小级别的访问权限。类似消费者,主要用来消费数据。

scala版本:

def lowerBound[A >: Animal](list: ListBuffer[A]): Unit = {     list += new Animal() // ok    list += new Cat() // ok    val obj: Any = list(0) // ok    val obj: Animal = list(0) // compile error}

协变和逆变

协变(covariance):对于一个带类型参数的类型,比如List[T],如果对A及其子类型B,满足List[B]也符合List[A]的子类型,那么就称为协变,用加号表示。比如 MyType[+A]

逆变(contravariance):如果List[A]是List[B]的子类型,用减号表示。比如MyType[+B]

如果一个类型支持协变或逆变,则称这个类型为可变的(variance)。否则称为不可变的(invariant)。

在java里,泛型类型都是不可变的,比如List并不是List的子类。

trait A[T]class C[T] extends A[T]class Parent; class Child extends Parentval c:C[Parent] = new C[Parent] // okval c:C[Parent] = new C[Child]; // Child <: Parent, but class C is invariant in type T.

协变

上面的例子提示已经很明确了。类C是不可变的,改成协变即可。

trait A[+T]class C[+T] extends A[T]class Parent; class Child extends Parentval c: C[Parent] = new C[Parent] // okval c: C[Parent] = new C[Child]  // ok

scala中List就是一个协变类。

val list:List[Parent] = List[Child](new Child())

逆变

逆变概念与协变相反。

trait A[-T]class C[-T] extends A[T]class Parent; class Child extends Parentval c: C[Parent] = new C[Parent] // okval c: C[Child] = new C[Parent]  // ok

协变逆变注意点

逆变协变并不会被继承,父类声明为逆变或协变,子类如果想要保持,任需要声明:

trait A[+T]class C[T] extends A[T] // C是不可变的,因为它不是逆变或协变。class D[+T] extends A[T] // D是可变的,是协变class E[-T] extends A[T] // E是可变的,是逆变


0