一文讲通Scala!!!

1. 计算机语言

语言可以分为【编译型】、【解释型】:

  • 编译型:C
  • 解释型:Python

对于 Java :

  • 需要编译,代码需要有编译器编译成字节码
  • 解释执行/直接执行
  • 可移动性,代码一次编译,到处运行,【JVM才是核心】!!!

对于 C 语言:

  • 可移植性,代码对应于不同平台,需要各自编译

【编译器】:编译型、解释型的根本区别在哪?

  • 是否是强类型,什么是类型 —— 宽度

语法 =》编译器 =》字节码《= JVM规则

编译器衔接人和机器

Java 其实不值钱,最值钱的是 JVM。

JVM 由 C 语言编写,字节码(二进制) =》JVM(堆内/外(二进制)) =》 kernel(mmap, sendfile) 更快!!

【语言模型】:

  • 面向过程的:第一类值 —— 基本类型 + 指针
  • 面向对象的:第一类值 —— 基本类型 + 对象类型
  • 函数式的:第一类值 —— 基本类型 + 对象类型 + 函数

2. Scala

Scala combines object-oriented and functional programming in one concise, high-level language. Scala's static types help avoid bugs in complex applications, and its JVM and JavaScript runtimes let you build high-performance systems with easy access to huge ecosystems of libraries.

Scala 是一种高级语言,其将【面向对象】和【函数式编程】相结合。Scala 的静态类型有助于避免复杂应用程序中的 Bug。同时它的 JVM 和 JavaScript 运行时允许构建高性能系统,可以方便的访问庞大的库生态系统。

  • SCALA on JVM
