Scala语言 · 入门学习笔记

Scala语言


W3Cschool的Scala文档:https://www.w3cschool.cn/scala/

菜鸟教程:https://www.runoob.com/scala/scala-tutorial.html

Scala 是一门多范式的编程语言,一种类似 Java 的编程语言,设计初衷是要集成面向对象编程和函数式编程的各种特性。

Scala 特性

面向对象特性

Scala是一种纯面向对象的语言,每个值都是对象。对象的数据类型以及行为由类和特质描述。

类抽象机制的扩展有两种途径:一种途径是子类继承,另一种途径是灵活的混入机制。这两种途径能避免多重继承的种种问题。

函数式编程

Scala也是一种函数式语言,其函数也能当成值来使用。Scala提供了轻量级的语法用以定义匿名函数,支持高阶函数,允许嵌套多层函数,并支持柯里化。Scala的case class及其内置的模式匹配相当于函数式编程语言中常用的代数类型。

更进一步,程序员可以利用Scala的模式匹配,编写类似正则表达式的代码处理XML数据。

静态类型

Scala具备类型系统,通过编译时检查,保证代码的安全性和一致性。类型系统具体支持以下特性:

  • 泛型类
  • 协变和逆变
  • 标注
  • 类型参数的上下限约束
  • 把类别和抽象类型作为对象成员
  • 复合类型
  • 引用自己时显式指定类型
  • 视图
  • 多态方法
扩展性

Scala的设计秉承一项事实,即在实践中,某个领域特定的应用程序开发往往需要特定于该领域的语言扩展。Scala提供了许多独特的语言机制,可以以库的形式轻易无缝添加新的语言结构:

  • 任何方法可用作前缀或后缀操作符
  • 可以根据预期类型自动构造闭包。
并发性

Scala使用Actor作为其并发模型,Actor是类似线程的实体,通过邮箱发收消息。Actor可以复用线程,因此可以在程序中可以使用数百万个Actor,而线程只能创建数千个。在2.10之后的版本中,使用Akka作为其默认Actor实现。

第一个Scala程序

先通过创建一个 HelloWorld.scala 的文件来执行代码,

HelloWorld.scala 代码如下所示:

object HelloWorld{
    /* 这是我的第一个 Scala 程序
    * 以下程序将输出'Hello World!' 
    */
    def main(args: Array[String]) {
        println("Hello World!")    // 输出 Hello World
    }
}

通过scala命令来编译

scalac HelloWorld.scala

编译后会生成 HelloWorld.class 文件,该文件可以在Java Virtual Machine (JVM)上运行。

编译后,我们可以使用以下命令来执行程序:

scala HelloWorld

在这里插入图片描述

基础

Scala 基础语法

Scala 与 Java 的最大区别是:Scala 语句末尾的分号 ; 是可选的

我们可以认为 Scala 程序是对象的集合,通过调用彼此的方法来实现消息传递。
接下来我们来理解下,类,对象,方法,实例变量的概念:

  • 对象 - 对象有属性和行为。例如:一只狗的状属性有:颜色,名字,行为有:叫、跑、吃等。对象是一个类的实例。
  • 类 - 类是对象的抽象,而对象是类的具体实例。
  • 方法 - 方法描述的基本的行为,一个类可以包含多个方法。
  • 字段 - 每个对象都有它唯一的实例变量集合,即字段。对象的属性通过给字段赋值来创建。

进入到Scala编程环境

1、表达式

1 + 1

如下图所示:
在这里插入图片描述
res0 是解释器自动创建的变量名称,用来指代表达式的计算结果。

Scala 中(几乎)一切都是表达式。

2、值

val a = 1 + 1

可以给一个表达式的结果起个名字赋成一个不变量(val)。
在这里插入图片描述
但你不能改变这个不变量的值。

3、变量

如果你需要修改这个名称和结果的绑定,可以选择使用 var

var name = "Sartin"

在这里插入图片描述

注意:

val => 不可变

var => 可变

4、函数

①使用 def 创建函数,需要为函数参数指定类型签名。

def addOne(m: Int): Int = m + 1

如下图所示:
在这里插入图片描述

如果函数不带参数,可不写括号

def three() = 1 + 2

如下图所示:
在这里插入图片描述

def three= 1 + 2

如下图所示:
在这里插入图片描述

②匿名函数

匿名函数通过自动创建的变量名称取值

(x: Int) => x + 1

在这里插入图片描述

还可以传递匿名函数,或将其保存成不变量。

val addOne = (x: Int) => x + 1
addOne(2)

在这里插入图片描述

③如果你的函数有很多表达式,可以使用 {} 来格式化代码,使之易读。

def times(i: Int): Int = {
|     println("hello world")
|     i * 2
| }

在这里插入图片描述
匿名函数也是如此。

在将一个匿名函数作为参数进行传递时,这个语法会经常被用到。

{ i: Int =>
| println("hello world")
| i * 2
| }

在这里插入图片描述

5、部分应用

使用下划线“_”部分应用一个函数,结果将得到另一个函数

Scala 使用下划线表示不同上下文中的不同事物,你通常可以把它看作是一个没有命名的神奇通配符。在{ + 2 }的上下文中,它代表一个匿名参数。

你可以部分应用参数列表中的任意参数,而不仅仅是最后一个。

def adder(m: Int, n: Int) = m + n

val add2 = adder(2, _:Int)

在这里插入图片描述

6、柯里化函数

有时会有这样的需求:允许别人一会在你的函数上应用一些参数,然后又应用另外的一些参数。

例如一个乘法函数,在一个场景需要选择乘数,而另一个场景需要选择被乘数。

def multiply(m: Int)(n: Int): Int = m * n

直接传入参数即可。

multiply(2)(3)

在这里插入图片描述

也可以用到上述所说的部分应用,填上第一个参数并且部分应用第二个参数。

val mul2 = multiply(2)_

在这里插入图片描述

可以对任何多参数函数执行柯里化。

例如之前的 adder 函数第一次传参是一个加数,返回一个函数,调用第二个函数传参,另一个数,得出结果是和。

def adder(m: Int, n: Int) = m + n
(adder _).curried

在这里插入图片描述

7、可变长度参数

