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

时间: 2024-08-30 03:51:29

Scala入门系列(十三):类型参数的相关文章

Scala入门系列(三):数组

Array 与Java的Array类似,也是长度不可变的数组,此外,由于Scala与Java都是运行在JVM中,双方可以互相调用,因此Scala数组的底层实际上是Java数组. 注意:访问数组中元素使用()而不是Java中的 [] scala> val a = new Array[String](10) a: Array[String] = Array(null, null, null, null, null, null, null, null, null, n ull) scala> val

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

object object相当于class的单个实例,类似于Java中的static,通常在里面放一些静态的field和method.   第一次调用object中的方法时,会执行object的constructor,也就是object内部不在method或者代码块中的所有代码,但是object不能定义接受参数的constructor   注意:object的构造函数只会在第一次被调用时被执行一次,这点类似Java类中static的初始化. object Person { private var

Scala入门系列(八):面向对象之trait

基础知识 1 将trait作为接口使用 此时Trait就与Java中的接口非常类似,不过注意,在Scala中无论继承还是trait,统一都是extends关键字. Scala跟Java 8前一样不支持对类进行多继承,但是支持多重继承trait,使用with关键字即可 trait HelloTrait{ def sayHello(name: String) } trait MakeFriends{ def makeFriends(p: Person) } class Person(val name

Scala入门系列(九):函数式编程

引言 Scala是一门既面向对象,又面向过程的语言,Scala的函数式编程,就是Scala面向过程最好的佐证.也真是因此让Scala具备了Java所不具备的更强大的功能和特性. 而之所以Scala一直没有替代Java,一是因为Java诞生早,基于Java开发了大量知名的工程,并且最重要的是Java现在不只是一门编程语言,而是一个庞大的技术生态圈,所以未来十年内Scala也不会完全替代Java,但是Scala会在自己特有的领域大发光彩.   将函数赋值给变量 Scala中函数是一等公民,可以独立定

Provisioning Services 7.8 入门系列教程之十三 使用 Boot Device Management(BDM)

续Provisioning Services 7.8 入门系列教程之十二 实现高可用性 可以使用 Boot Device Management 实用程序将 IP 和引导信息(引导设备)交付给目标设备,此方法可以取代传统的 DHCP.PXE 和 TFTP 方法. 如果使用此方法,当目标设备启动时, 将直接从引导设备获取引导信息. 使用这些信息,目标设备可以找到相应的 Provisioning Server.与之通信并从该服务器引导. 对用户进行身份验证后,Provisioning Server 将

《鸡啄米C++编程入门系列》系列技术文章整理收藏

<鸡啄米C++编程入门系列>系列技术文章整理收藏 收藏整理鸡啄米C++编程入门系列文章,供个人和网友学习C++时参考 1鸡啄米:C++编程入门系列之前言 2鸡啄米:C++编程入门系列之一(进制数) 3鸡啄米:C++编程入门系列之二(原码.反码与补码) 4鸡啄米:C++编程入门系列之三(VS2010的使用介绍) 5鸡啄米:C++编程入门系列之四(数据类型) 6鸡啄米:C++编程入门系列之五(运算符和表达式) 7鸡啄米:C++编程入门系列之六(算法的基本控制结构之选择结构) 8鸡啄米:C++编程入

【 D3.js 入门系列 — 11 】 入门总结

D3 新专题首页 一转眼,这个入门系列已经积累了二十二篇文章之多,我想作为 D3.js 这款数据可视化工具的入门来说已经足够了.相信仅仅要看完本系列.以后全然能够在辅以查询的情况下完毕大部分可视化工作. D3.js 最早的 v1.0 版本号是由 Michael Bostock 于2011年2月18日公布,其后经过多人的不断完好,眼下最新的版本号为 v3.4.11.从公布至今三年多的时间里,D3.js 在国外不断有人尝试并制作教程.成为了流行的数据可视化工具.可是眼下在国内能查询到中文资料还比較少

Provisioning Services 7.8 入门系列教程14篇全部完成了.....

经过近期一段时间的努力,Provisioning Services 7.8 入门系列教程14篇全部完成了-- Provisioning Services 7.8 入门系列教程之十四 UEFI支持和BOOTPTAB 编辑器 2016-05-14 Provisioning Services 7.8 入门系列教程之十三 使用 Boot Device Management(BDM)2016-05-13 Provisioning Services 7.8 入门系列教程之十二 实现高可用性 2016-05-

Provisioning Services 7.8 入门系列教程之十四 UEFI支持和BOOTPTAB 编辑器

 续Provisioning Services 7.8 入门系列教程之十三 使用 Boot Device Management(BDM) UEFI,全称Unified Extensible Firmware Interface,即"统一的可扩展固件接口",是一种详细描述全新类型接口的标准,是适用于电脑的标准固件接口,旨在代替BIOS(基本输入/输出系统).此标准由UEFI联盟中的140多个技术公司共同创建,其中包括微软公司.UEFI旨在提高软件互操作性和解决BIOS的局限性. 相比传统