JAVA SCALA
编译器 编译器
  • scala 代码和 java 代码不一样:编译器做了很多事情
  • java 中变量类型需要明文给出
  • scala 中可以对变量类型进行推断(推断不代表糊弄,防止运行期报错
var a = 1
var b = "1"
var c = '1'

xx(a) {} // 无法推断

使用 Scala:

  • 开发:JDK + SDK(编译器)
  • 运行:JDK + JRE

2.1 Scala 的 六大特点

  • SEANLESS JAVA INTEROP:Scala 运行在 JVM 上,因此 Java 代码和 Scala 代码可以相互混合,实现完全无缝集成;
  • TYPE INFERENCE:Don't work for the type system,Let the type system work for you。
  • CONCURRENCY & DISTRIBUTION:对集合进行并行操作,使用 actors 进行并发和分发,以及 futures 的异步编程
  • TRAITS:将 JAVA 风格接口的灵活性和类的强大功能结合起来
  • PATTERN MATCHING:类似于"Switch",实现类的结构、序列、常量匹配
  • HIGER-ORDER FUNCTIONS:函数是第一类值,函数可以是变量,也可以传递给其他函数

3. Hello World

学习计算机语言的的入门方式,就是我们熟悉的"Hello World",下面就是 Scala 中的示例:

object Hello {
  def main(args: Array[String]): Unit = {
    println("Hello,World")
  }
}
  • 代码在 Hello 对象中定义了一个 main 函数

    • object 类似于 class,可以理解为一个类的单例
    • main 函数则类似于一个静态方法
  • main 函数接收一个名为 args 的输入参数,它是一个字符串数组

  • Array 是一个包装 Java 数组原语的类

代码保存为".scala"文件,使用 scalac 命令进行编译:

scalac Hello.scala

编译完成后,创建了两个文件:

  • Hello$.class
  • Hello.class

这些都是【类的字节码】文件,都可以在 JVM 中运行,使用 scala 命令可以运行 Hello 程序:

scala Hello

3.1 关于注释

Scala 中的注释与 Java 中类似:

// 单行注释

/* 
 * 多行注释
 */

/** 
 * 多行注释
 */

3.2 命名惯例

Scala 命名约定遵循与 Java 相同的"驼峰式"命名:

  • 类名:Person,StoreEmployee
  • 变量名:name,firstName
  • 函数名:convertToInt,toUpper

3.3 输入输出

  • println 将输出写入标准输出(STDOUT),字符串后会添加一个换行符
println("Hello,world")
  • print 将输出写入标准输出,不添加换行符
print("Hello,world")
  • 也可以将输出写入标准错误(STDERR)
System.err.println("yikes, an error happend")
  • readLine 实现命令行输入
import scala.io.StdIn.readLine

object HelloInteractive extends App {
  print("Enter your first name : ")
  val firstName = readLine()

  print("Enter your last name : ")
  val lastName = readLine()

  println(s"Your name is $firstName $lastName")
}

上述代码中使用到了 import 关键字,该导入语句将 readLine 函数带入到当前范围,从而可以在程序中使用。

4. 两种变量

Scala 有两种类型的变量:

  • 【val】表示一种不可变变量,类似于 Java 中的 final,在 scala 中推荐使用该变量
  • 【var】表示一种可变变量,建议在有特定需要的情况下使用

在 Scala 中,通常不声明变量的类型就可以创建变量:

// val a: Int
val a = 1
// var b: String
var b = "bbxx"

这时,Scala 编译器会自动推断变量的类型,这就是【类型推断】的特性,当然,也可以显示声明变量的类型:

val c: Int = 2
val d: String = "String"

4.1 内置的类型

Scala 中的数据类型都是对象,而不是基本类型,下面是内置的数据类型,其中 Int 和 Double 是默认的类型:

DataType 值域
Boolean true or false
Byte 8-bit 有符号2进制整数(-2^7 到 2^7 - 1)
Short 16-bit 有符号2进制整数(-2^15 到 2^15 - 1)
Int 32-bit 有符号2进制整数(-2^31 到 2^31 - 1)
Long 64-bit 有符号2进制整数(-2^63 到 2^63 - 1)
Float 32-bit 单精度浮点数
Double 64-bit 双精度浮点数
Char 16-bit 无符号 Unicode 字符
String 字符序列

4.2 BigInt 和 BigDecimal

对于大数字,可以使用 BigInt 和 BigDecimal 类型:

var b = BigInt(12345667790)
var b = BigDecimal(123456.789)

BigInt 和 BigDecimal 也支持处理普通数值类型的运算符。

4.3 String 和 Char

使用双引号括住字符串,使用单引号括住字符

val name = "Bill"
val c = 'a'

当需要对字符串进行合并时,可以使用如下操作:

val firstName = "John"
val mi = 'C'
val lastName = "Doe"

val name = firstName + " " + mi + " " + lastName
val name = s"$firstName $mi $lastName"

println(s"Name: $firstName $mi $lastName")

在字符串前加上字母's',然后在字符串内的变量名前面方式一个 '$' 符号,这就是字符串插值

  • 可以将变量名用花括号包围
println(s"Name: ${firstName} ${mi} ${lastName}")
  • 表达式也可以放入大括号中
println(s"1+1= ${1+1}")
  • 在字符串前加上字母 'f',就可以在字符串内部使用 printf 样式的格式设置
  • raw 插值不会执行字符串中的转义字符
  • 可以自定义字符串插值

使用三个双引号,可以创建多行字符串:

val speech = """
FOUR score and 
|	seven years ago
|	our fathers...
""".stripMargin

'|' 和 stripMargin 的作用是避免第一行后的行式缩进的。

5. 流程控制

5.1 if/else

Scala 的 if/else 控制结构类似于 Java 中的 if/else:

if (条件1) {

} else if (条件2) {

} else {

}

if 结构总是会返回一个结果,这个结果可以选择忽略,也可以将结果赋给一个变量,写作三元运算符:

val x = if (a < b) a else b

【面向表达式编程】

  • 当编写的每个表达式都返回一个值时,这种风格被称为面向表达式的编程或 EOP
  • 相反,不返回值的代码被称为语句,用于产生其他效果

5.2 模式匹配

Scala 中有 match 表达式,类似于 Java 中的 switch 语句:

val result = i match {
	case 1 => "one"
  case 2 => "two"
  case _ => "not 1 or 2"
}

match 表达式可以用于任何数据类型:

def getClassAsString(x: Any): String = x match {
  case s: String => s + " is a String"
  case i: Int => "Int"
  case f: Float => "Float"
  case l: List[_] => "List"
  case _ => "Unknown"
}

match 表达式也可以返回值,可以将字符串结果赋给一个新值:

val monthName = i match {
  case 1 => "January"
  case 2 => "February"
  case 3 => "March"
  case 4 => "April"
  case 5 => "May"
  case _ => "Invalid month"
}

match 表达式支持在一个 case 语句中处理多个 case,下面的代码演示了将 0 或空字符串计算为 false:

def isTrue(a: Any) = a match {
  case 0 | "" => false
  case _ => true
}

可以看到这里输入参数 a 被定义为 Any 类型,这是所有 Scala 类的根类,就像 Java 中的 Object。

在 case 语句中使用 if 表达式可以表达强大的模式匹配:

count match {
  case 1 => println("one, a lonely number")
  case x if x == 2 || x == 3 => println("two's company, three's a crowd")
  case x if x > 3 => println("4+, that's a party")
  case _ => println("i'm guessing your number is zero or less")
}

i match {
  case a if 0 to 9 contains a => println("0-9 range: " + a)
  case b if 10 to 19 contains b => println("10-19 range: " + b)
  case _ => println("Hmmm...")
}

5.3 异常捕获

Scala 的异常捕获由 try/catch/finally 结构完成:

try {
	writeToFile(text)
} catch {
  case fnfe: FileNotFoundException => println(fnfe)
  case ioe: IOException => println(ioe)
}

finally 子句通常在需要关闭资源时使用:

try {
  
} catch {
  case foo: FooException => handleFooException(foo)
} finally {
  
}

5.4 for 循环

Scala 中的 for 循环用来迭代集合中的元素,通常这样写:

for (arg <- args) println(arg)

for (i <- 0 to 5) println(i)

for (i <- 0 to 10 by 2) println(i)

在 for 循环中还可以使用 【yield】 关键字,从现有集合创建新集合:

val x = for (i <- 1 to 5) yield i * 2

在 yield 关键字之后使用代码块可以解决复杂的创建问题:

val capNames = for (name <- names) yield {
  val nameWithoutUnderscore = name.drop(1)
  val capName = nameWithoutUnderscore.capitalize
  capName
}

// 简约格式
val capNames = for (name <- names) yield name.drop(1).capitalize

还可以向 for 循环中添加守卫代码,实现元素的过滤操作:

val fruit = for{
  f <- fruits if f.length > 4
} yield f.length

5.5 while 或 do/while 循环

while (condition) {
  
}

do {
  
} while (condition)

6. Class

Scala 中的类与 Java 中类似:

class Person(var firstName: String, var lastName: String) {
  def printFullName() = println(s"$firstName $lastName")
}

类的调用如下:

val person = new Person("zhang", "san")
println(person.firstName)
person.lastName = "si"
person.printFullName()

【注意】:

  • 不需要创建"get"和"set"方法来访问类中的字符

6.1 类构造函数

Scala 在定义类时,可以定义参数,这样编译器会产生默认构造函数:

class Person(var firstName: String, var lastName: String)

类构造函数中定义的参数会自动在类中创建字段,可以通过"."来访问,上面代码中两个字段都定义为 var 字段,所以是可变的,如果没有手动指定,那么默认是 val 字段,且默认是 private,只有类名构造函数中的参数可以设置为 var,其他函数中的参数都是 val 类型。

在 Scala 中,一个类的主构造函数是以下的组合:

  • 构造函数参数
  • 类主体中调用的函数
  • 类主体中执行的语句和表达式

类主体中声明的字段类似于 Java 静态代码块,在类首次实例化时分配:

class Person(var firstName: String, var lastName: String) {
	println("the constructor begins")

  // 默认是 public
  var age = 0

  private val HOME = System.getProperty("user.home")

  override def toString(): String = s"$firstName $lastName is $age years old"

  def printHome(): Unit = println(s"HOME = $HOME")
  def printFullName(): Unit = println(this)

  printHome()
  printFullName()
  println("you've reached the end of the constructor")
}

这段代码的执行结果如下:

the constructor begins
HOME = “”
$firstName $lastName is $age years old
you've reached the end of the constructor

6.2 this 辅助构造函数

通过 this 关键字可以定义辅助构造函数:

  • 每个辅助构造函数必须具有不同的函数签名(不同的参数列表)
  • 每个构造函数必须调用之前定义的构造函数之一
val DefaultCrustSize = 12
val DefaultCrustType = "THIN"

class Oizza (var crustSize: Int, var crustType: String) {
  def this(crustSize: Int) = {
    this(crustSize, DefaultCrustType)
  }

  def this(crustType: String) = {
    this(DefaultCrustSize, crustType)
  }

  def this() = {
    this(DefaultCrustSize, DefaultCrustType)
  }

  override def toString = s"A $crustSize inch pizza with a $crustType crust"
}

6.3 默认值构造函数

Scala 允许为构造函数提供默认值:

class Socket(var timeout: Int = 2000, var linger: Int = 3000) {
  override def toString = s"timeout: $timeout, linger: $linger"
}

默认值构造函数为参数提供了首选的默认值,但也可以根据自己的需要重写这些值,同时在调用构造函数时可用指定参数的名称:

val s = new Socket(timeout = 1000, linger = 3000)

6.4 枚举类

枚举是创建小型常量组的工具,如一周中的几天、一年中的几个月等。

sealed trait DayOfWeek
case object Sunday extends DayOfWeek
case object Monday extends DayOfWeek
case object Tuesday extends DayOfWeek
case object Wednesday extends DayOfWeek
case object Thursday extends DayOfWeek
case object Friday extends DayOfWeek
case object Saturday extends DayOfWeek

只需要声明一个基本的 trait,然后根据需要使用 case 对象来扩展该 trait。

6.5 伴生对象

Scala 中的伴生对象即用 object 关键字声明的对象,并且与 class 有相同的名称。

class Pizza {}

object Pizza {}
  • 伴生对象及其类可以访问彼此的私有成员(字段和函数)
  • 当在伴生对象中定义一个 apply 函数后,可以无需使用 new 关键字即可创建类的实例,实际是 apply 方法充当了工厂方法
classs Person {
  var name = ""
}

object Person {
  def apply(name: String): Person = {
    var p = new  Person
    p.name = name
    p
  }
}

val p = Person.apply("Fred Flinstone")
val p = Person("Fred Flinstone")

在调用时,实际过程如下:

  • 输入 val p = Person("Fred")
  • Scala 编译器发现在 Person 之前没有 new 关键字
  • 编译器在 Person 类的伴生对象中查找与输入的函数签名匹配的 apply 方法
  • 找到了就调用 apply 方法,否则返回编译器错误

在伴生对象中可以创建多个 apply 方法,从而提供多个构造函数。

class Person {
  var name: Option[String] = None
  var age: Option[Int] = None
  override def toString = s"$name, $age"
}

object Person {
  def apply(name: Option[String]): Person = {
    var p = new Person
    p.name = name
    p
  }

  def apply(name: Option[String], age: Option[Int]): Person = {
    var p = new Person
    p.name = name
    p.age = age
    p
  }
}

Scala 中还提供了反构造方法,可以从一个对象实例中返回传入的参数:

class Person(var name: String, var age: Int)

object Person {
  def unapply(p: Person): String = s"${p.name}, ${p.age}"
}

val p = new Person("Lori", 29)
val result = Person.unapply(p)

6.6 样例类

样例类使用 case 关键字定义,它具有常规类的所有功能:

  • 默认情况下,样例类的构造参数是公共 val 字段,每个参数会生成访问方法
  • apply 方法是在类的伴生对象中创建的,所以不需要使用 new 关键字创建实例
  • unapply 方法对实例进行解构
  • 类中会生成一个 copy 方法,在克隆对象或克隆过程中更新字段时非常有用
case class BaseballTeam(name: String, lastWorldSeriesWin: Int)

val cubs1908 = BaseballTeam("Chicago Cubs", 1908)

val cubs2016 = cubs1908.copy(lastWorldSeriesWin = 2016)
  • 类中会生成 equals 和 hashcode 方法,用于比较对象
  • 生成默认的 toString 方法

6.7 样例对象

样例对象使用 case object 关键字定义:

  • 可以序列化
  • 有一个默认的 hashcode 实现
  • 有一个 toString 实现

样例对象主要用于:

  • 创建枚举
sealed trait Topping
case object Cheese extends Topping
case object Pepperoni extends Topping
case object Sausage extends Topping
case object Mushrooms extends Topping
case object Onions extends Topping

sealed trait CrustSize
case object SmallCrustSize extends CrustSize
case object MediumCrustSize extends CrustSize
case object LargeCrustSize extends CrustSize

sealed trait CrustType
case object RegularCrustType extends CrustType
case object ThinCrustType extends CrustType
case object ThickCrustType extends CrustType

case class Pizza {
  crustSize: CrustSize,
  crustType: CrustType,
  toppings: Seq[Topping]
}
  • 为在其他对象之间传递的"消息"创建容器时(如 Akka actor 库)
case class StartSpeakingMessage(textToSpeak: String)
case object StopSpeakingMessage
case object PauseSpeakingMessage
case object ResumeSpeakingMessage

class Spark extends Actor {
  def receive = {
    case StartSpeakingMessage(textToSpeak) =>
        // code to speak the text
    case StopSpeakingMessage =>
        // code to stop speaking
    case PauseSpeakingMessage =>
        // code to pause speaking
    case ResumeSpeakingMessage =>
        // code to resume speaking
  }
}

7. 函数

Scala 中定义函数的方式如下:

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

// 不声明函数的返回类型
def sum(a: Int, b: Int) = a + b
  • def 是用来定义函数的关键字
  • sum 是函数名
  • 输入参数 a 的类型是 Int
  • 输入参数 b 的类型是 Int
  • Int 是函数的返回值类型
  • = 左侧是函数名和函数签名
  • = 右侧是函数体

函数的调用如下:

val x = sum(1, 2)

7.1 匿名函数

  • 匿名函数可以作为代码段编写
  • 匿名函数经常和集合中的 map、filter 等方法一起使用
// 创建一个 List
val ints = List(1, 2, 3)

val doubledInts = ints.map(_ * 2)
val doubledInts = ints.map((i: Int) => i * 2)
val doubledInts = ints.map(i => i * 2)

val x = ints.filter(_ > 5)
val x = ints.filter(_ % 2 == 0)

8. Traits

Scala 中 Traits 类似于 Java 中的接口,它可以将代码分解成小的模块化单元,实现对类的扩展。

trait Speaker {
  def speak(): String // 这是一个抽象方法
}

trait TailWagger {
  def startTail(): Unit = println("tail is wagging")
  def stopTail(): Unit = println("tail is stopped")
}
class Dog(name: String) extends Speaker with TailWagger {
  def speak(): String = "Woof!"
}

上面的代码中,我们使用 extends 关键字和 with 关键字为 Dog 类扩展了 speak 方法。

8.1 trait

定义一个 trait,它具有一个具体方法和一个抽象方法:

trait Pet {
  def speak = println("Yo")
  def comToMaster(): Unit
}

当一个类扩展一个 trait 时,每个抽象方法都必须实现:

class Dog(name: String) extends Pet {
  def comToMaster(): Unit = println("Woo-hoo, I'm coming!")
}

对于具有具体方法的 trait,可以在创建实例时将其混合:

val d = new Dog("Fido") with TailWagger with Runner

8.2 重写方法

类可以覆盖在 trait 中已定义的方法:

class Cat extends Pet {
  override def speak(): Unit = println("meow")
  def comToMaster(): Unit = println("That's not happen.")
}

8.3 抽象类

Scala 中的抽象类使用较少,一般只在以下情况使用:

  • 希望创建一个需要构造函数参数的基类
  • Scala 代码将被 Java 代码调用

由于 Scala 的 trait 不支持构造函数参数:

// 报错
trait Animal(name: String)

当一些基类需要具有构造函数参数时,使用抽象类:

abstract class Animal(name: String)
  • 一个类只能扩展一个抽象类
abstract class Pet(name: String) {
  def speak(): Unit = println("Yo")
  def comeToMaster(): Unit
}

class Dog(name: String) extends Pet(name) {
  override def speak() = println("Woof")
  def comToMaster() = println("Here I come!")
}

9. 集合类型

Class 描述
ArrayBuffer 有索引的、可变序列
List 线性链表、不可变序列
Vector 有索引的、不可变序列
Map 键值对
Set 无序的、去重集合
  • Map 和 Set 都有可变和不可变两种版本

9.1 List

List 是一个线性的、不可变的序列,即一个无法修改的链表,想要添加或删除 List 元素时,都要从现有 List 中创建一个新 List。

List 列表的构建方式如下:

val ints = List(1, 2, 3)
val nums = List.range(0, 10)
val nums = (1 to 10 by 2).toList
val letters = ('a' to 'f').toList
val letters = ('a' to 'f' by 2).toList

因为 List 是不可变的,只能通过创建一个新列表来象现有 List 中添加或追加元素:

val a = List(1, 2, 3)

// 在 List 前面添加元素
val b = 0 +: a
val b = List(-1, 0) ++: a
  • 如果需要在一个不可变序列中添加元素,可以使用 Vector
  • 因为 List 是一个链表类,所以不要通过索引值访问大型列表元素,可以使用 Vector 或 ArrayBuffer

9.2 Map

Map 是由键和值组成的可迭代序列。

Scala 中的 Map 类似于 Java 的 HashMap:

val ratings = Map (
  "Lady in the water" -> 3.0,
  "Snakes on a plane" -> 4.0,
  "You, Me and Dupree" -> 3.5
)

【可变的 Map 】:

// 导入
import scala.collection.mutable.Map

var states = collection.mutable.Map("AK" -> "Alaska")

// 添加单个元素
states += ("AL" -> "Alabama")

// 添加多个元素
states += ("AR" -> "Arkansas", "AZ" -> "Arizona")

// 从另一个Map添加元素
states ++= Map("CA" -> "California", "CO" -> "Colorado")

// 删除元素
states -= "AR"
states -= ("AL", "AZ")
states --= List("AL", "AZ")

// 更新元素
states("AK") = "Alaska, A Really Big State"

// 迭代元素
for((k, v) <- ratings) println(s"key: $k, value: $v")

ratings.foreach {
  case(movie, rating) => println(s"key: $movie, value: $rating")
}

// 获取 Map 所有的 key
states.keys

// 获取 Map 所有的 Value
states.values

// 判断 Map 是否包含某个 Key
states.contains(3)

// 对 Map 的 value 进行转换操作
states.transform((k, v) => v.toUpperCase)

// 对 Map 的 key 进行过滤
states.view.filterKeys(Set("AR", "AL")).toMap

// 获取一个 Map 的前两个元素
states.take(2)

9.3 ArrayBuffer

ArrayBuffer 是一个可变序列,可以使用它的方法来修改它的内容:

// 导入
import scala.collection.mutable.ArrayBuffer

val ints = ArrayBuffer[Int]()
val names = ArrayBuffer[String]()

ints += 1
ints += 2

// 创建带有初试元素的 ArrayBuffer
val nums = ArrayBuffer(1, 2, 3)
nums += 5 += 6
nums ++= List(7, 8, 9)

// 删除元素
nums -= 9
nums -= 7 -= 8
nums --= Array(5, 6)

ArrayBuffer 具有一些它自己的方法:

val a = ArrayBuffer(1, 2, 3)		// 1, 2, 3
a.append(4)											// 1, 2, 3, 4
a.appendAll(Seq(5, 6))					// 1, 2, 3, 4, 5, 6
a.clear()												// 

val a = ArrayBuffer(9, 10)			// 9, 10
a.insert(0, 8)									// 8, 9, 10
a.insertAll(0, Vector(4, 5, 6, 7)) // 4, 5, 6, 7, 8, 9, 10
a.prepend(3)											 // 3, 4, 5, 6, 7, 8, 9, 10
a.prependAll(Array(0, 1, 2))			 // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10

val a = ArrayBuffer.range('a', 'h') // a, b, c, d, e, f, g
a.remove(0)													// b, c, d, e, f, g
a.remove(2, 3)											// b, c, g
a.dropInPlace(2)										// g
a.dropRightInPlace(2)

9.4 Vector

Vector 是一个索引的、不可变序列,索引意味着可以通过索引值快速访问,其他方面与 List 类似:

val nums = Vector(1, 2, 3, 4, 5)
val strings = Vector("one", "two")

val peeps = Vector(
  Person("Bert"),
  Person("Ernie"),
  Person("Grover")
)

由于 Vector 是不可变的,只能通过创建新序列的方式来向现有 Vector 追加元素:

val b = a :+ 4
val b = a ++ Vector(4, 5)

val b = 0 +: a
val b = Vector(-1, 0) ++: a

9.5 Set

Set 是一个没有重复元素的可迭代集合。同时拥有可变和不可变的 Set 类,默认是不可变的。

【可变 Set】:

import scala.collection.mutable.Set

var set = scala.collection.mutabl.Set[Int]()
set += 1
set +=2 +=3
set ++= Vector(4, 5)

// add函数,元素添加成功返回true,否则false
set.add(6)
set.add(5)

set -= 1
set -= (2, 3)
set --= Array(4, 5)

// 清空
set.clear()

// 删除
set.remove()

9.6 集合常用函数

下面是一些常用的函数(这些函数都是返回一个新的集合,不改变原有集合):

  • foreach:该函数对列表进行遍历操作,同时可以传入函数参数,对每个元素执行该函数
nums.foreach(println)
  • filter:该函数用于对列表元素进行过滤
nums.filter(_ < 4).foreach(println)
  • map:将传入的函数作用于列表中的每个元素,为每个元素返回一个新的、转换后的值
val doubles = nums.map(_ * 2)
  • head:返回第一个元素
nums.head
“foo”.head
  • tail:返回 head 元素之后的每一个元素
nums.tail

"foo".tail
  • take:将元素从集合中提取出来
nums.take(1)
nums.take(2)
  • takeWhile:将符合条件的元素从集合中提取出来
nums.takeWhile(_ < 5)
  • drop:将指定元素之外的元素从集合中提取出来
nums.drop(1)

nums.drop(5)
  • dropWhile:将条件之外的元素从集合中提取出
nums.dropWhile(_ < 5)
  • reduce:接收一个函数,并将函数作用于集合的后续元素
def add(x: Int, y: Int): Int = {
  val theSum = x + y
  println(s"received $x and $y, their sum is $theSum")
  theSum
}

val a = List(1, 2, 3, 4)
a.reduce(add)
a.reduce(_ + _)
  • foldLeft:支持传入一个种子值,然后在其基础上进行计算操作
nums.foldLeft(0)(_ + _)
nums.foldLeft(1)(_ * _)

10. Tuples

元组中可以放入不同类型的元素(同一容器中存储异构数据),可以包含2个到22个值,所有值可以具有不同的类型:

val t = (11, 11.0, "Eleven")

对于元组变量,可以通过"_数字"来访问它的值:

t._1
t._2
t._3

也可以用模式匹配来将元组中的元素分配给各个变量:

val (symbol, price, volume) = ("AAPL", 123.45, 1101011)

当需要多次使用相同的元组时,可以声明一个专用的 case 类:

case class StockInfo(symbol: String, price: BigDecimal, volume: Long)

11. 函数式编程

函数式编程是一种强调只使用纯函数和不可变值编写应用程序的编程风格。

11.1 纯函数

纯函数应该有以下特点:

  • 函数的输出只取决于它的输入变量
  • 函数不会改变任何隐藏状态
  • 函数没有任何“后门”:不从外部世界(控制台、Web服务、数据库、文件等)读取数据,也不向外部世界写出数据

Scala 中 "scala.math._package"中的如下方法都是纯函数:

  • abs
  • ceil
  • max
  • min

当然,Scala 中有很多非纯函数,如 foreach:

  • 集合类的 foreach 函数是不纯粹的,它具有其副作用,如打印到 STDOUT
  • 它的方法签名声明了返回 Unit 类型,类似的,任何返回 Unit 的函数都是一个不纯函数

非纯函数通常有以下特点:

  • 读取隐藏的输入,即访问没有显示传递到函数中作为输入参数的变量和数据
  • 写出隐藏输出
  • 修改给定的参数
  • 对外部世界执行某种I/O

11.2 传递函数

函数可以作为变量进行传递,允许将函数作为参数传递给其他函数。

val doubles = nums.map(_ * 2)

11.3 没有 Null 值

在 Scala 中,没有 null 的存在,使用 Option/Some/Nothing 这样的结构进行处理。

  • Some 和 Nothing 是 Option 的子类
  • 通常声明一个函数返回一个 Option 类型
  • 接收到可以处理的参数则返回 Some
  • 接收到无法处理的参数则返回 Nothing
def toInt(s: String): Option[Int] = {
  try{
    Some(Integer.parseInt(s.trim))
  } catch {
    case e: Exception => None
  }
}

toInt(x) match {
  case Some(i) => println(i)
  case None => println("That didn't work.")
}

val y = for {
  a <- toInt(stringA)
  b <- toInt(stringB)
  c <- toInt(stringC)
} yield a + b + c
  • 当三个字符串都转换为整数时,则返回一个包装在 Some 中的整数
  • 当三个字符串中任何一个不能转换为整数时,则返回一个 None
posted @   编程理想国  阅读(53)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端
点击右上角即可分享
微信分享提示