这是一个特殊的语法,可以向方法传入任意多个同类型的参数

例如要在多个字符串上执行 String 的 capitalize 函数。(首字母大写)

def capitalizeAll(args: String*) = {
|     args.map { arg =>
|     arg.capitalize
|    }
| }

在这里插入图片描述

8、类

以下例子展示了如何在类中用 def 定义方法和用 val 定义字段值

方法就是可以访问类的状态的函数。

scala> class Calculator {
     |    val brand: String = "HP"
     |       def add(m: Int, n: Int): Int = m + n
     | }
defined class Calculator

scala> val calc = new Calculator
calc: Calculator = Calculator@1f410c8

scala> calc.add(2, 3)
res13: Int = 5

scala> calc.brand
res14: String = HP

在这里插入图片描述

9、构造函数

构造函数不是特殊的方法,他们是除了类的方法定义之外的代码。

以下为扩展计算器的例子,增加一个构造函数参数,并用它来初始化内部状态

scala> class Calculator(brand: String) {
     |    val color: String = if (brand == "TI"){
     |       "blue"
     |     } else if (brand == "HP"){
     |       "black"
     |     } else {
     |       "white"
     |     }
     |     def add(m: Int, n: Int): Int = m + n
     | }
defined class Calculator
scala> val cal = new Calculator("HP")
cal: Calculator = Calculator@231cdda8

scala> cal.color
res0: String = black

scala> cal.add(2, 3)
res1: Int = 5

在这里插入图片描述
上述的 Calculator 例子说明了 Scala 是如何面向表达式的。
颜色的值就是绑定在一个if/else表达式上的。
Scala 是高度面向表达式的:大多数东西都是表达式而非指令。

10、函数 vs 方法

函数和方法在很大程度上是可以互换的。由于函数和方法是如此的相似,你可能都不知道你调用的东西是一个函数还是一个方法。

scala> class C {
     |     var acc = 0
     |     def minc = { acc += 1 }
     |     val finc = { () => acc += 1 }
     | }
defined class C

scala> val c = new C
c: C = C@14ca88bc

scala> c.minc

scala> c.finc
res3: () => Unit = C$$Lambda$802/1208288923@56554365

在这里插入图片描述

11、继承

class ScientificCalculator(brand: String) extends Calculator(brand) {
  def log(m: Double, base: Double) = math.log(m) / math.log(base)
}

12、重载方法

class EvenMoreScientificCalculator(brand: String) extends ScientificCalculator(brand) {
  def log(m: Int): Double = log(m, math.exp(1))
}

13、抽象类

一个抽象类,它定义了一些方法但没有实现它们。取而代之是由扩展抽象类的子类定义这些方法。

不能创建抽象类的实例。

scala> abstract class shape {
     |    def getArea(): Int      //子类应定义为这个
     | }
defined class shape

scala> class Circle(r: Int) extends shape {
     |    def getArea():Int = { r * r * 3 }
     | }
defined class Circle

scala> val c = new Circle(3)
c: Circle = Circle@104a287c

scala> c.getArea()
res0: Int = 27

scala> val s = new shape
               ^
       error: class shape is abstract; cannot be instantiated

在这里插入图片描述

14、特质

特质是一些字段和行为的集合,可以扩展或混入(mixin)你的类中。

scala> trait Car {
     |   val brand: String
     | }
defined trait Car

scala> trait Shiny {
     |    val shineRefraction: Int
     | }
defined trait Shiny

scala> class BMW extends Car {
     |    val brand = "BMW"
     | }
defined class BMW

scala> val b = new BMW
b: BMW = BMW@78e50fca

scala> b.brand
res1: String = BMW

在这里插入图片描述

通过 with 关键字,一个类可以扩展多个特质

scala> class BMW extends Car with Shiny {
     |    val brand = "BMW"
     |    val shineRefraction = 12
     | }
defined class BMW

scala> val bmw = new BMW
bmw: BMW = BMW@9ba167e

scala> bmw.brand
res2: String = BMW

scala> bmw.shineRefraction
res3: Int = 12

在这里插入图片描述

什么时候应该使用特质而不是抽象类?
如果你想定义一个类似接口的类型,你可能会在特质和抽象类之间难以取舍。这两种形式都可以让你定义一个类型的一些行为,并要求继承者定义一些其他行为。

一些经验法则:

  • 优先使用特质。一个类扩展多个特质是很方便的,但却只能扩展一个抽象类。
  • 如果你需要构造函数参数,使用抽象类。因为抽象类可以定义带参数的构造函数,而特质不行。例如,你不能说trait t(i: Int) {},参数i是非法的。

15、类型

我们定义了一个函数的参数为 Int,表示输入是一个数字类型。其实函数也可以是泛型的,来适用于所有类型。当这种情况发生时,你可以用方括号语法引入的类型参数。

基础引深

1、apply方法

当类或对象有一个主要用途的时候,apply 方法为你提供了一个很好的语法糖。

以下的实例化对象看起来像是在调用一个方法。

scala> class Foo {}
defined class Foo

scala> object FooMaker {
     |    def apply() = new Foo
     | }
defined object FooMaker

scala> val newFoo = FooMaker()
newFoo: Foo = Foo@1192c925

在这里插入图片描述

或者:

scala> class Bar {
     |   def apply() = 0
     | }
defined class Bar

scala> val bar = new Bar
bar: Bar = Bar@15d79b70

scala> bar()
res2: Int = 0

在这里插入图片描述

2、单例对象

单例对象用于持有一个类的唯一实例。通常用于工厂模式。

可像以下例子那样使用

scala> object Timer {
     |    var count = 0
     |    def currentCount(): Long = {
     |      count += 1
     |      count
     |    }
     | }
defined object Timer

scala> Timer.currentCount()
res3: Long = 1

在这里插入图片描述

单例对象可以和类具有相同的名称,此时该对象也被称为“伴生对象”。我们通常将伴生对象作为工厂使用。

下面是一个简单的例子,可以不需要使用new来创建一个实例了。

class Bar(foo: String)

object Bar {
  def apply(foo: String) = new Bar(foo)
}

Scala伴生对象

如果有同样一个类与该object名字一样,则称该object为该类的伴生对象,相对应,该类为object的伴生类。如果一个类有它的伴生对象,这个类就可通过object的名字访问到所有成员,但object不在该类的作用范围。

