引言
Scala中类型参数是什么呢?其实就类似于Java中的泛型
。定义一种类型参数,比如在集合、类、函数中定义类型参数,然后就可以保证使用到该类型参数的地方就只能是这种类型,从而实现程序更好的健壮性。
泛型类
泛型类,顾名思义,其实就是在类的声明中,定义一些泛型类型,然后在类内部,比如field或method,就可以使用这些泛型类型。
使用泛型类,通常是需要对类中的某些成员,比如某些field和method中的参数或变量,进行统一的类型限制,这样可以保证程序更好的健壮性和稳定性。
在使用类的时候,比如创建类的对象,将类型参数替换为实际的类型即可,甚至可以直接赋值,Scala会自动进行类型推断。
// 定义泛型类 // 语法:类名[泛型标识], 泛型标识可以任意设定,常用(T, V, U, K, W,甚至是_) // 案例:新生报到,需要为每个学生生成ID class Student[T] (val localId: T) { def getSchoolId(homeId: T) = "S-" + homeId + "-" + localId } defined class Student // 测试,指定该泛型为整数类型 scala> val leo = new Student[Int](111) leo: Student[Int] = Student@129b69b2 // 这样当我们传入字符类型时就会报错 scala> leo.getSchoolId("222") <console>:13: error: type mismatch; found : String("222") required: Int leo.getSchoolId("222") ^ // 传入整数类型OK scala> leo.getSchoolId(222) res26: String = S-222-111 // 测试:不指定类型,直接传入类型,Scala会自动进行类型推断 scala> val leo = new Student("111") leo: Student[String] = Student@4990b335 scala> leo.getSchoolId(222) <console>:13: error: type mismatch; found : Int(222) required: String leo.getSchoolId(222) ^ scala> leo.getSchoolId("222") res30: String = S-222-111
泛型函数
因为函数在Scala中跟类一样也是一等公民,所以泛型也同样可以作用于函数。
// 定义泛型函数, 案例:卡片售卖机 def getCard[T](content: T) = { if(content.isInstanceOf[Int]) "int card: " + content else if(content.isInstanceOf[String]) "String card: " + content else "card: " + content } getCard: [T](content: T)String // 测试 scala> getCard[String]("100") res34: String = String card: 100 scala> getCard[Int](100) res35: String = int card: 100 scala> getCard(100) res36: String = int card: 100
上下边界Bounds
在指定泛型类型的时候,有时候我们需要对泛型类型的范围进行界定,而不是可以任意的类型。
Scala的上边界特性限制类型必须是某个类本身或其子类。
// 案例:在派对上交朋友 class Person(val name: String) { def sayHello = println("Hello, I‘m " + name) def makeFriends(p: Person){ sayHello p.sayHello } } class Student(name: String) extends Person(name) class Worker(val name: String) // 定义泛型上边界,语法为 T <: 类,表示必须要是该类或是其子类 class Party[T <: Person](p1: T, p2: T){ def play = p1.makeFriends(p2) } // Exiting paste mode, now interpreting. defined class Person defined class Student defined class Worker defined class Party // 测试两个Person的子类 scala> val leo = new Student("leo") leo: Student = Student@288b73c1 scala> val spark = new Student("Sparks") spark: Student = Student@57df09a7 scala> val party = new Party(leo, spark) party: Party[Student] = Party@998fbd4 scala> party.play Hello, I‘m leo Hello, I‘m Sparks // 用一个不是Person子类的Worker类来演示错误 scala> val jack = new Worker("jack") jack: Worker = Worker@68229a6 // 因为不是Person子类而报错 scala> val party = new Party(leo, jack) <console>:16: error: inferred type arguments [Object] do not conform to class Par bounds [T <: Person] val party = new Party(leo, jack) ^ <console>:16: error: type mismatch; found : Student required: T val party = new Party(leo, jack) ^ <console>:16: error: type mismatch; found : Worker required: T val party = new Party(leo, jack)
Scala的下边界特性限制类型必须是某个类本身或其父类,这里不多赘述。
View Bounds
上下边界Bounds,虽然可以让类型限制在父子关系的范围内,但是如果某个类与上下边界Bounds指定的父子类型没有任何关系,那么默认是肯定不能接受的。
然而,View Bounds作为一种上下边界的加强版,支持对类型进行隐式转换后,再判断是否在边界指定的类型范围内。
// 案例:跟小狗交朋友 class Person(val name: String) { def sayHello = println("Hello, I‘m " + name) def makeFriends(p: Person){ sayHello p.sayHello } } class Student(name: String) extends Person(name) class Dog(val name: String) {def sayHello = println("wang, wang, I‘m " +name)} // 隐式转换,将小狗类似转换为Person implicit def dog2person(obj: Object):Person = if(obj.isInstanceOf[Dog]){ val dog = obj.asInstanceOf[Dog] new Person(dog.name)} else Nil // 定义view bounds, 语法为 [ 泛型参数 <% 或 >% 目标类型] // 代表将该类型进行隐式转化后,在判断是否在上下边界范围内 class Party [T <% Person](p1: T, p2: T) // 测试 scala> val spark = new Student("sparks") spark: Student = [email protected]2a0ce342 scala> val doggy = new Dog("doggy") doggy: Dog = Dog@dcdb883 // 通过隐式转换后,小狗也可以参加party scala> val party = new Party(spark, doggy) party: Party[Object] = [email protected]19de32cb
Context Bounds(可删除)
Context Bounds是一种特殊的Bounds,它会根据泛型类型的声明,比如”T:类型”要求必须存在一个类型为”类型[T]”的隐式值。
其实个人认为,Context Bounds之所以叫Context,是因为它基于的是一种全局的上下文,需要利用到上下文的隐式值以及注入。
// 使用Scala内置的比较器比较大小 class Calculator[T: Ordering] (val number1: T, val number2: T){ def max(implicit order: Ordering[T]) = if(order.compare(number1, number2) > 0) number1 else number 2 } defined class Calculator scala> val cal = new Calculator(1, 2) cal: Calculator[Int] = Calculator@7ae42ce3 scala> cal.max res0: Int = 2
Manifest Context Bounds
在Scala中,如果要实例化一个泛型数组,就必须使用Manifest Context Bounds。也就是说,如果数组元素为T的话,需要为类或者函数定义[T: Manifest]
泛型类型,这样才能实例化Array[T]这种泛型数组。
// 案例:打包饭菜(一种食品达成一包) class Meat(val name: String) class Vegetable(val name: String) // 定义了一个泛型数组,[T:Manifest] def packageFood[T: Manifest] (food: T*) = { val foodPackage = new Array[T](food.length) for (i <- 0 until food.length) foodPackage(i) = food(i) foodPackage } defined class Meat defined class Vegetable packageFood: [T](food: T*)(implicit evidence$1: Manifest[T])Array[T] // 测试 scala> val m1 = new Meat("niu rou") m1: Meat = Meat@1e63d216 scala> val m2 = new Meat("yang rou") m2: Meat = Meat@22d9c961 scala> val m3 = new Meat("zhu rou") m3: Meat = Meat@1e6cc850 // 将三种肉类打成一包 scala> packageFood(m1, m2, m3) res1: Array[Meat] = Array(Meat@49aa766b, Meat@22d9c961, Meat@1e6cc850)
协变和逆变
Scala中的协变和逆变是非常有特色的!完全解决了Java泛型中的一大缺憾!
举例来说,在Java中如果Professional是Master的子类,那么类Card[Professionnal]是不是类Card[Master]的子类呢?答案是否定的,因此对于开发程序造成了很多的麻烦。
而Scala中,只要灵活使用协变和逆变,就可以解决Java泛型的问题。
协变
语法为:[+泛型参数]
,可以让类Card[Professionnal]成为类Card[Master]的子类
// 案例:进入会场 class Master class Professional extends Master // 我们希望大师以及大师级别以下的名片都可以进入会场 // [+泛型]即为协变,可以让类Card[Professionnal]成为类Card[Master]的子类 class Card[+T] (val name: String) def enterMeet(card: Card[Master]){ println("Welcome to have this meeting!") } defined class Master defined class Professional defined class Card enterMeet: (card: Card[Master])Unit // 测试 scala> val spark = new Card[Master]("sparks") spark: Card[Master] = Card@730f9695 scala> val leo = new Card[Professional]("leo") leo: Card[Professional] = Card@4c6007fb scala> enterMeet(spark) Welcome to have this meeting! // 不仅大师可以进,专家也可以进入会场 scala> enterMeet(leo) Welcome to have this meeting!
逆变
语法为:[-泛型参数]
,可以让类Card[Professionnal]成为类Card[Master]的父类
// 案例:进入会场 class Master class Professional extends Master /* 我们希望专家级别的名片就可以进入会场,如果大师级别的名片,那就更欢迎了 这里实际上需要将父类参数传递给子类泛型,默认不可以,需要进行强制类型转换才可以。但是逆变可以让我们轻松实现这一操作。 [-泛型]即为逆变,可以让类Card[Professionnal]成为类Card[Master]的父类 */ class Card[-T] (val name: String) def enterMeet(card: Card[Professional]){ println("welcome to have this meeting!") } // 测试 scala> val sparks = new Card[Master]("sparks") sparks: Card[Master] = Card@4cc36c19 scala> val leo = new Card[Professional]("leo") leo: Card[Professional] = Card@529c2a9a scala> enterMeet(leo) welcome to have this meeting! // 不仅专家可以进入,大师也可以进入! scala> enterMeet(sparks) welcome to have this meeting!
Existential Type
在Scala中,有一种特殊的类型参数,就是Existential Type,存在性类型。就是可以用_
代替某种泛型参数,很简单但很重要,因为在Spark源码中随处可见。
Array[T] forSome { type T} // 相当于 Array[_] for Somw { type _ }