6.1.3 Scala《特质》接口实现,构造顺序,继承,order,《匹配/样例》模式/字符/串/守卫/类型/数组元组集合,Opt,《函数/抽象化》区分,匿名占位符高阶,闭包,柯里化,部分应用,偏函数

本文围绕Scala展开,介绍了特质的多种用法,如作为接口、带有具体实现等,还阐述了特质构造顺序和继承类的情况。同时讲解了模式匹配机制,可匹配类型、集合等,以及样例类的特点。此外,对函数及抽象化进行了说明,包括函数字面量、高阶函数、闭包等概念。

目录

第六部分 特质

第1节 作为接口使用的特质

第2节 带有具体实现的特质

第3节 特质构造顺序

第4节 特质继承类

第5节 Ordered和Ordering

第七部分 模式匹配和样例类

第1节 模式匹配

第2节 字符和字符串匹配

第3节 守卫式匹配 match case if

第4节 匹配类型 typeMatch

第5节 匹配数组、元组、集合

第6节 样例类 (类比抽象类的模板)

第7节 Option与模式匹配

第八部分 函数及抽象化

第1节 函数字面量及函数的定义

第2节 函数与方法的区别

第3节 匿名函数与占位符

第4节 高阶函数

第5节 闭包

第6节 柯里化

第7节 部分应用函数

第8节 偏函数


 

第六部分 特质

第1节 作为接口使用的特质

Scala中的trait特质是一种特殊的概念。
首先可以将trait作为接口来使用,此时的trait就与Java中的接口非常类似。
在trait中可以定义抽象方法,与抽象类中的抽象方法一样,只要不给出方法的具体实现即可。
类可以使用extends关键字继承trait。
注意:在Scala中没有implement的概念,无论继承类还是trait特质,统一都是extends。
类继承trait特质后,必须实现其中的抽象方法,实现时可以省略override关键字。
Scala不支持对类进行多继承,但是支持多重继承trait特质,使用with关键字即可。

package com.ch.part06

trait HelloTrait{
  def sayHello    // 抽象方法
}

trait MakeFriendTrait{
  def makFriend
}

//如果一个类继承了多个Trait特质,第一个Trait用extends,其他Trait用with关键字
class Person(name:String) extends HelloTrait with MakeFriendTrait{

  override def sayHello: Unit = {
    println(s"Hello, My name is $name")
  }

  override def makFriend: Unit = {
    println(s"Hello,$name")
  }
}

object TraitDemo {
  def main(args: Array[String]): Unit = {
    val person=new Person("jacky")
    person.sayHello
    person.makFriend  // Hello, My name is jacky    Hello,jacky
  }
}

 

第2节 带有具体实现的特质

具体方法
Scala中的trait特质不仅仅可以定义抽象方法,还可以定义具体实现的方法,这时的trait更像是包含了通用工具方法的类。比如,trait中可以包含一些很多类都通用的功能方法,比如打印日志等等,Spark中就使用了trait来定义通用的日志打印方法。
具体字段
Scala trait特质中的字段可以是抽象的,也可以是具体的。

package com.ch.part06

// 与java的接口的区别, 主要是, trait 中可以有非抽象字段
trait People {

  val name: String  // 抽象字段
  val age = 30

  def eat: Unit = {
    println("Eating......")
  }
}

trait Worker {
  val age = 25

  def work: Unit = {
    println("Working......")
  }
}

class Student extends Worker with People {

  //重写name抽象字段,此处override可以省略
  override val name: String = "lisi"

  //由于Worker和People中都有age字段,所以当Student类继承这两个特质时,需要重写age字段
  //并且要使用override关键字,否则就会报错。
  //此时的override关键字不能省略。
  override val age = 20
}

object TraitDemoTwo {

  def main(args: Array[String]): Unit = {
    val stu = new Student
    stu.eat                                       // Eating......
    stu.work                                      // Working......
    println(s"姓名:${stu.name},年龄:${stu.age}")  // 姓名:lisi,年龄:20
  }
}

注意:特质Person和Worker中都有age字段,当Student继承这两个特质时,需要重写age字段,并且要用override关键字,否则就会报错。

 

第3节 特质构造顺序