object对象为静态常量、静态变量区域,可以直接调用,共享全局变量很有意义,伴生对象方便类的构建,可做为当前类的静态方法、成员的集合。

  • 伴生对象与伴生类在Scala的面向对象编程方法中占据极其重要的位置,例如Scala中许多工具方法都是由伴 生对象提供的。
  • 伴生对象与伴生类

伴生对象首先是一个单例对象,单例对象用关键字object定义。
在Scala中,单例对象分为两种,一种是并未自动关联到特定类上的单例对象,称为独立对象(Standalone Object);另一种是关联到一个类上的单例对象,该单例对象与该类共有相同名字,则这种单例对象称为伴生对象(Companion Object),对应类称为伴生类。


3、函数即对象

函数是一些特质的集合。

具体来说,具有一个参数的函数是 Function1 特质的一个实例。这个特征定义了 apply()语法糖,让你调用一个对象时就像你在调用一个函数。

这个 Function 特质集合下标从 0 开始一直到 22。

为什么是 22?这是一个主观的魔幻数字。我从来没有使用过多于 22 个参数的函数,所以这个数字似乎是合理的。

scala> object addOne extends Function1[Int, Int] {
     |    def apply(m: Int): Int = m + 1
     | }
defined object addOne

scala> addOne(1)
res6: Int = 2

scala> addOne(3)
res7: Int = 4

在这里插入图片描述

在类中定义的方法是方法而不是函数。

类也可以扩展 Function,这些类的实例可以使用()调用

scala> class AddOne extends Function1[Int, Int] {
     |     def apply(m: Int): Int = m + 1
     | }
defined class AddOne

scala> val plusOne = new AddOne()
plusOne: AddOne = <function1>

scala> plusOne(2)
res8: Int = 3

在这里插入图片描述

可以使用更直观快捷的 extends (Int => Int) 代替 extends Function1[Int, Int]

class AddOne extends (Int => Int) {
     |     def apply(m: Int): Int = m + 1
     | }

4、包
(运行不成功,报错:error: illegal start of definition)

package com.twitter.example

在文件头部定义包,会将文件中所有的代码声明在那个包中。

值和函数不能在类或单例对象之外定义。

package com.twitter.example

scala> object colorHolder {
     |   val BLUE = "Blue"
     |   val RED = "Red"
     | }
defined object colorHolder
println("the color is: " + com.twitter.example.colorHolder.BLUE)

5、模式匹配

这是 Scala 中最有用的部分之一。

在最后一行指令中的 _ 是一个通配符;它保证了我们可以处理所有的情况。

匹配值

scala> val times = 1
times: Int = 1

scala> times match {
     |   case 1 => "one"
     |   case 2 => "two"
     |   case _ => "some other number"
     | }
res11: String = one

在这里插入图片描述

使用守卫匹配,如下图:

scala> val times = 1
times: Int = 1

scala> times match {
     |    case i if i == 1 => "one"
     |    case i if i == 2 => "two"
     |    case _ => "some other number"
     | }
res12: String = one

运行结果如下图所示:
在这里插入图片描述

scala> val times = 2
times: Int = 2

scala> times match {
     |    case i if i == 1 => "one"
     |    case i if i == 2 => "two"
     |    case _ => "some"
     | }
res13: String = two

运行结果如下图:
在这里插入图片描述

6、匹配类型

可以使用 match 来分别处理不同类型的值。

scala> def bigger(o: Any): Any = {
     |   o match {
     |      case i: Int if i < 0 => i - 1
     |      case i: Int => i + 1
     |      case d: Double if d <0.0 => d - 0.1
     |      case d: Double => d + 0.1
     |      case text: String => text + "s"
     |    }
     | }
bigger: (o: Any)Any

scala> bigger(4)
res15: Any = 5

scala> bigger(4.0)
res16: Any = 4.1

scala> bigger("s")
res19: Any = ss

运行结果如下图:
在这里插入图片描述

在这里插入图片描述

集合

1、列表 List

scala> val numbers = List(1, 2, 3, 4)
numbers: List[Int] = List(1, 2, 3, 4)

2、集 Set

集没有重复

scala> Set(1, 1, 2, 3, 3)
res0: scala.collection.immutable.Set[Int] = Set(1, 2, 3)

3、元组 Tuple

①元组是在不使用类的前提下,将元素组合起来形成简单的逻辑集合。

scala> val hostPort = ("localhost", 90)
hostPort: (String, Int) = (localhost,90)

在这里插入图片描述

②与样本类不同,元组不能通过名称获取字段,而是使用位置下标来读取对象;而且这个下标基于 1,而不是基于 0。

scala> hostPort._1
res1: String = localhost

scala> hostPort._2
res2: Int = 90

在这里插入图片描述

③元组可以很好的与模式匹配相结合。

④在创建两个元素的元组时,可以使用特殊语法:->

scala> 1 -> 2
res3: (Int, Int) = (1,2)

在这里插入图片描述

4、映射 Map

它可以持有基本数据类型。

scala> Map(1 -> 2)
res4: scala.collection.immutable.Map[Int,Int] = Map(1 -> 2)

scala> Map("dog" -> "cat")
res5: scala.collection.immutable.Map[String,String] = Map(dog -> cat)

在这里插入图片描述

Map()方法也使用了从第一节课学到的变参列表:Map(1 -> "one", 2 -> "two")将变为 Map((1, "one"), (2, "two")),其中第一个参数是映射的键,第二个参数是映射的值。

映射的值可以是映射甚或是函数。

scala> Map(1 -> Map("dog" -> "cat"))
res6: scala.collection.immutable.Map[Int,scala.collection.immutable.Map[String,String]] = Map(1 -> Map(dog -> cat))
Map("timesTwo" -> { timesTwo(_) })

5、选项 Option

Option 是一个表示有可能包含值的容器。

Option基本的接口是这样的:

scala> trait Option[T] {
     |    def isDefined: Boolean
     |    def get: T
     |    def getOrElse(t: T): T
     | }
defined trait Option

在这里插入图片描述

Option 本身是泛型的,并且有两个子类: Some[T] 或 None

