Scala编程:函数式编程

函数式编程

引言

Scala中的函数是Java中完全没有的概念。因为Java是完全面向对象的编程语言,没有任何面向过程编程语言的特性,因此Java中的一等公民是类和对象,而且只有方法的概念,即寄存和依赖于类和对象中的方法。Java中的方法是绝对不可能脱离类和对象独立存在的。

而Scala是一门既面向对象,又面向过程的语言。因此在Scala中有非常好的面向对象的特性,可以使用Scala来基于面向对象的思想开发大型复杂的系统和工程;而且Scala也面向过程,因此Scala中有函数的概念。在Scala中,函数与类、对象等一样,都是一等公民。Scala中的函数可以独立存在,不需要依赖任何类和对象。

Scala的函数式编程,就是Scala面向过程的最好的佐证。也正是因为函数式编程,才让Scala具备了Java所不具备的更强大的功能和特性。

而之所以Scala一直没有替代Java,是因为Scala之前一直没有开发过太多知名的应用;而Java则不一样,Java诞生的非常早,上个世界90年代就诞生了,基于Java开发了大量知名的工程。而且最重要的一点在于,Java现在不只是一门编程语言,还是一个庞大的,涵盖了软件开发,甚至大数据、云计算的技术生态,Java生态中的重要框架和系统就太多了:Spring、Lucene、Activiti、Hadoop等等。

将函数赋值给变量

// Scala中的函数是一等公民,可以独立定义,独立存在,而且可以直接将函数作为值赋值给变量
// Scala的语法规定,将函数赋值给变量时,必须在函数后面加上空格和下划线

def sayHello(name: String) { println("Hello, " + name) }
val sayHelloFunc = sayHello _
sayHelloFunc("leo")

匿名函数

// Scala中,函数也可以不需要命名,此时函数被称为匿名函数。// 可以直接定义函数之后,将函数赋值给某个变量;也可以将直接定义的匿名函数传入其他函数之中
// Scala定义匿名函数的语法规则就是,(参数名: 参数类型) => 函数体
// 这种匿名函数的语法必须深刻理解和掌握,在spark的中有大量这样的语法,如果没有掌握,是看不懂spark源码的

val sayHelloFunc = (name: String) => println("Hello, " + name)

高阶函数

// Scala中,由于函数是一等公民,因此可以直接将某个函数传入其他函数,作为参数。这个功能是极其强大的,也是Java这种面向对象的编程语言所不具备的。
// 接收其他函数作为参数的函数,也被称作高阶函数(higher-order function)

val sayHelloFunc = (name: String) => println("Hello, " + name)
def greeting(func: (String) => Unit, name: String) { func(name) }
greeting(sayHelloFunc, "leo")

Array(1, 2, 3, 4, 5).map((num: Int) => num * num)

// 高阶函数的另外一个功能是将函数作为返回值

def getGreetingFunc(msg: String) = (name: String) => println(msg + ", " + name)
val greetingFunc = getGreetingFunc("hello")
greetingFunc("leo")

高阶函数的类型推断

// 高阶函数可以自动推断出参数类型,而不需要写明类型;而且对于只有一个参数的函数,还可以省去其小括号;如果仅有的一个参数在右侧的函数体内只使用一次,则还可以将接收参数省略,并且将参数用_来替代
// 诸如3 * _的这种语法,必须掌握!!spark源码中大量使用了这种语法!

def greeting(func: (String) => Unit, name: String) { func(name) }
greeting((name: String) => println("Hello, " + name), "leo")
greeting((name) => println("Hello, " + name), "leo")
greeting(name => println("Hello, " + name), "leo")

def triple(func: (Int) => Int) = { func(3) }
triple(3 * _)

Scala的常用高阶函数

// map: 对传入的每个元素都进行映射,返回一个处理后的元素
Array(1, 2, 3, 4, 5).map(2 * _)

// foreach: 对传入的每个元素都进行处理,但是没有返回值
(1 to 9).map("*" * _).foreach(println _)

// filter: 对传入的每个元素都进行条件判断,如果对元素返回true,则保留该元素,否则过滤掉该元素
(1 to 20).filter(_ % 2 == 0)

// reduceLeft: 从左侧元素开始,进行reduce操作,即先对元素1和元素2进行处理,然后将结果与元素3处理,再将结果与元素4处理,依次类推,即为reduce;reduce操作必须掌握!spark编程的重点!!!
// 下面这个操作就相当于1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9
(1 to 9).reduceLeft( _ * _)