在Scala中,trait特质也是有构造器的,也就是trait中的不包含在任何方法中的代码。
构造器以如下顺序执行:
1、执行父类的构造器;
2、执行trait的构造器,多个trait从左到右依次执行;
3、构造trait时会先构造父trait,如果多个trait继承同一个父trait,则父trait只会构造一次;
4、所有trait构造完毕之后,子类的构造器才执行

package com.ch.part06

class Person2 {
  println("Person's Constructor!")
}

trait Logger {
  println("Logger's Constructor!")
}

trait MyLogger extends Logger {
  println("MyLogger's Constructor!")
}

trait TimeLogger extends Logger {
  println("TimeLogger's Constructor!")
}

//如果一个类既继承了父类,也继承特质,那么要先写父类,再写特质
//extends 父类  with 特质1 with 特质2.....
class Student2 extends Person2 with MyLogger with TimeLogger {
  println("Student's Constructor!")
}

object TraitDemoThree {
  def main(args: Array[String]): Unit = {
    val stu = new Student2
  }
}

上面代码的输出结果:

 

第4节 特质继承类

在Scala中,trait特质也可以继承class类,此时这个class类就会成为所有继承此trait的类的父类。

package com.ch.part06

class MyUtil {

  def printMessage(msg: String): Unit = {
    println(msg)
  }
}

trait Log extends MyUtil {

  def log(msg: String): Unit = {
    println(msg)
  }
}

//Person3继承了Log特质,Log特质继承了MyUtil类,那么MyUtil类就成为Person3的父类
// Person3中就可以调用其父类中的方法
class Person3(name: String) extends Log {

  def sayHello: Unit = {

    log("hello, " + name)
    printMessage("hi, " + name)
  }
}

object TraitDemoFour {

  def main(args: Array[String]): Unit = {

    val person = new Person3("jacky")     // hello, jacky
    person.sayHello                              // hi, jacky
  }
}

 

第5节 Ordered和Ordering

在Java中对象的比较有两个接口,分别是Comparable和Comparator。它们之间的区别在于:
实现Comparable接口的类,重写compareTo()方法后,其对象自身就具有了可比较性;
实现Comparator接口的类,重写了compare()方法后,则提供一个第三方比较器,用于比较两个对象。
在Scala中也引入了以上两种比较方法(Scala.math包下):

Ordered特质混入Java的Comparable接口,它定义了相同类型间的比较方式,但这种内部比较方式是单一的;

trait Ordered[A] extends Any with java.lang.Comparable[A]{......}

 

Ordering特质混入Comparator接口,它是提供第三方比较器,可以自定义多种比较方式,在实际开发中也是使用比较多的,灵活解耦合。

trait Ordering[T] extends Comparator[T] with PartialOrdering[T] with Serializable {......}

 

使用Ordered特质进行排序操作

使用Ordering特质进行排序操作

package com.ch.part06

import scala.util.Sorting

// 样例类
case class Project(tag: String, score: Int) extends Ordered[Project] {

  override def compare(that: Project): Int = {
    tag.compareTo(that.tag)
  }
}

object OrderDemo {

  def main(args: Array[String]): Unit = {
    val list = List(Project("hadoop", 40), Project("flink", 90), Project("spark", 80), Project("hive", 60))
    
    // Ordered 按字典排序
    println(list.sorted)  // List(Project(flink,90), Project(hadoop,40), Project(hive,60), Project(spark,80))

    val pairs = Array(("a", 7, 2), ("b", 9, 1), ("c", 8, 3))
    
    // Ordering 排序
    //Ordering.by[(String,Int,Int),Int](_._2)表示从Tuple3转到Int型,根据Tuple3中的第二个元素进行排序
    Sorting.quickSort(pairs)(Ordering.by[(String,Int,Int),Int](_._2))
    println(pairs.toBuffer)   // ArrayBuffer((a,7,2), (c,8,3), (b,9,1))
  }
}

 

 

第七部分 模式匹配和样例类

第1节 模式匹配

Scala没有Java中的switch case,它有一个更加强大的模式匹配机制,可以应用到很多场合。

Scala的模式匹配可以匹配各种情况,比如变量的类型、集合的元素、有值或无值。

模式匹配的基本语法结构:变量 match { case 值 => 代码 }

模式匹配match case中,只要有一个case分支满足并处理了,就不会继续判断下一个case分支了,不需要使用break语句。这点与Java不同,Java的switch case需要用break阻止。如果值为下划线,则代表不满足以上所有情况的时候如何处理。