我们看一个使用 Option 的例子:

Map.get 使用 Option 作为其返回值,表示这个方法也许不会返回你请求的值。

scala> val numbers = Map("one" -> 1, "two" -> 2)
numbers: scala.collection.immutable.Map[String,Int] = Map(one -> 1, two -> 2)

scala> numbers.get("two")
res10: Option[Int] = Some(2)

scala> numbers.get("three")
res11: Option[Int] = None

scala> numbers.get("one")
res12: Option[Int] = Some(1)

在这里插入图片描述

现在我们的数据似乎陷在 Option 中了,我们怎样获取这个数据呢?

直觉上想到的可能是在 isDefined 方法上使用条件判断来处理。

注意:以下涉及的res10是根据个人代码结果来定的,每个人会不一样

// We want to multiply the number by two, otherwise return 0.
scala> val result = if (res10.isDefined) {
     |    res10.get * 2
     | } else {
     |    0
     | }
result: Int = 4

我们建议使用 getOrElse 或模式匹配处理这个结果。

getOrElse 让你轻松地定义一个默认值。

scala> val result = res10.getOrElse(0) * 2
result: Int = 4

在这里插入图片描述

模式匹配能自然地配合 Option 使用。

scala> val result = res10 match {
     |    case Some(n) => n * 2
     |    case None => 0
     | }
result: Int = 4

在这里插入图片描述

6、函数组合子

List(1, 2, 3) map squared 对列表中的每一个元素都应用了squared 平方函数,并返回一个新的列表 List(1, 4, 9)。我们称这个操作 map 组合子。

scala> val numbers = List(1, 2, 3, 4)
numbers: List[Int] = List(1, 2, 3, 4)

①map

map 对列表中的每个元素应用一个函数,返回应用后的元素所组成的列表。

scala> numbers.map((i: Int) => i * 2)
res14: List[Int] = List(2, 4, 6, 8)

或传入一个部分应用函数

scala> def timesTwo(i: Int): Int = i * 2
timesTwo: (i: Int)Int

scala> numbers.map(timesTwo _)
res15: List[Int] = List(2, 4, 6, 8)

在这里插入图片描述

②foreach

foreach 很像 map,但没有返回值。foreach 仅用于有副作用[side-effects]的函数。

scala> numbers.foreach((i: Int) => i * 2)

什么也没有返回。

你可以尝试存储返回值,但它会是 Unit 类型(即void)

scala> val doubled = numbers.foreach((i: Int) => i * 2)
doubled: Unit = ()

在这里插入图片描述

③filter

filter 移除任何对传入函数计算结果为 false 的元素。返回一个布尔值的函数通常被称为谓词函数[或判定函数]

scala> numbers.filter((i: Int) => i % 2 == 0)
res17: List[Int] = List(2, 4)

scala> def isEven(i: Int): Boolean = i % 2 == 0
isEven: (i: Int)Boolean

scala> numbers.filter(isEven _)
res18: List[Int] = List(2, 4)

在这里插入图片描述

④zip

zip 将两个列表的内容聚合到一个对偶列表中。

scala> List(1, 2, 3).zip(List("a", "b", "c"))
res19: List[(Int, String)] = List((1,a), (2,b), (3,c))

⑤partition

partition 将使用给定的谓词函数分割列表

scala> val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
numbers: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

scala> numbers.partition(_ % 2 == 0)
res20: (List[Int], List[Int]) = (List(2, 4, 6, 8, 10),List(1, 3, 5, 7, 9))

在这里插入图片描述

⑥find

find 返回集合中第一个匹配谓词函数的元素。

这里的numbers是紧接上面position中的numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

scala> numbers.find((i: Int) => i > 5)
res21: Option[Int] = Some(6)

⑦drop & dropWhile

drop 将删除前 i 个元素,以下例子为删除前5个元素

scala> numbers.drop(5)
res22: List[Int] = List(6, 7, 8, 9, 10)

dropWhile 将删除元素直到找到第一个匹配谓词函数的元素

例如,如果我们在 numbers 列表上使用 dropWhile 奇数的函数, 1 将被丢弃(但 3 不会被丢弃,因为他被 2 “保护”了)。

scala> numbers.dropWhile(_ % 2 != 0)
res23: List[Int] = List(2, 3, 4, 5, 6, 7, 8, 9, 10)

在这里插入图片描述

⑧foldLeft

foldLeft其实就是给定一个初始值和一个函数,输入的函数也需要输入两个参数:累加值和当前item的索引。

初始值0作为第一个参数传进到fold函数中,list中的第一个item作为第二个参数传进fold函数中。

直到list中的所有元素都被遍历之后,返回最后的计算值,整个过程结束;

scala> numbers.foldLeft(0)((m: Int, n: Int) => m + n)
res24: Int = 55

0 为初始值(记住 numbers 是 List[Int] 类型),m 作为一个累加器

直接观察运行过程如下:

scala> numbers.foldLeft(0){ (m: Int, n: Int) => println("m:" + m + "n:" + n); m + n}
m:0n:1
m:1n:2
m:3n:3
m:6n:4
m:10n:5
m:15n:6
m:21n:7
m:28n:8
m:36n:9
m:45n:10
res25: Int = 55

在这里插入图片描述

⑨foldRight

和 foldLeft 一样,只是运行过程相反。

scala> numbers.foldRight(0){ (m: Int,n: Int) => println("m:" + m + "n:" + n ); m + n }
m:10n:0
m:9n:10
m:8n:19
m:7n:27
m:6n:34
m:5n:40
m:4n:45
m:3n:49
m:2n:52
m:1n:54
res26: Int = 55

在这里插入图片描述

⑩flatten

flatten 将嵌套结构扁平化为一个层次的集合。

scala> List(List(1, 2), List(3, 4)).flatten
res27: List[Int] = List(1, 2, 3, 4)

⑩① flatMap

flatMap 是一种常用的组合子,结合映射 [mapping] 和扁平化 [flattening]。

flatMap 需要一个处理嵌套列表的函数,然后将结果串连起来。

scala> val nestedNumbers = List(List(1, 2), List(3, 4))
nestedNumbers: List[List[Int]] = List(List(1, 2), List(3, 4))