// sortWith: 对元素进行两两相比,进行排序

闭包

// 闭包最简洁的解释:函数在变量不处于其有效作用域时,还能够对变量进行访问,即为闭包

def getGreetingFunc(msg: String) = (name: String) => println(msg + ", " + name)
val greetingFuncHello = getGreetingFunc("hello")
val greetingFuncHi = getGreetingFunc("hi")

// 两次调用getGreetingFunc函数,传入不同的msg,并创建不同的函数返回
// 然而,msg只是一个局部变量,却在getGreetingFunc执行完之后,还可以继续存在创建的函数之中;greetingFuncHello("leo"),调用时,值为"hello"的msg被保留在了函数体内部,可以反复的使用
// 这种变量超出了其作用域,还可以使用的情况,即为闭包

// Scala通过为每个函数创建对象来实现闭包,实际上对于getGreetingFunc函数创建的函数,msg是作为函数对象的变量存在的,因此每个函数才可以拥有不同的msg

// Scala编译器会确保上述闭包机制

SAM转换

// 在Java中,不支持直接将函数传入一个方法作为参数,通常来说,唯一的办法就是定义一个实现了某个接口的类的实例对象,该对象只有一个方法;而这些接口都只有单个的抽象方法,也就是single abstract method,简称为SAM

// 由于Scala是可以调用Java的代码的,因此当我们调用Java的某个方法时,可能就不得不创建SAM传递给方法,非常麻烦;但是Scala又是支持直接传递函数的。此时就可以使用Scala提供的,在调用Java方法时,使用的功能,SAM转换,即将SAM转换为Scala函数

// 要使用SAM转换,需要使用Scala提供的特性,隐式转换

SAM转换

import javax.swing._
import java.awt.event._

val button = new JButton("Click")
button.addActionListener(new ActionListener {
  override def actionPerformed(event: ActionEvent) {
    println("Click Me!!!")
  }
})

implicit def getActionListener(actionProcessFunc: (ActionEvent) => Unit) = new ActionListener {
  override def actionPerformed(event: ActionEvent) {
    actionProcessFunc(event)
  }
}
button.addActionListener((event: ActionEvent) => println("Click Me!!!"))

Currying函数

// Curring函数,指的是,将原来接收两个参数的一个函数,转换为两个函数,第一个函数接收原先的第一个参数,然后返回接收原先第二个参数的第二个函数。
// 在函数调用的过程中,就变为了两个函数连续调用的形式
// 在Spark的源码中,也有体现,所以对()()这种形式的Curring函数,必须掌握!

def sum(a: Int, b: Int) = a + b
sum(1, 1)

def sum2(a: Int) = (b: Int) => a + b
sum2(1)(1)

def sum3(a: Int)(b: Int) = a + b

return

// Scala中,不需要使用return来返回函数的值,函数最后一行语句的值,就是函数的返回值。在Scala中,return用于在匿名函数中返回值给包含匿名函数的带名函数,并作为带名函数的返回值。
// 使用return的匿名函数,是必须给出返回类型的,否则无法通过编译

def greeting(name: String) = {
  def sayHello(name: String):String = {
    return "Hello, " + name
  }
  sayHello(name)
}

函数式编程之集合操作

Scala的集合体系结构

// Scala中的集合体系主要包括:Iterable、Seq、Set、Map。其中Iterable是所有集合trait的根trai。这个结构与Java的集合体系非常相似。

// Scala中的集合是分成可变和不可变两类集合的,其中可变集合就是说,集合的元素可以动态修改,而不可变集合的元素在初始化之后,就无法修改了。分别对应scala.collection.mutable和scala.collection.immutable两个包。

// Seq下包含了Range、ArrayBuffer、List等子trait。其中Range就代表了一个序列,通常可以使用“1 to 10”这种语法来产生一个Range。 ArrayBuffer就类似于Java中的ArrayList。

List

// List代表一个不可变的列表
// List的创建,val list = List(1, 2, 3, 4)
// List有head和tail,head代表List的第一个元素,tail代表第一个元素之后的所有元素,list.head,list.tail
// List有特殊的::操作符,可以用于将head和tail合并成一个List,0 :: list
// ::这种操作符要清楚,在spark源码中都是有体现的,一定要能够看懂!
// 如果一个List只有一个元素,那么它的head就是这个元素,它的tail是Nil