模式匹配match case最基本的应用,就是对变量的值进行模式匹配。match是表达式,与if表达式一样,是有返回值的。

除此之外,Scala还提供了样例类,对模式匹配进行了优化,可以快速进行匹配。

 

第2节 字符和字符串匹配

package com.ch.part07

import scala.util.Random

object MatchDemo {

  def main(args: Array[String]): Unit = {
    //对字符进行模式匹配
    val character = '*'
    character match {
      case '+' => println("加号")
      case '-' => println("减号")
      case '*' => println("乘号")
      case '/' => println("除号")
      case _ => println("不是运算符!")
    }

    //对字符串进行模式匹配
    val content = Array("hadoop", "spark", "flink")
    val project = content(Random.nextInt(content.length))

    project match {
      case "hadoop" => println("大数据计算框架")
      case "spark" => println("大数据内存框架")
      case "flink" => println("大数据实时框架")
      case _ => println("不送,慢走~~")
    }
  }
}

 

第3节 守卫式匹配 match case if

package com.ch.part07

import scala.util.Random

object MatchDemo {

  def main(args: Array[String]): Unit = {

    //守卫式匹配,添加if判断
    val char = '/'
    val num = char match {
      case '+' => 1
      case '-' => 2
      case _ if char.equals('*') => 3
      case _ => 4
    }
    println(num)
  }
}

 

第4节 匹配类型 typeMatch

Scala的模式匹配还有一个强大的功能,它可以直接匹配类型,而不是值。这一点是Java的switch case做不到的。
匹配类型的语法:case 变量 : 类型 => 代码,而不是匹配值的“case 值 => 代码”这种语法。

package com.ch.part07

import scala.util.Random

object MatchDemo {

  //匹配类型,语法:case 变量:类型 => 代码
  def typeMatch(x: Any) = {

    x match {
      case x: String => println("这是一个字符串类型")
      case x: Int => println("是一个整型")
      case x: Boolean if (x == false) => println("是一个false的布尔类型")
      case x: Array[Int] => println("是一个整型数组")
      case _ => println("不知是啥类型!")
    }
  }

  def main(args: Array[String]): Unit = {

    typeMatch(Array(1,2))
  }
}

 

第5节 匹配数组、元组、集合

package com.ch.part07

object MatchCollection {
  def main(args: Array[String]): Unit = {

    val array = Array(3, 5, 7)

    //对Array数组进行模式匹配,分别匹配:
    //带有指定个数元素的数组、带有指定元素的数组、以某元素开头的数组
    array match {
      case Array(1, x, y) => println(x + " " + y)
      case Array(1) => println("only 1...")
      case Array(1, _*) => println("以1开头的数组")
      case _ => println("something else ...")
    }

    val list = List(3, 4)

    //对List列表进行模式匹配,分别匹配:
    //带有指定个数元素的列表、带有指定元素的列表、以某元素开头的列表
    //Nil表示一个空列表,::起到一个连接的作用,表示将元素添加到某列表中   类比  12 + ""
    //tail表示返回除第一个元素之外的其他元素的列表
    list match {
      case x :: y :: Nil => println(s"$x  $y")
      case 2 :: Nil => println("only 2...")
      case 1 :: tail => println("以1开头的列表")
      case _ => println("something else...")
    }

    val tuple = (3, 4, 5)
    tuple match {

      //以1开头,包含三个元素的元组
      case (1, x, y) => println(s"$x  $y")
      //以5结尾,包含三个元素的元组
      case (_, z, 5) => println(z)
      case _ => println("else")
    }
  }
}

 

第6节 样例类 (类比抽象类的模板)

case class样例类是Scala中特殊的类。当声明样例类时,以下事情会自动发生:

  • 主构造函数接收的参数通常不需要显式使用var或val修饰,Scala会自动使用val修饰
  • 自动为样例类定义了伴生对象,并提供apply方法,不用new关键字就能够构造出相应的对象
  • 将生成toString、equals、hashCode和copy方法,除非显示的给出这些方法的定义
  • 继承了Product和Serializable这两个特质,也就是说样例类可序列化和可应用Product的方法

 

case class是多例的,后面要跟构造参数,case object是单例的。

