引言

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 = Student@2a0ce342
scala> val doggy = new Dog("doggy")
doggy: Dog = Dog@dcdb883
// 通过隐式转换后,小狗也可以参加party
scala> val party = new Party(spark, doggy)
party: Party[Object] = Party@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入门系列(十三):类型参数的更多相关文章

  1. Scala入门系列(一):基础语法

    Scala基础语法 Scala与JAVA的关系 Scala是基于Java虚拟机,也就是JVM的一门编程语言,所有Scala的代码都需要经过编译为字节码,然后交由Java虚拟机来运行. 所以Scala和 ...

  2. Scala入门系列(四):Map & Tuple

    Map 创建Map // 创建一个不可变的Map scala> val ages = Map("Leo" -> 30, "Sparks" -> ...

  3. Scala入门系列(五):面向对象之类

    定义类 // 定义类,包含field以及method class HelloWorld { private var name = "Leo" def sayHello() { pr ...

  4. Scala入门系列(七):面向对象之继承

    extends 与Java一样,也是使用extends关键字,使用继承可以有效复用代码 class Person { private var name = "leo" def ge ...

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

    基础知识 1 将trait作为接口使用 此时Trait就与Java中的接口非常类似,不过注意,在Scala中无论继承还是trait,统一都是extends关键字. Scala跟Java 8前一样不支持 ...

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

    引言 Scala是一门既面向对象,又面向过程的语言,Scala的函数式编程,就是Scala面向过程最好的佐证.也真是因此让Scala具备了Java所不具备的更强大的功能和特性. 而之所以Scala一直 ...

  7. Scala入门系列(十):函数式编程之集合操作

    1. Scala的集合体系结构 Scala中的集合体系主要包括(结构跟Java相似): Iterable(所有集合trait的根trait) Seq(Range.ArrayBuffer.List等) ...

  8. Scala入门系列(十一):模式匹配

    引言 模式匹配是Scala中非常有特色,非常强大的一种功能. 类似于Java中的switch case语法,但是模式匹配的功能要比它强大得多,switch只能对值进行匹配,但是Scala的模式匹配除了 ...

  9. Scala入门系列(十二):隐式转换

    引言 Scala提供的隐式转换和隐式参数功能,是非常有特色的功能.是Java等编程语言所没有的功能.它可以允许你手动指定,将某种类型的对象转换成其他类型的对象.通过这些功能可以实现非常强大而且特殊的功 ...

随机推荐

  1. hive的简单理解--笔记

    Hive的理解 数据仓库的工具  Hive仅仅是在hadoop上面包装了SQL: Hive的数据存储在hadoop上 Hive的计算由MR进行 Hive批量处理数据  Hive的特点 1 可扩展性(h ...

  2. 【转载】SQL注入

             "SQL注入"是一种利用未过滤/未审核用户输入的攻击方法("缓存溢出"和这个不同),意思就是让应用运行本不应该运行的SQL代码.如果应用毫无防 ...

  3. 一起写框架-Ioc内核容器的实现-基础功能-ComponentScan支持多包扫描(六)

    实现功能 1.我们看到@ComponentScan注解一个开始定义就是需要支持,扫描多个包,将多个包的类名获取到.现在就实现这个功能. 实现思路 根据传入的字符串数组,获得多个包下的类全限制名. 实现 ...

  4. C#自定义运行时窗体设计器Runtime FormDesigner

    写在前面:因为业务的需要,有时会使用到自定义运行时窗体设计器Runtime FormDesigner,实现的功能,就是IDE设计器的简化.设想一下,如果可以在程序运行时,再设计一个Form,然后编译代 ...

  5. MyBatis 一、二级缓存和自定义缓存

    1.一级缓存 ​ MyBatis 默认开启了一级缓存,一级缓存是在SqlSession 层面进行缓存的.即,同一个SqlSession ,多次调用同一个Mapper和同一个方法的同一个参数,只会进行一 ...

  6. Java实现归并排序和快速排序

    参考http://blog.csdn.net/morewindows/article/details/6684558和http://developer.51cto.com/art/201206/344 ...

  7. FPGA DDR3调试

    FPGA DDR3调试 Spartan6 FPGA芯片中集成了MCB硬核,它可以支持到DDR3.在Xilinx的开发工具Xilinx ISE中提供了MIG IP核,设计者可以用它来直接生成 DDR3 ...

  8. window下安装Apache+PHP

    本地系统为windows 10,Apache选择httpd-2.4.25-x64-vc14-r1,PHP选择php7.1_x64线程安全版. 1.安装Apache 将apache解压到c:/serve ...

  9. ASP.NET Core 认证与授权[7]:动态授权

    ASP.NET Core 中基于策略的授权旨在分离授权与应用程序逻辑,它提供了灵活的策略定义模型,在一些权限固定的系统中,使用起来非常方便.但是,当要授权的资源无法预先确定,或需要将权限控制到每一个具 ...

  10. Science发表的超赞聚类算法

    作者(Alex Rodriguez, Alessandro Laio)提出了一种很简洁优美的聚类算法, 可以识别各种形状的类簇, 并且其超参数很容易确定. 算法思想 该算法的假设是类簇的中心由一些局部 ...