// 案例:用递归函数来给List中每个元素都加上指定前缀,并打印加上前缀的元素

def decorator(l: List[Int], prefix: String) {
  if (l != Nil) { 
    println(prefix + l.head)
    decorator(l.tail, prefix)
  }
}

LinkedList

// LinkedList代表一个可变的列表,使用elem可以引用其头部,使用next可以引用其尾部
// val l = scala.collection.mutable.LinkedList(1, 2, 3, 4, 5); l.elem; l.next

// 案例:使用while循环将LinkedList中的每个元素都乘以2

val list = scala.collection.mutable.LinkedList(1, 2, 3, 4, 5)
var currentList = list
while (currentList != Nil) {
  currentList.elem = currentList.elem * 2
  currentList = currentList.next
}

// 案例:使用while循环将LinkedList中,从第一个元素开始,每隔一个元素,乘以2

val list = scala.collection.mutable.LinkedList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
var currentList = list
var first = true
while (currentList != Nil && currentList.next != Nil) {
  if (first) { currentList.elem = currentList.elem * 2; first = false }
  currentList  = currentList.next.next
  if (currentList != Nil) currentList.elem = currentList.elem * 2
}

Set

// Set代表一个没有重复元素的集合
// 将重复元素加入Set是没有用的,比如val s = Set(1, 2, 3); s + 1; s + 4
// 而且Set是不保证插入顺序的,也就是说,Set中的元素是乱序的,

val s = new scala.collection.mutable.HashSet[Int](); s += 1; s += 2; s += 5

// LinkedHashSet会用一个链表维护插入顺序,

val s = new scala.collection.mutable.LinkedHashSet[Int](); i += 1; s += 2; s += 5

// SrotedSet会自动根据key来进行排序,

val s = scala.collection.mutable.SortedSet("orange", "apple", "banana")

集合的函数式编程

// 集合的函数式编程非常非常非常之重要!!!
// 必须完全掌握和理解Scala的高阶函数是什么意思,Scala的集合类的map、flatMap、reduce、reduceLeft、foreach等这些函数,就是高阶函数,因为可以接收其他函数作为参数
// 高阶函数的使用,也是Scala与Java最大的一点不同!!!因为Java里面是没有函数式编程的,也肯定没有高阶函数,也肯定无法直接将函数传入一个方法,或者让一个方法返回一个函数
// 对Scala高阶函数的理解、掌握和使用,可以大大增强你的技术,而且也是Scala最有诱惑力、最有优势的一个功能!!!
// 此外,在Spark源码中,有大量的函数式编程,以及基于集合的高阶函数的使用!!!所以必须掌握,才能看懂spark源码

// map案例实战:为List中每个元素都添加一个前缀
List("Leo", "Jen", "Peter", "Jack").map("name is " + _)

// faltMap案例实战:将List中的多行句子拆分成单词
List("Hello World", "You Me").flatMap(_.split(" "))

// foreach案例实战:打印List中的每个单词
List("I", "have", "a", "beautiful", "house").foreach(println(_))

// zip案例实战:对学生姓名和学生成绩进行关联
List("Leo", "Jen", "Peter", "Jack").zip(List(100, 90, 75, 83))

函数式编程综合案例:统计多个文本内的单词总数

// 使用scala的io包将文本文件内的数据读取出来

val lines01 = scala.io.Source.fromFile("C://Users//Administrator//Desktop//test01.txt").mkString
val lines02 = scala.io.Source.fromFile("C://Users//Administrator//Desktop//test02.txt").mkString
// 使用List的伴生对象,将多个文件内的内容创建为一个List
val lines = List(lines01, lines02)

// 下面这一行才是我们的案例的核心和重点,因为有多个高阶函数的链式调用,以及大量下划线的使用,如果没有透彻掌握之前的课讲解的Scala函数式编程,那么下面这一行代码,完全可能会看不懂!!!
// 但是下面这行代码其实就是Scala编程的精髓所在,就是函数式编程,也是Scala相较于Java等编程语言最大的功能优势所在
// 而且,spark的源码中大量使用了这种复杂的链式调用的函数式编程
// 而且,spark本身提供的开发人员使用的编程api的风格,完全沿用了Scala的函数式编程,比如Spark自身的api中就提供了map、flatMap、reduce、foreach,以及更高级的reduceByKey、groupByKey等高阶函数
// 如果要使用Scala进行spark工程的开发,那么就必须掌握这种复杂的高阶函数的链式调用!!!