此外,case class样例类中可以添加方法和字段,并且可用于模式匹配。

package com.ch.part07

class Amount

//样例类中主构造器的参数默认用val修饰
//样例类中自动生成apply方法
case class Dollar(value: Double) extends Amount

case class Currency(value: Double, unit: String) extends Amount

case object Nothing extends Amount

object CaseClassDemo {

  def main(args: Array[String]): Unit = {
    
    judgeIdentity(Dollar(10.0))
    judgeIdentity(Currency(20.0, "100"))
    judgeIdentity(Nothing)
  }

  def judgeIdentity(amt: Amount): Unit = {

    amt match {
      case Dollar(value) => println(s"$value")
      case Currency(value, unit) => println(s"$value   $unit")
      case Nothing => println("nothing")
    }
  }
}

 

第7节 Option与模式匹配

Scala Option选项类型用来表示一个值是可选的,有值或无值。
Option[T] 是一个类型为 T 的可选值的容器,可以通过get()函数获取Option的值。如果值存在,Option[T] 就是一个 Some。如果不存在,Option[T] 就是对象 None 。
Option通常与模式匹配结合使用,用于判断某个变量是有值还是无值。

package com.ch.part07

object OptionDemo {

  val grades = Map("jacky" -> 90, "tom" -> 80, "jarry" -> 60)

  def getGrade(name: String): Unit = {

    // 下面的grade是一个容器, 有值Some 或无值 None
    val grade: Option[Int] = grades.get(name)
    grade match {
      case Some(grade) => println("成绩是:" + grade)
      case None => println("没有此人成绩!")
    }
  }

  def main(args: Array[String]): Unit = {

    getGrade("jacky")
    getGrade("lisi")
  }
}

 

第八部分 函数及抽象化

第1节 函数字面量及函数的定义

Scala中函数为头等公民,不仅可以定义一个函数然后调用它,还可以写一个未命名的函数字面量,然后可以把它当成一个值传递到其它函数或是赋值给其它变量。

函数字面量体现了函数式编程的核心理念。字面量包括整数字面量、浮点数字面量、布尔型字面量、字符字面量、字符串字面量、符号字面量、函数字面量等。

什么是函数字面量呢?

在函数式编程中,函数是“头等公民”,可以像任何其他数据类型一样被传递和操作。函数的使用方式和其他数据类型的使用方式完全一致,可以像定义变量那样去定义一个函数,函数也会和其他变量一样,有类型有值;

就像变量的“类型”和“值”是分开的两个概念一样,函数的“类型”和“值”也成为两个分开的概念;
函数的“值”,就是“函数字面量”。

def add1(x: Int): Int = { x + 1 }
// add1: (x: Int)Int
// 函数的类型为: (Int) => Int
// 输入参数列表只有一个括号,可以简写为: Int => Int

def add2(x: Int, y: Int): Int = { x + y }
// add2: (x: Int, y: Int)Int
// 函数的类型为: (Int, Int) => Int

def add3(x: Int, y: Int, z: Int): Int = { x + y + z }
// add3: (x: Int, y: Int, z: Int)Int
// 函数的类型为: (Int, Int, Int) => Int

def add4(x: Int, y: Int, z: Int): (Int, Int) = { (x + y, y + z) }
// add4: (x: Int, y: Int, z: Int)(Int, Int)
// 函数的类型为: (Int, Int, Int) => (Int, Int)

 

函数类型:(输入参数类型列表) => (输出参数类型列表)

只有一个参数时,小括号可省略;函数体中只有1行语句时,大括号可以省略;

把函数定义中的类型声明部分去除,剩下的就是函数的“值”,即函数字面量:

  • 对 add1 而言函数的值为:(x) => x+1
  • 对 add2 而言函数的值为:(x, y) => x+y
  • 对 add3 而言函数的值为:(x, y, z) => x+y+z
  • 对 add4 而言函数的值为:(x, y, z) => (x+y, y+z)

 

在Scala中我们这样定义变量: val 变量名: 类型 = 值 ;
我们可以用完全相同的方式定义函数: val 函数名: 函数类型 = 函数字面量

val add1: Int => Int = (x) => x+1
val add2: (Int, Int) => Int = (x, y) => x + y
val add3: (Int, Int, Int) => Int = (x, y, z) => x + y + z
val add4: (Int, Int, Int) => (Int, Int) = (x, y, z) => (x + y, y + z)

 