scala> nestedNumbers.flatMap(x => x.map(_ * 2))
res28: List[Int] = List(2, 4, 6, 8)

可以把它看做是“先映射后扁平化”的快捷操作:

scala> nestedNumbers.map((x: List[Int]) => x.map(_ * 2)).flatten
res29: List[Int] = List(2, 4, 6, 8)

在这里插入图片描述
这个例子先调用 map,然后可以马上调用 flatten,这就是“组合子”的特征,也是这些函数的本质。

7、扩展函数组合子

上面所展示的每一个函数组合子都可以用 fold 方法实现。例如:

scala> def ourMap(numbers: List[Int], fn: Int => Int): List[Int] = {
     |    numbers.foldRight(List[Int]()) { (x: Int, xs: List[Int]) =>
     |      fn(x) :: xs
     |    }
     | }
ourMap: (numbers: List[Int], fn: Int => Int)List[Int]

scala> ourMap(numbers, timesTwo(_))
res30: List[Int] = List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)

在这里插入图片描述

为什么是List[Int]()?Scala没有聪明到理解你的目的是将结果积聚在一个空的 Int 类型的列表中。

Map

所有展示的函数组合子都可以在 Map 上使用。

Map 可以被看作是一个二元组的列表,所以你写的函数要处理一个键和值的二元组。

scala> val extensions = Map("steve" -> 100, "bob" -> 101, "joe" -> 201)
extensions: scala.collection.immutable.Map[String,Int] = Map(steve -> 100, bob -> 101, joe -> 201)

现在筛选出电话分机号码低于 200 的条目。

scala> extensions.filter((namePhone:(String, Int)) => namePhone._2 < 200)
res31: scala.collection.immutable.Map[String,Int] = Map(steve -> 100, bob -> 101)

因为参数是元组,所以你必须使用位置获取器来读取它们的键和值。

幸运的是,我们其实可以使用模式匹配更优雅地提取键和值。

scala> extensions.filter({case (name, extension) => extension < 200})
res32: scala.collection.immutable.Map[String,Int] = Map(steve -> 100, bob -> 101)

在这里插入图片描述

这里我的结果好像还是没有变化,人家的结果如下:

scala> extensions.filter({case (name, extension) => extension < 200})
res0: scala.collection.immutable.Map[String,Int] = Map((steve,100), (bob,101))
更多的集合

Scala 提供了一套很好的集合实现,提供了一些集合类型的抽象。这让你的代码可以与 Foo 的集合交互,而无需担心该集合是是一个 List,还是 Set,或是任何你有的类型。

1、表 List

标准的链表

scala> List(1, 2, 3)
res0: List[Int] = List(1, 2, 3)

也可以用函数语言的方式连接它们

scala> 1 :: 2 :: 3 :: Nil
res1: List[Int] = List(1, 2, 3)

在这里插入图片描述

2、集 Set

集没有重复

Set(1, 1, 2)

3、序列 Seq

序列有一个给定的顺序。

请注意返回的是一个列表。因为 Seq 是一个特质;而列表是序列的很好实现。

如你所见,Seq 也是一个工厂单例对象,可以用来创建列表。

scala> Seq(1, 1, 2)
res2: Seq[Int] = List(1, 1, 2)

4、映射 Map

映射是键值容器。

Map('a' -> 1, 'b' -> 2)

5、层次结构与方法

下面介绍的都是特质,它们在可变的(mutable)和不可变的(immutable)的包中都有特定实现。

①Traversable

所有集合都可以被遍历。这个特质定义了标准函数组合子。 这些组合子根据 foreach 来写,所有集合必须实现。

下面所有方法在子类中都是可用的。

参数和返回值的类型可能会因为子类的覆盖而看起来不同。

def head : A
def tail : Traversable[A]

这是函数组合子定义的地方。

def map [B] (f: (A) => B) : CC[B]

返回每个元素都被 f 转化的集合

def foreach[U](f: Elem => U): Unit

在集合中的每个元素上执行 f 。

def find (p: (A) => Boolean) : Option[A]

返回匹配谓词函数的第一个元素

def filter (p: (A) => Boolean) : Traversable[A]

返回所有匹配谓词函数的元素集合

划分:

def partition (p: (A)Boolean) : (Traversable[A], Traversable[A])

按照谓词函数把一个集合分割成两部分

def groupBy [K] (f: (A) => K) : Map[K, Traversable[A]]

转换:

可以转换集合类型。

def toArray : Array[A]
def toArray [B >: A] (implicit arg0: ClassManifest[B]) : Array[B]
def toBuffer [B >: A] : Buffer[B]
def toIndexedSeq [B >: A] : IndexedSeq[B]
def toIterable : Iterable[A]
def toIterator : Iterator[A]
def toList : List[A]
def toMap [T, U] (implicit ev: <:<[A, (T, U)]) : Map[T, U]
def toSeq : Seq[A]
def toSet [B >: A] : Set[B]
def toStream : Stream[A]
def toString () : String
def toTraversable : Traversable[A]

把映射转换为一个数组,您会得到一个键值对的数组。

scala> Map(1 -> 2).toArray
res3: Array[(Int, Int)] = Array((1,2))

在这里插入图片描述

②Iterable

iterator() 方法返回一个 Iterator 来迭代元素。

添加一个迭代器的访问。

def iterator: Iterator[A]

一个迭代器能给你提供什么?

def hasNext(): Boolean
def next(): A

这是非常 Java 式的。

你通常不会看到在 Scala 中使用迭代器,通常更容易出现的是函数组合器或 for 循环的使用。

③Seq 序列

有顺序的对象序列。

④Set

没有重复的对象集合。(这里还没太明白)

def contains(key: A): Boolean
def +(elem: A): Set[A]
def -(elem: A): Set[A]

⑤Map

键值对。通过键查找的键值对的序列。

可以像这样将一个键值对列表传入 apply()

scala> Map("a" -> 1, "b" -> 2)
res4: scala.collection.immutable.Map[String,Int] = Map(a -> 1, b -> 2)

或者:

scala> Map(("a", 2), ("b", 2))
res5: scala.collection.immutable.Map[String,Int] = Map(a -> 2, b -> 2)

在这里插入图片描述