lines.flatMap(.split(" ")).map((, 1)).map(_.2).reduceLeft( + _)

模式匹配

引言

模式匹配是Scala中非常有特色,非常强大的一种功能。模式匹配,其实类似于Java中的swich case语法,即对一个值进行条件判断,然后针对不同的条件,进行不同的处理。

但是Scala的模式匹配的功能比Java的swich case语法的功能要强大地多,Java的swich case语法只能对值进行匹配。但是Scala的模式匹配除了可以对值进行匹配之外,还可以对类型进行匹配、对Array和List的元素情况进行匹配、对case class进行匹配、甚至对有值或没值(Option)进行匹配。

而且对于Spark来说,Scala的模式匹配功能也是极其重要的,在spark源码中大量地使用了模式匹配功能。因此为了更好地编写Scala程序,并且更加通畅地看懂Spark的源码,学好模式匹配都是非常重要的。

模式匹配

// Scala是没有Java中的switch case语法的,相对应的,Scala提供了更加强大的match case语法,即模式匹配,类替代switch case,match case也被称为模式匹配
// Scala的match case与Java的switch case最大的不同点在于,Java的switch case仅能匹配变量的值,比1、2、3等;而Scala的match case可以匹配各种情况,比如变量的类型、集合的元素、有值或无值
// match case的语法如下:变量 match { case 值 => 代码 }。如果值为下划线,则代表了不满足以上所有情况下的默认情况如何处理。此外,match case中,只要一个case分支满足并处理了,就不会继续判断下一个case分支了。(与Java不同,java的switch case需要用break阻止)
// match case语法最基本的应用,就是对变量的值进行模式匹配

// 案例:成绩评价

def judgeGrade(grade: String) {
  grade match {
    case "A" => println("Excellent")
    case "B" => println("Good")
    case "C" => println("Just so so")
    case _ => println("you need work harder")
  }
}

在模式匹配中使用if守卫

// Scala的模式匹配语法,有一个特点在于,可以在case后的条件判断中,不仅仅只是提供一个值,而是可以在值后面再加一个if守卫,进行双重过滤

// 案例:成绩评价(升级版)

def judgeGrade(name: String, grade: String) {
  grade match {
    case "A" => println(name + ", you are excellent")
    case "B" => println(name + ", you are good")
    case "C" => println(name + ", you are just so so")
    case _ if name == "leo" => println(name + ", you are a good boy, come on")
    case _ => println("you need to work harder")
  }
}

在模式匹配中进行变量赋值

// Scala的模式匹配语法,有一个特点在于,可以将模式匹配的默认情况,下划线,替换为一个变量名,此时模式匹配语法就会将要匹配的值赋值给这个变量,从而可以在后面的处理语句中使用要匹配的值
// 为什么有这种语法??思考一下。因为只要使用用case匹配到的值,是不是我们就知道这个只啦!!在这个case的处理语句中,是不是就直接可以使用写程序时就已知的值!
// 但是对于下划线这种情况,所有不满足前面的case的值,都会进入这种默认情况进行处理,此时如果我们在处理语句中需要拿到具体的值进行处理呢?那就需要使用这种在模式匹配中进行变量赋值的语法!!

// 案例:成绩评价(升级版)

def judgeGrade(name: String, grade: String) {
  grade match {
    case "A" => println(name + ", you are excellent")
    case "B" => println(name + ", you are good")
    case "C" => println(name + ", you are just so so")
    case _grade if name == "leo" => println(name + ", you are a good boy, come on, your grade is " + _grade)
    case _grade => println("you need to work harder, your grade is " + _grade)
  }
}

对类型进行模式匹配

// Scala的模式匹配一个强大之处就在于,可以直接匹配类型,而不是值!!!这点是java的switch case绝对做不到的。
// 理论知识:对类型如何进行匹配?其他语法与匹配值其实是一样的,但是匹配类型的话,就是要用“case 变量: 类型 => 代码”这种语法,而不是匹配值的“case 值 => 代码”这种语法。

// 案例:异常处理

import java.io._

def processException(e: Exception) {
  e match {
    case e1: IllegalArgumentException => println("you have illegal arguments! exception is: " + e1)
    case e2: FileNotFoundException => println("cannot find the file you need read or write!, exception is: " + e2)
    case e3: IOException => println("you got an error while you were doing IO operation! exception is: " + e3)
    case _: Exception => println("cannot know which exception you have!" )
  }
}