在Scala中有自动类型推断,所以可以省略变量的类型  val 变量名 = 值 。
同样函数也可以这样: val 函数名 = 函数字面量

val add1 = (x: Int) => x + 1
val add2 = (x: Int, y: Int) => x + y
val add3 = (x: Int, y: Int, z: Int) => x + y + z
val add4 = (x: Int, y: Int, z: Int) => (x + y, y + z)

备注:要让编译器进行自动类型推断,要告诉编译器足够的信息,所以添加了 x 的类型信息。

 

函数的定义:

val 函数名: (参数类型1,参数类型2) => (返回类型) = 函数字面量
val 函数名 = 函数字面量

函数字面量:  (参数1:类型1,参数2:类型2)  =>  函数体
val 函数名 = (参数1:类型1,参数2:类型2)  =>  函数体

 

第2节 函数与方法的区别

scala> def addm(x: Int, y: Int): Int = x + y
addm: (x: Int, y: Int)Int

scala> val addf = (x: Int, y: Int) => x + y
addf: (Int, Int) => Int = <function2>

严格的说:使用 val 定义的是函数(function),使用 def 定义的是方法(method)。二者在语义上的区别很小,在绝大多数情况下都可以不去理会它们之间的区别,但是有时候有必要了解它们之间的不同。
Scala中的方法与函数有以下区别:

  • Scala 中的方法与 Java 的类似,方法是组成类的一部分
  • Scala 中的函数则是一个完整的对象。Scala 中用 22 个特质(从 Function1 到 Function22)抽象出了函数的概念
  • Scala 中用 val 语句定义函数,def 语句定义方法
// 下面用三种方式定义了函数,其中第二种方式最常见
val adder1: (Int, Int) => Int = (x, y) => x+y
val adder2 = (x: Int, y: Int) => x+y

// Function2是特质,不能直接new
// new Function2[Int,Int,Int]{ ... } 其实是定义并实例化一个实现了 Function2 特质的类的对象
val adder3 = new Function2[Int, Int, Int]{
  def apply(x: Int, y: Int): Int = {
    x + y
  }
}

 

  • 方法不能作为单独的表达式而存在,而函数可以;
  • 函数必须要有参数列表,而方法可以没有参数列表;
  • 方法名是方法调用,而函数名只是代表函数对象本身;
  • 在需要函数的地方,如果传递一个方法,会自动把方法转换为函数
// 方法不能作为单独的表达式而存在,而函数可以
 def addm(x: Int, y: Int): Int = x + y
// addm: (x: Int, y: Int)Int

 val addf = (x: Int, y: Int) => x + y
// addf: (Int, Int) => Int = <function2>

 addm
// error: missing argument list for method addm

 addf
// res8: (Int, Int) => Int = <function2>

// 函数必须要有参数列表,而方法可以没有参数列表
 def m1 = "This is lagou edu"
// m1: String

// 函数必须有参数列表
 val f1 = () => "This is lagou edu"
// f1: () => String = <function0>

// 方法名是方法调用
 m1
// res16: String = This is lagou edu

// 函数名代表函数对象
 f1
// res17: () => String = <function0>

// 这才代表函数调用
 f1()
// res18: String = This is lagou edu

// 需要函数的地方,可以传递一个方法
 val list = (1 to 10).toList
// lst: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

 def double(x: Int) = x*x
// double: (x: Int)Int

// 下面对list中的每一个元素进行求平方操作
 list.map(double(_))
// res20: List[Int] = List(1, 4, 9, 16, 25, 36, 49, 64, 81, 100)

 

将方法转换为函数:

// 方法名 _
def f1 = double _  //注意:方法名与下划线之间有一个空格
// f1: Int => Int

f1
// res21: Int => Int = <function1>

写程序的时候是定义方法、还是定义函数?
一般情况下,不对二者做区分,认为都是函数,更多的时候使用def定义函数。

 

第3节 匿名函数与占位符

函数没有名字就是匿名函数;
匿名函数,又被称为 Lambda 表达式。 Lambda表达式的形式如下:
(参数名1: 类型1, 参数名2: 类型2, ... ...) => 函数体

