Scala入门系列(十三):类型参数

Posted 小丑进场

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Scala入门系列(十三):类型参数相关的知识,希望对你有一定的参考价值。

引言

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 _ }

以上是关于Scala入门系列(十三):类型参数的主要内容,如果未能解决你的问题,请参考以下文章

Spark零基础入门:Scala类型参数

《C#零基础入门之百识百例》(三十三)方法参数 -- 位置&命名参数 -- 求长方体体积

2021年大数据常用语言Scala(十三):基础语法学习 函数 重点掌握

《C#零基础入门之百识百例》(六十三)结构体类型数组 -- 学生数据存储

Scala入门系列:面向对象之object

scala入门系列--数组