什么是->?这不是特殊的语法,这是一个返回元组的方法。

scala> "a".->(2)
res6: (String, Int) = (a,2)

请记住,这仅仅是下面代码的语法糖

scala> "a" ->(2)
res7: (String, Int) = (a,2)

您也可以使用++操作符构建

scala> Map.empty ++ List(("a", 1),("b", 2),("c", 3))
res8: scala.collection.immutable.Map[String,Int] = Map(a -> 1, b -> 2, c -> 3)

在这里插入图片描述

6、常用的子类

HashSet 和 HashMap 的快速查找,这些集合的最常用的形式。 HashSet API, HashMap API

TreeMap 是 SortedMap 的一个子类,它可以让你进行有序访问。

Vector 快速随机选择和快速更新。 Vector API

scala> IndexedSeq(1, 2, 3)
res9: IndexedSeq[Int] = Vector(1, 2, 3)

Range 等间隔的 Int 有序序列。你经常会在 for 循环看到。 Range API

scala> for (i <- 1 to 3) { println(i) }
1
2
3

Ranges 支持标准的函数组合子。

scala> (1 to 3).map { i => i }
res11: IndexedSeq[Int] = Vector(1, 2, 3)

在这里插入图片描述

7、默认实现

使用特质的 apply 方法会给你默认实现的实例。

例如,Iterable(1, 2)会返回一个列表作为其默认实现。

scala> Iterable(1, 2)
res12: Iterable[Int] = List(1, 2)

序列 Seq 也是一样的,正如我们前面所看到的

scala> Seq(1, 2)
res13: Seq[Int] = List(1, 2)

scala> Iterable(1, 2)
res14: Iterable[Int] = List(1, 2)

Set

scala> Set(1, 2)
res17: scala.collection.immutable.Set[Int] = Set(1, 2)

在这里插入图片描述

8、可变 vs 不可变

不可变

优点

  • 在多线程中不会改变

缺点

  • 一点也不能改变

Scala 允许我们是务实的,它鼓励不变性,但不惩罚我们需要的可变性。

这和 var vs. val 非常相似。我们总是先从 val 开始并在必要时回退为 var。

我们赞成使用不可改变的版本的集合,但如果性能使然,也可以切换到可变的。使用不可变集合意味着你在多线程不会意外地改变事物。

可变集合

前面讨论的所有类都是不可变的。接下来是常用的可变集合。

HashMap 定义了 getOrElseUpdate, += HashMap API

scala> val numbers = collection.mutable.Map(1 -> 2)
numbers: scala.collection.mutable.Map[Int,Int] = HashMap(1 -> 2)

scala> numbers.get(1)
res18: Option[Int] = Some(2)

scala> numbers.getOrElseUpdate(2, 3)
res19: Int = 3

scala> numbers
res20: scala.collection.mutable.Map[Int,Int] = HashMap(1 -> 2, 2 -> 3)

scala> numbers += (4 -> 1)
res21: numbers.type = HashMap(1 -> 2, 2 -> 3, 4 -> 1)

在这里插入图片描述

9、与java生活(没有运行过)

可以通过 JavaConverters package 轻松地在 Java 和 Scala 的集合类型之间转换。

它用 asScala 装饰常用的 Java 集合以和用 asJava 方法装饰 Scala 集合。

import scala.collection.JavaConverters._
val sl = new scala.collection.mutable.ListBuffer[Int]
val jl : java.util.List[Int] = sl.asJava
val sl2 : scala.collection.mutable.Buffer[Int] = jl.asScala
assert(sl eq sl2)

双向转换:

scala.collection.Iterable <=> java.lang.Iterable
scala.collection.Iterable <=> java.util.Collection
scala.collection.Iterator <=> java.util.{ Iterator, Enumeration }
scala.collection.mutable.Buffer <=> java.util.List
scala.collection.mutable.Set <=> java.util.Set
scala.collection.mutable.Map <=> java.util.{ Map, Dictionary }
scala.collection.mutable.ConcurrentMap <=> java.util.concurrent.ConcurrentMap

也提供了以下单向转换

scala.collection.Seq => java.util.List
scala.collection.mutable.Seq => java.util.List
scala.collection.Set => java.util.Set
scala.collection.Map => java.util.Map
Scala 访问修饰符

Scala 访问修饰符基本和Java的一样,分别有:private,protected,public。

如果没有指定访问修饰符,默认情况下,Scala 对象的访问级别都是 public。

Scala 中的 private 限定符,比 Java 更严格,在嵌套类情况下,外层类甚至不能访问被嵌套类的私有成员。

私有(Private)成员

用 private 关键字修饰,带有此标记的成员仅在包含了成员定义的类或对象内部可见,同样的规则还适用内部类。

Scala运算符

一个运算符是一个符号,用于告诉编译器来执行指定的数学运算和逻辑运算。

Scala 含有丰富的内置运算符,包括以下几种类型:

  • 算术运算符
  • 关系运算符
  • 逻辑运算符
  • 位运算符
  • 赋值运算符
算术运算符

假定变量 A 为 10,B 为 20:

运算符描述实例
+加号A + B 运算结果为 30
-减号A - B 运算结果为 -10
*乘号A * B 运算结果为 200
/除号B / A 运算结果为 2
%取余B % A 运算结果为 0

实例代码Test.scala如下:

object Test {
   def main(args: Array[String]) {
      var a = 10;
      var b = 20;
      var c = 25;
      var d = 25;
      println("a + b = " + (a + b) );
      println("a - b = " + (a - b) );
      println("a * b = " + (a * b) );
      println("b / a = " + (b / a) );
      println("b % a = " + (b % a) );
      println("c % a = " + (c % a) );
      
   }
}

编译运行

D:\StudyInSchool\大数据分析技术\Scala\Scala-work>scalac Test.scala
warning: there was one deprecation warning (since 2.13.0); re-run with -deprecation for details
one warning found

D:\StudyInSchool\大数据分析技术\Scala\Scala-work>scala Test
a + b = 30
a - b = -10
a * b = 200
b / a = 2
b % a = 0
c % a = 5

在这里插入图片描述

关系运算符

假定变量 A 为 10,B 为 20:

运算符描述实例
==等于(A == B) 运算结果为 false
!=不等于(A != B) 运算结果为 true
>大于(A > B) 运算结果为 false
<小于(A < B) 运算结果为 true
>=大于等于(A >= B) 运算结果为 false
<=小于等于(A <= B) 运算结果为 true

实例代码Test1.scala

object Test1 {
   def main(args: Array[String]):Unit = {
      var a = 10;
      var b = 20;
      println("a == b = " + (a == b) );
      println("a != b = " + (a != b) );
      println("a > b = " + (a > b) );
      println("a < b = " + (a < b) );
      println("b >= a = " + (b >= a) );
      println("b <= a = " + (b <= a) );
   }
}

编译运行

D:\StudyInSchool\大数据分析技术\Scala\Scala-work>scalac Test1.scala

D:\StudyInSchool\大数据分析技术\Scala\Scala-work>scala Test1
a == b = false
a != b = true
a > b = false
a < b = true
b >= a = true
b <= a = false

在这里插入图片描述

逻辑运算符

假定变量 A 为 1,B 为 0:

运算符描述实例
&&逻辑与(A && B) 运算结果为 false
||逻辑或(A || B) 运算结果为 true
!逻辑非!(A && B) 运算结果为 true

实例代码Test2.scala如下:

object Test2 {
    def main(args: Array[String]):Unit ={
        var a = true;
        var b = false;

        println("a && b = " + (a&&b));
        println("a || b = " + (a||b));
        println("!(a && b) = " + !(a && b));
    }
}

运行编译

D:\StudyInSchool\大数据分析技术\Scala\Scala-work>scalac Test2.scala

D:\StudyInSchool\大数据分析技术\Scala\Scala-work>scala Test2
a && b = false
a || b = true
!(a && b) = true

在这里插入图片描述

位运算符

符用来对二进制位进行操作,~,&,|,^分别为取反,按位与与,按位与或,按位与异或运算,如下表实例:

pqp & qp | qp ^ q
00000
01011
11110
10011

如果指定 A = 60; 及 B = 13; 两个变量对应的二进制为:

A = 0011 1100

B = 0000 1101

-------位运算----------

A&B = 0000 1100

A|B = 0011 1101

A^B = 0011 0001

~A  = 1100 0011

Scala 中的按位运算法则如下:

运算符描述实例
&按位与运算符(a & b) 输出结果 12 ,二进制解释: 0000 1100
|按位或运算符(a | b) 输出结果 61 ,二进制解释: 0011 1101
^按位异或运算符(a ^ b) 输出结果 49 ,二进制解释: 0011 0001
~按位取反运算符(~a ) 输出结果 -61 ,二进制解释: 1100 0011, 在一个有符号二进制数的补码形式。
<<左移动运算符a << 2 输出结果 240 ,二进制解释: 1111 0000
>>右移动运算符a >> 2 输出结果 15 ,二进制解释: 0000 1111
>>>无符号右移A >>>2 输出结果 15, 二进制解释: 0000 1111

实例代码Test3.scala

object Test3 {
   def main(args: Array[String]):Unit = {
      var a = 60;           /* 60 = 0011 1100 */  
      var b = 13;           /* 13 = 0000 1101 */
      var c = 0;

      c = a & b;            /* 12 = 0000 1100 */ 
      println("a & b = " + c );

      c = a | b;            /* 61 = 0011 1101 */
      println("a | b = " + c );

      c = a ^ b;            /* 49 = 0011 0001 */
      println("a ^ b = " + c );

      c = ~a;               /* -61 = 1100 0011 */
      println("~a = " + c );

      c = a << 2;           /* 240 = 1111 0000 */
      println("a << 2 = " + c );

      c = a >> 2;           /* 15 = 1111 */
      println("a >> 2  = " + c );

      c = a >>> 2;          /* 15 = 0000 1111 */
      println("a >>> 2 = " + c );
   }
} 

运行编译

D:\StudyInSchool\大数据分析技术\Scala\Scala-work>scalac Test3.scala

D:\StudyInSchool\大数据分析技术\Scala\Scala-work>scala Test3
a & b = 12
a | b = 61
a ^ b = 49
~a = -61
a << 2 = 240
a >> 2  = 15
a >>> 2 = 15

在这里插入图片描述

赋值运算符
运算符描述实例
=简单的赋值运算,指定右边操作数赋值给左边的操作数。C = A + B 将 A + B 的运算结果赋值给 C
+=相加后再赋值,将左右两边的操作数相加后再赋值给左边的操作数。C += A 相当于 C = C + A
-=相减后再赋值,将左右两边的操作数相减后再赋值给左边的操作数。C -= A 相当于 C = C - A
*=相乘后再赋值,将左右两边的操作数相乘后再赋值给左边的操作数。C *= A 相当于 C = C * A
/=相除后再赋值,将左右两边的操作数相除后再赋值给左边的操作数。C /= A 相当于 C = C / A
%=求余后再赋值,将左右两边的操作数求余后再赋值给左边的操作数。C %= A is equivalent to C = C % A
<<=按位左移后再赋值C <<= 2 相当于 C = C << 2
>>=按位右移后再赋值C >>= 2 相当于 C = C >> 2
&=按位与运算后赋值C &= 2 相当于 C = C & 2
^=按位异或运算符后再赋值C ^= 2 相当于 C = C ^ 2
|=按位或运算后再赋值C |= 2 相当于 C = C | 2

实例代码Test4.scala

object Test4 {
   def main(args: Array[String]):Unit = {
      var a = 10;    
      var b = 20;
      var c = 0;

      c = a + b;
      println("c = a + b  = " + c );

      c += a ;
      println("c += a  = " + c );

      c -= a ;
      println("c -= a = " + c );

      c *= a ;
      println("c *= a = " + c );

      a = 10;
      c = 15;
      c /= a ;
      println("c /= a  = " + c );

      a = 10;
      c = 15;
      c %= a ;
      println("c %= a  = " + c );

      c <<= 2 ;
      println("c <<= 2  = " + c );

      c >>= 2 ;
      println("c >>= 2  = " + c );

      c >>= a ;
      println("c >>= a  = " + c );

      c &= a ;
      println("c &= 2  = " + c );
     
      c ^= a ;
      println("c ^= a  = " + c );

      c |= a ;
      println("c |= a  = " + c );
   }
} 