// 定义匿名函数
 (x: Int) => x + 1
// res0: Int => Int = <function1>

// 函数没有名字,在集成开发环境中是无法被调用的
 res0(10)
// res1: Int = 11

 val list = (1 to 10).toList
// lst: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

// 将匿名函数作为参数传递给另一个函数
// 下面对list的每个元素进行+1操作
 list.map((x: Int) => x + 1)
// res2: List[Int] = List(2, 3, 4, 5, 6, 7, 8, 9, 10, 11)

// x一定是Int类型,这里可以省略
 list.map((x) => x + 1)
// res3: List[Int] = List(2, 3, 4, 5, 6, 7, 8, 9, 10, 11)

// 只有一个参数,小括号可以省略
 list.map(x => x + 1)
// res4: List[Int] = List(2, 3, 4, 5, 6, 7, 8, 9, 10, 11)

// 使用占位符简化函数字面量
 list.map(_ + 1)
// res5: List[Int] = List(2, 3, 4, 5, 6, 7, 8, 9, 10, 11)

// 实现将List中的每个元素*2 + 1,但是出错了
 list.map(_ + _ + 1)
//error: missing parameter type for expanded function ((x$1, x$2) => x$1.$plus(x$2).$plus(1))

// 这样是可行的
 list.map(2 * _ + 1)

// 通过reduce这个高阶函数,将list列表中的元素相加求和
 list.reduce((x,y) => x + y)
 // res0: Int = 55

// 使用占位符简化函数字面量
// 第一个下划线代表第一个参数,第二个下划线代表第二个参数
 list.reduce(_ + _)
 // res1: Int = 55

多个下划线指代多个参数,而不是单个参数的重复运用
第一个下划线代表第一个参数
第二个下划线代表第二个参数
第三个……,如此类推

 

第4节 高阶函数

高阶函数:接收一个或多个函数作为输入 或 输出一个函数。
函数的参数可以是变量,而函数又可以赋值给变量,由于函数和变量地位一样,所以函数参数也可以是函数;
常用的高阶函数:map、reduce、flatMap、foreach、filter、count … … (接收函数作为参数)

package com.ch.part08

object HighFunction {

  def main(args: Array[String]): Unit = {

    //接收一个或多个函数作为输入的高阶函数
    val func = n => "*" * n
    (1 to 5).map(func(_)).foreach(println)

    //输出一个函数的高阶函数
    val URLBuilder = (ssl: Boolean, domainName: String) => {

      val schema = if (ssl) "https://" else "http://"
      //输出一个匿名函数
      (endpoint: String, query: String) => s"$schema$domainName/$endpoint?$query"
    }
    val domainName = "www.lagou.com"

    def getURl = URLBuilder(true, domainName)

    val endpoint = "show"
    val query = "id=1"
    val url = getURl(endpoint, query)
    println(url)    // https://www.lagou.com/show?id=1
  }
}

 

第5节 闭包

闭包是一种函数,一种比较特殊的函数,它和普通的函数有很大区别:

  // 普通的函数
  val addMore1 = (x: Int) => x + 10

  // 外部变量,也称为自由变量
  var more = 10

  // 闭包
  val addMore2 = (x: Int) => x + more

  // 调用addMore1函数
  println(addMore1(5))   // 15

  // 每次addMore2函数被调用时,都会去捕获外部的自由变量
  println(addMore2(10))  // 15

  more = 100
  println(addMore2(10))  // 110

  more = 1000
  println(addMore2(10))  // 1010

闭包是在其上下文中引用了自由变量的函数;
闭包引用到函数外面定义的变量,定义这个函数的过程就是将这个自由变量捕获而构成的一个封闭的函数,也可理解为”把函数外部的一个自由变量关闭进来“。

闭包需满足下面三个条件:
1、闭包是一个函数
2、函数必须要有返回值
3、返回值依赖声明在函数外部的一个或多个变量,用Java的话说,就是返回值和定义的全局变量有关

 

第6节 柯里化

函数编程中,接收多个参数的函数都可以转化为接收单个参数的函数,这个转化过程就叫柯里化(Currying)。

Scala中,柯里化函数的定义形式和普通函数类似,区别在于柯里化函数拥有多组参数列表,每组参数用小括号括起来。

Scala API中很多函数都是柯里化的形式。