对Array和List进行模式匹配

// 对Array进行模式匹配,分别可以匹配带有指定元素的数组、带有指定个数元素的数组、以某元素打头的数组
// 对List进行模式匹配,与Array类似,但是需要使用List特有的::操作符

// 案例:对朋友打招呼

def greeting(arr: Array[String]) {
  arr match {
    case Array("Leo") => println("Hi, Leo!")
    case Array(girl1, girl2, girl3) => println("Hi, girls, nice to meet you. " + girl1 + " and " + girl2 + " and " + girl3)
    case Array("Leo", _*) => println("Hi, Leo, please introduce your friends to me.")
    case _ => println("hey, who are you?")
  }
}

def greeting(list: List[String]) {
  list match {
    case "Leo" :: Nil => println("Hi, Leo!")
    case girl1 :: girl2 :: girl3 :: Nil => println("Hi, girls, nice to meet you. " + girl1 + " and " + girl2 + " and " + girl3)
    case "Leo" :: tail => println("Hi, Leo, please introduce your friends to me.")
    case _ => println("hey, who are you?")
  }
}

case class与模式匹配

// Scala中提供了一种特殊的类,用case class进行声明,中文也可以称作样例类。case class其实有点类似于Java中的JavaBean的概念。即只定义field,并且由Scala编译时自动提供getter和setter方法,但是没有method。
// case class的主构造函数接收的参数通常不需要使用var或val修饰,Scala自动就会使用val修饰(但是如果你自己使用var修饰,那么还是会按照var来)
// Scala自动为case class定义了伴生对象,也就是object,并且定义了apply()方法,该方法接收主构造函数中相同的参数,并返回case class对象

// 案例:学校门禁

class Person
case class Teacher(name: String, subject: String) extends Person
case class Student(name: String, classroom: String) extends Person

def judgeIdentify(p: Person) {
  p match {
    case Teacher(name, subject) => println("Teacher, name is " + name + ", subject is " + subject)
    case Student(name, classroom) => println("Student, name is " + name + ", classroom is " + classroom)
    case _ => println("Illegal access, please go out of the school!")
  }  
}

Option与模式匹配

// Scala有一种特殊的类型,叫做Option。Option有两种值,一种是Some,表示有值,一种是None,表示没有值。
// Option通常会用于模式匹配中,用于判断某个变量是有值还是没有值,这比null来的更加简洁明了
// Option的用法必须掌握,因为Spark源码中大量地使用了Option,比如Some(a)、None这种语法,因此必须看得懂Option模式匹配,才能够读懂spark源码。

// 案例:成绩查询

val grades = Map("Leo" -> "A", "Jack" -> "B", "Jen" -> "C")

def getGrade(name: String) {
  val grade = grades.get(name)
  grade match {
    case Some(grade) => println("your grade is " + grade)
    case None => println("Sorry, your grade information is not in the system")
  }
}

类型参数

引言

类型参数是什么?类型参数其实就类似于Java中的泛型。先说说Java中的泛型是什么,比如我们有List a = new ArrayList(),接着a.add(1),没问题,a.add("2"),然后我们a.get(1) == 2,对不对?肯定不对了,a.get(1)获取的其实是个String——"2",String——"2"怎么可能与一个Integer类型的2相等呢?

所以Java中提出了泛型的概念,其实也就是类型参数的概念,此时可以用泛型创建List,List a = new ArrayListInteger,那么,此时a.add(1)没问题,而a.add("2")呢?就不行了,因为泛型会限制,只能往集合中添加Integer类型,这样就避免了上述的问题。

那么Scala的类型参数是什么?其实意思与Java的泛型是一样的,也是定义一种类型参数,比如在集合,在类,在函数中,定义类型参数,然后就可以保证使用到该类型参数的地方,就肯定,也只能是这种类型。从而实现程序更好的健壮性。

此外,类型参数是Spark源码中非常常见的,因此同样必须掌握,才能看懂spark源码。

泛型类