运行编译

D:\StudyInSchool\大数据分析技术\Scala\Scala-work>scalac Test4.scala

D:\StudyInSchool\大数据分析技术\Scala\Scala-work>scala Test4
c = a + b  = 30
c += a  = 40
c -= a = 30
c *= a = 300
c /= a  = 1
c %= a  = 5
c <<= 2  = 20
c >>= 2  = 5
c >>= a  = 0
c &= 2  = 0
c ^= a  = 10
c |= a  = 10

在这里插入图片描述

Scala IF…ELSE 语句

Scala IF…ELSE 语句是通过一条或多条语句的执行结果(True或者False)来决定执行的代码块。

if语句

if 语句有布尔表达式及之后的语句块组成。

if 语句的语法格式如下:

if(布尔表达式)
{
   // 如果布尔表达式为 true 则执行该语句块
}

如果布尔表达式为 true 则执行大括号内的语句块,否则跳过大括号内的语句块,执行大括号之后的语句块。

if…else 语句

if 语句后可以紧跟 else 语句,else 内的语句块可以在布尔表达式为 false 的时候执行。

if…else 的语法格式如下:

if(布尔表达式){
   // 如果布尔表达式为 true 则执行该语句块
}else{
   // 如果布尔表达式为 false 则执行该语句块
}
if…else if…else 语句

if 语句后可以紧跟 else if…else 语句,在多个条件判断语句的情况下很有用。

if…else if…else 语法格式如下:

if(布尔表达式 1){
   // 如果布尔表达式 1 为 true 则执行该语句块
}else if(布尔表达式 2){
   // 如果布尔表达式 2 为 true 则执行该语句块
}else if(布尔表达式 3){
   // 如果布尔表达式 3 为 true 则执行该语句块
}else {
   // 如果以上条件都为 false 执行该语句块
}
if…else 嵌套语句

if…else 嵌套语句可以实现在 if 语句内嵌入一个或多个 if 语句。

if…else 嵌套语句语法格式如下:

if(布尔表达式 1){
   // 如果布尔表达式 1 为 true 则执行该语句块
   if(布尔表达式 2){
      // 如果布尔表达式 2 为 true 则执行该语句块
   }
}

else if…else 的嵌套语句 类似 if…else 嵌套语句。

模式匹配与函数组合

函数组合

先创建两个函数:

scala> def f(s: String) = "f(" + s + ")"
f: (s: String)String

scala> def g(s: String) = "g(" + s + ")"
g: (s: String)String

在这里插入图片描述

1、compose

compose 组合其他函数形成一个新的函数 f(g(x))

scala> val fComposeG = f _ compose g _
fComposeG: String => String = scala.Function1$$Lambda$760/1908116276@197ce367

scala> fComposeG("yay")
res1: String = f(g(yay))

在这里插入图片描述

2、andThen

andThen 和 compose很像,但是调用顺序是先调用第一个函数,然后调用第二个,即g(f(x))

scala> val fAndThenG = f _ andThen g _
fAndThenG: String => String = scala.Function1$$Lambda$769/83812683@11c3d22f

scala> fAndThenG("yay")
res2: String = g(f(yay))

在这里插入图片描述

3、PartialFunction(偏函数)

柯里化 vs 偏应用

case 语句

那么究竟什么是 case 语句?

这是一个名为 PartialFunction 的函数的子类。

多个 case 语句的集合是什么?他们是共同组合在一起的多个 PartialFunction。

理解 PartialFunction(偏函数)

对给定的输入参数类型,函数可接受该类型的任何值。换句话说,一个(Int) => String的函数可以接收任意 Int 值,并返回一个字符串。

对给定的输入参数类型,偏函数只能接受该类型的某些特定的值。一个定义为(Int) => String 的偏函数可能不能接受所有 Int 值为输入。

isDefinedAt 是 PartialFunction 的一个方法,用来确定 PartialFunction 是否能接受一个给定的参数

注意:偏函数 PartialFunction 和我们前面提到的部分应用函数是无关的。

scala> val one: PartialFunction[Int, String] = { case 1 => "one" }
one: PartialFunction[Int,String] = <function1>

scala> one.isDefinedAt(1)
res3: Boolean = true

scala> one.isDefinedAt(2)
res4: Boolean = false

可以调用一个偏函数。

scala> one(1)
res5: String = one

在这里插入图片描述

PartialFunctions 可以使用 orElse 组成新的函数,得到的 PartialFunction 反映了是否对给定参数进行了定义。

scala> val one: PartialFunction[Int, String] = { case 1 => "one" }
one: PartialFunction[Int,String] = <function1>

scala> val two: PartialFunction[Int, String] = { case 2 => "two" }
two: PartialFunction[Int,String] = <function1>

scala> val three: PartialFunction[Int, String] = { case 3 => "three" }
three: PartialFunction[Int,String] = <function1>

scala> val wildcard: PartialFunction[Int, String] = { case _ => "something else" }
wildcard: PartialFunction[Int,String] = <function1>

scala> val partial = one orElse two orElse three orElse wildcard
partial: PartialFunction[Int,String] = <function1>

scala> partial(5)
res6: String = something else

scala> partial(3)
res7: String = three

scala> partial(2)
res8: String = two

scala> partial(200)
res9: String = something else

scala> partial(0)
res10: String = something else

scala> partial(1)
res11: String = one

在这里插入图片描述

4、case 之谜

我们会在通常应该使用函数的地方看到了一个 case 语句。

scala> case class PhoneExt(name: String, ext: Int)
defined class PhoneExt

scala> val extensions = List(PhoneExt("steve", 100), PhoneExt("rebey", 200))
extensions: List[PhoneExt] = List(PhoneExt(steve,100), PhoneExt(rebey,200))

scala> extensions.filter { case PhoneExt(name, extension) => extension < 200 }
res12: List[PhoneExt] = List(PhoneExt(steve,100))

在这里插入图片描述

为什么这段代码可以工作?

filter 使用一个函数。在这个例子中是一个谓词函数(PhoneExt) => Boolean

PartialFunction 是 Function 的子类型,所以 filter 也可以使用 PartialFunction!

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值