// 使用普通的方式
def add1(x: Int, y: Int) = x + y

// 使用闭包的方式,将其中一个函数作为返回值
def add2(x: Int) = (y:Int) => x + y

// 使用柯里化的方式
def add(x: Int)(y: Int) = x + y

//调用柯里化函数add
scala> add(1)(2)
res1: Int = 3

//add(1)(2)实际上第一次调用使用参数x,返回一个函数类型的值,第二次使用参数y调用这个函数类型的值。
//实际上最先演变成这样的函数:def add(x: Int) = (y:Int) => x + y
//在这个函数中,接收一个x为参数,返回一个匿名函数,这个匿名函数的定义是:接收一个Int型参数y,函数体是x+y。
//调用过程如下:
scala> val result=add(1)
result: Int => Int = <function1>

scala> val sum=result(2)
sum: Int = 3

scala> sum
res0: Int = 3

 

第7节 部分应用函数

部分应用函数(Partial Applied Function)也叫偏应用函数,与偏函数从名称上看非常接近,但二者之间却有天壤之别。
部分应用函数是指缺少部分(甚至全部)参数的函数。
如果一个函数有n个参数, 而为其提供少于n个参数, 那就得到了一个部分应用函数。

// 定义一个函数
scala> def add(x:Int, y:Int, z:Int) = x+y+z
add: (x: Int, y: Int, z: Int)Int

// Int不能省略
scala> def addX = add(1, _:Int, _:Int)
addX: (Int, Int) => Int

scala> addX(2,3)
res0: Int = 6

scala> addX(3,4)
res1: Int = 8

scala> def addXAndY = add(10, 100, _:Int)
addXAndY: Int => Int

scala> addXAndY(1)
res2: Int = 111

scala> def addZ = add(_:Int, _:Int, 10)
addZ: (Int, Int) => Int

scala> addZ(1,2)
res3: Int = 13

// 省略了全部的参数,下面两个等价。第二个更常用
scala> def add1 = add(_: Int, _: Int, _: Int)
add1: (Int, Int, Int) => Int

scala> def add2 = add _
add2: (Int, Int, Int) => Int

 

第8节 偏函数

偏函数(Partial Function)之所以“偏”,原因在于它们并不处理所有可能的输入,而只处理那些能与至少一个 case 语句匹配的输入;
偏函数中只能使用 case 语句,整个函数必须用大括号包围。这与普通的函数字面量不同,普通的函数字面量可以使用大括号,也可以用小括号;
被包裹在大括号中的一组case语句是一个偏函数,是一个并非对所有输入值都有定义的函数;
Scala中的Partial Function是一个trait,其类型为PartialFunction[A,B],表示:接收一个类型为A的参数,返回一个类型为B的结果。

scala> val pf: PartialFunction[Int, String] = {
     |  case 1 => "One"
     |  case 2 => "Two"
     |  case 3 => "Three"
     |  case _=> "Other"
     | }
pf: PartialFunction[Int,String] = <function1>

scala> pf(1) // 返回: One
res0: String = One

scala> pf(2) // 返回: Two
res1: String = Two

scala> pf(5) // 返回: Other
res2: String = Other

 

需求:过滤List中的String类型的元素,并将Int类型的元素加1。
通过偏函数实现上述需求。

package com.ch.part08

object PartialFunctionDemo {

  def main(args: Array[String]): Unit = {

    // [Any,Int],偏函数接收的数据类型是Any,返回的数据类型是Int
    val partialFunction = new PartialFunction[Any, Int] {
      
      // 如果返回true,那么就调用apply方法;如果返回的是false,就过滤掉
      override def isDefinedAt(x: Any): Boolean = {
        println(x.toString)
        x.isInstanceOf[Int]
      }

      //对传入的整数值+1,并将其返回
      override def apply(v1: Any): Int = {
        println(v1.toString)
        v1.asInstanceOf[Int] + 1
      }
    }

    // 过滤字符串,对整型+1
    // collect通过执行一个并行计算(偏函数),得到一个新的数组对象
    val list=List(10,"hadoop",20,"spark",30,"flink")
    list.collect(partialFunction).foreach(println(_))

    // 实际上采用下面的一行代码, 就可以实现上面所有语句的功能
    list.collect{case x: Int => x+1}.foreach(println)
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值