// 泛型类,顾名思义,其实就是在类的声明中,定义一些泛型类型,然后在类内部,比如field或者method,就可以使用这些泛型类型。
// 使用泛型类,通常是需要对类中的某些成员,比如某些field和method中的参数或变量,进行统一的类型限制,这样可以保证程序更好的健壮性和稳定性。
// 如果不使用泛型进行统一的类型限制,那么在后期程序运行过程中,难免会出现问题,比如传入了不希望的类型,导致程序出问题。
// 在使用类的时候,比如创建类的对象,将类型参数替换为实际的类型,即可。
// Scala自动推断泛型类型特性:直接给使用了泛型类型的field赋值时,Scala会自动进行类型推断。

案例:新生报到,每个学生来自不同的地方,id可能是Int,可能是String

class Student[T](val localId: T) {
  def getSchoolId(hukouId: T) = "S-" + hukouId + "-" + localId
}

val leo = new Student[Int](111)

泛型函数

// 泛型函数,与泛型类类似,可以给某个函数在声明时指定泛型类型,然后在函数体内,多个变量或者返回值之间,就可以使用泛型类型进行声明,从而对某个特殊的变量,或者多个变量,进行强制性的类型限制。
// 与泛型类一样,你可以通过给使用了泛型类型的变量传递值来让Scala自动推断泛型的实际类型,也可以在调用函数时,手动指定泛型类型。

案例:卡片售卖机,可以指定卡片的内容,内容可以是String类型或Int类型

def getCard[T](content: T) = {
  if(content.isInstanceOf[Int]) "card: 001, " + content
  else if(content.isInstanceOf[String]) "card: this is your card, " + content
  else "card: " + content
}

getCard[String]("hello world")

上边界Bounds

// 在指定泛型类型的时候,有时,我们需要对泛型类型的范围进行界定,而不是可以是任意的类型。比如,我们可能要求某个泛型类型,它就必须是某个类的子类,这样在程序中就可以放心地调用泛型类型继承的父类的方法,程序才能正常的使用和运行。此时就可以使用上下边界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 Party[T <: Person](p1: T, p2: T) {
  def play = p1.makeFriends(p2)
}

下边界Bounds

// 除了指定泛型类型的上边界,还可以指定下边界,即指定泛型类型必须是某个类的父类

案例:领身份证

class Father(val name: String) 
class Child(name: String) extends Father(name)

def getIDCard[R >: Child](person: R) {
  if (person.getClass == classOf[Child]) println("please tell us your parents' names.")
  else if (person.getClass == classOf[Father]) println("sign your name for your child's id card.")
  else println("sorry, you are not allowed to get id card.")
}

View Bounds

// 上下边界Bounds,虽然可以让一种泛型类型,支持有父子关系的多种类型。但是,在某个类与上下边界Bounds指定的父子类型范围内的类都没有任何关系,则默认是肯定不能接受的。
// 然而,View Bounds作为一种上下边界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) }

implicit def dog2person(dog: Object): Person = if(dog.isInstanceOf[Dog]) {val _dog = dog.asInstanceOf[Dog]; new Person(_dog.name) } else Nil

class Party[T <% Person](p1: T, p2: T)

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

Manifest Context Bounds

// 在Scala中,如果要实例化一个泛型数组,就必须使用Manifest Context Bounds。也就是说,如果数组元素类型为T的话,需要为类或者函数定义[T: Manifest]泛型类型,这样才能实例化Array[T]这种泛型数组。

案例:打包饭菜(一种食品打成一包)

class Meat(val name: String)
class Vegetable(val name: String)

def packageFood[T: Manifest] (food: T*) = {
  val foodPackage = new Array[T](food.length)
  for(i <- 0 until food.length) foodPackage(i) = food(i)
  foodPackage 
}

协变和逆变

Scala的协变和逆变是非常有特色的!完全解决了Java中的泛型的一大缺憾!
举例来说,Java中,如果有Professional是Master的子类,那么Card[Professionnal]是不是Card[Master]的子类?答案是:不是。因此对于开发程序造成了很多的麻烦。
而Scala中,只要灵活使用协变和逆变,就可以解决Java泛型的问题。

案例:进入会场

class Master
class Professional extends Master

// 大师以及大师级别以下的名片都可以进入会场
class Card[+T] (val name: String)
def enterMeet(card: Card[Master]) {
  println("welcome to have this meeting!")
}

// 只要专家级别的名片就可以进入会场,如果大师级别的过来了,当然可以了!
class Card[-T] (val name: String)
def enterMeet(card: Card[Professional]) {
  println("welcome to have this meeting!")
}

Existential Type

// 在Scala里,有一种特殊的类型参数,就是Existential Type,存在性类型。这种类型务必掌握是什么意思,因为在spark源码实在是太常见了!

Array[T] forSome { type T }
Array[_]

隐式转换与隐式参数

引言

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

Scala的隐式转换,其实最核心的就是定义隐式转换函数,即implicit conversion function。定义的隐式转换函数,只要在编写的程序内引入,就会被Scala自动使用。Scala会根据隐式转换函数的签名,在程序中使用到隐式转换函数接收的参数类型定义的对象时,会自动将其传入隐式转换函数,转换为另外一种类型的对象并返回。这就是“隐式转换”。

隐式转换函数叫什么名字是无所谓的,因为通常不会由用户手动调用,而是由Scala进行调用。但是如果要使用隐式转换,则需要对隐式转换函数进行导入。因此通常建议将隐式转换函数的名称命名为“one2one”的形式。

Spark源码中有大量的隐式转换和隐式参数,因此必须精通这种语法。

隐式转换

要实现隐式转换,只要程序可见的范围内定义隐式转换函数即可。Scala会自动使用隐式转换函数。隐式转换函数与普通函数唯一的语法区别就是,要以implicit开头,而且最好要定义函数返回类型。

// 案例:特殊售票窗口(只接受特殊人群,比如学生、老人等)

class SpecialPerson(val name: String)
class Student(val name: String)
class Older(val name: String)

implicit def object2SpecialPerson (obj: Object): SpecialPerson = {
  if (obj.getClass == classOf[Student]) { val stu = obj.asInstanceOf[Student]; new SpecialPerson(stu.name) }
  else if (obj.getClass == classOf[Older]) { val older = obj.asInstanceOf[Older]; new SpecialPerson(older.name) }
  else Nil
}

var ticketNumber = 0
def buySpecialTicket(p: SpecialPerson) = {
  ticketNumber += 1
  "T-" + ticketNumber
}

使用隐式转换加强现有类型

隐式转换非常强大的一个功能,就是可以在不知不觉中加强现有类型的功能。也就是说,可以为某个类定义一个加强版的类,并定义互相之间的隐式转换,从而让源类在使用加强版的方法时,由Scala自动进行隐式转换为加强类,然后再调用该方法。

// 案例:超人变身

class Man(val name: String)
class Superman(val name: String) {
  def emitLaser = println("emit a laster!")
}

implicit def man2superman(man: Man): Superman = new Superman(man.name)

val leo = new Man("leo")
leo.emitLaser

隐式转换函数作用域与导入

Scala默认会使用两种隐式转换,一种是源类型,或者目标类型的伴生对象内的隐式转换函数;一种是当前程序作用域内的可以用唯一标识符表示的隐式转换函数。

如果隐式转换函数不在上述两种情况下的话,那么就必须手动使用import语法引入某个包下的隐式转换函数,比如import test._。通常建议,仅仅在需要进行隐式转换的地方,比如某个函数或者方法内,用import导入隐式转换函数,这样可以缩小隐式转换函数的作用域,避免不需要的隐式转换。

隐式转换的发生时机

// 1、调用某个函数,但是给函数传入的参数的类型,与函数定义的接收参数类型不匹配(案例:特殊售票窗口)
// 2、使用某个类型的对象,调用某个方法,而这个方法并不存在于该类型时(案例:超人变身)
// 3、使用某个类型的对象,调用某个方法,虽然该类型有这个方法,但是给方法传入的参数类型,与方法定义的接收参数的类型不匹配(案例:特殊售票窗口加强版)

// 案例:特殊售票窗口加强版

class TicketHouse {
  var ticketNumber = 0
  def buySpecialTicket(p: SpecialPerson) = {
    ticketNumber += 1
    "T-" + ticketNumber
  }
}

隐式参数

// 所谓的隐式参数,指的是在函数或者方法中,定义一个用implicit修饰的参数,此时Scala会尝试找到一个指定类型的,用implicit修饰的对象,即隐式值,并注入参数。
// Scala会在两个范围内查找:一种是当前作用域内可见的val或var定义的隐式变量;一种是隐式参数类型的伴生对象内的隐式值

// 案例:考试签到

class SignPen {
  def write(content: String) = println(content)
}
implicit val signPen = new SignPen

def signForExam(name: String) (implicit signPen: SignPen) {
  signPen.write(name + " come to exam in time.")
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335