Scala 入门 01

2020/10/19

概览

Scala 名称来源于 scalable (可拓展的) ,主要设计者是 javac 之父 Martin Odersky 。 驱动着世界上最繁忙的网站,包括 Twitter、 Netflix、Tumblr、 LinkedIn、 Foursquare 等等。

以下是关于 Scala 的概览:

安装

https://www.scala-lang.org/download

Hello, world

cat <<EOF > Hello.scala
object Hello extends App {
    println("Hello, world")
}
EOF

scalac Hello.scala
scala Hello

使用 REPL

Read-Evaluate-Print-Loop

交互式命令行,类似 playground 可以实时运行 Scala 代码。

输入 scala 命令即可进入 REPL 模式:

❯ scala
Welcome to Scala 2.12.12 (Eclipse OpenJ9 VM, Java 1.8.0_272).
Type in expressions for evaluation. Or try :help.

scala> 

接下来的练习,可以直接在 REPL 模式下进行。

基础

变量

两种声明变量的方式

val a = 1 // immutable
var b = 2 // mutable

最直接的区别就是, val 声明的变量不可变, var 声明的变量可变。

Scala 惯例上,推荐使用 val 。这使得代码更像代数运算,有助于入门函数式编程。

REPL 中与源码编译运行方式不是完全相同的,比如你可以重新定义 val 声明的变量。

声明变量类型

Scala 拥有类型推断(type inference)能力。因此定义变量时,类型是可选的。

val count: Int = 1
val name: String = "shank"

val count0 = 1
val name0 = "shank"

显示声明变量类型,有时显得冗余:

val p = new Person("Mike") // 推荐
val p: Person = new Person("Mike") // 不必要的冗余

内建数据类型

字符串的小技巧

字符串拼接

val firstName = "John"
val lastName = "Doe"

val name = firstName + " " + lastName

可读性更好的拼接方式 —— 字符串插值

val name = s"$firstName $lastName"

字符串插值的更多功能

println(s"1+1 = ${1+1}")

多行字符串

val speech = """Four score and
               seven years ago
               our fathers ..."""

解决多行输出的缩进问题,在除了第一行之外每行之前加 | ,最后调用 stripMargin 方法。

val speech = """Four score and
               |seven years ago
               |our fathers ...""".stripMargin
实用数据类型

List

val fruit = List("apples", "oranges", "pears")
val nums = List(1, 2, 3, 4)
val diag3 = List(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1))
val empty = List()

// 另类构造方式
val nums0 = 1 :: 2 :: 3 :: Nil
val nums1 = Nil.::(4).::(3).::(2).::(1)

Map

val ratinbgs = Map(
    "Lady in the Water"  -> 3.0, 
    "Snakes on a Plane"  -> 4.0, 
    "You, Me and Dupree" -> 3.5
)

关于集合类,后面有更多介绍

控制结构

常见结构

if/else 控制结构
if (a == b) {
    doX()
} else if (a > b) {
    doY()
} else {
    doZ()
}

if 表达式总是返回一个结果:

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

因此 scala 设计出来不需要三目运算符。

try/catch/finally
try {
    // your scala code here
} catch {
    case foo: FooException => handleFooException(foo)
    case bar: BarException => handleBarException(bar)
    case _: Throwable => println("Got some other kind of Throwable exception")
} finally {
    // your scala code here, such as closing a database connection
    // or file handle
}
for 循环
val nums = Seq(1, 2, 3)
for (n <- nums) println(n)

val names = List("Bill", "Leo", "Kite")
for (n <- names) println(n)

SeqList 是两种线性集合。 Scala 中这些线性集合类都比 Array 更可取。(以后解释)

线性集合可以使用 foreach 方法做循环处理

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

for ((name,rating) <- ratings) println(s"Movie: $name, Rating: $rating")

ratings.foreach {
    case(movie, rating) => println(s"key: $movie, value: $rating")
}
while 和 do/while 循环
// while loop
while(condition) {
    statement(a)
    statement(b)
}

// do-while
do {
   statement(a)
   statement(b)
} 
while(condition)

独特结构

for 表达式

常用于利用现有集合构造新的集合

val nums = Seq(1,2,3)
val doubledNums = for(n <- nums) yield n * 2

val names = List("adam", "david", "frank")
val ucNames = for (name <- names) yield name.capitalize

yield 关键字也支持代码块

val names = List("_adam", "_david", "_frank")
val capNames = for (name <- names) yield {
    val nameWithoutUnderscore = name.drop(1)
    val capName = nameWithoutUnderscore.capitalize
    capName
}
match 表达式

类似于其他语言中的 switch 语句。

// i 是一个整数
i match {
    case 1  => println("January")
    case 2  => println("February")
    case 3  => println("March")
    case 4  => println("April")
    case 5  => println("May")
    case 6  => println("June")
    case 7  => println("July")
    case 8  => println("August")
    case 9  => println("September")
    case 10 => println("October")
    case 11 => println("November")
    case 12 => println("December")
    // 缺省处理
    case _  => println("Invalid month")
}

match 表达式也有返回值:

val monthName = i match {
    case 1  => "January"
    case 2  => "February"
    case 3  => "March"
    case 4  => "April"
    case 5  => "May"
    case 6  => "June"
    case 7  => "July"
    case 8  => "August"
    case 9  => "September"
    case 10 => "October"
    case 11 => "November"
    case 12 => "December"
    case _  => "Invalid month"
}

match 表达式可以作为方法体:

def convertBooleanToStringMessage(bool: Boolean): String = bool match {
    case true => "you said true"
    case false => "you said false"
}
val result = convertBooleanToStringMessage(true)
println(result)

处理可替代的 case :

val evenOrOdd = i match {
    case 1 | 3 | 5 | 7 | 9 => println("odd")
    case 2 | 4 | 6 | 8 | 10 => println("even")
    case _ => println("some other number")
}

if 表达式用在 case 语句中:

count match {
    case 1 => 
        println("one, a lonely number")
    case x if x == 2 || x == 3 => // if 作为表达式可以省略括号
        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")
}

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

class Person(){} // 自定义类
def getClassAsString(x: Any):String = x match {
    case b: Boolean => b + " is Boolean"
    case s: String => s + " is a String"
    case i: Int => "Int"
    case f: Float => "Float"
    case l: List[_] => "List"
    case p: Person => "Person"
    case _ => "Unknown"
}

类和方法

先介绍一般类 class

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

val p = new Person("Julia", "Kern")
println(p.firstName)
p.lastName = "Manes"
p.printFullName()

类方法,也是可以省略返回值类型的

class Tool() {
    def sum(a: Int, b: Int): Int = a + b // 带方法返回类型
    def concatenate(s1: String, s2: String) = s1 + s2 // 省略方法返回类型
}

特质(Traits)

类似抽象类或者 interface ,构造模块化类的关键

trait Speaker {
    def speak(): String  // has no body, so it’s abstract
}

trait TailWagger {
    def startTail(): Unit = println("tail is wagging")
    def stopTail(): Unit = println("tail is stopped")
}

trait Runner {
    def startRunning(): Unit = println("I’m running")
    def stopRunning(): Unit = println("Stopped running")
}

// 对于 Dog 类,需要实现 spead 方法。
class Dog(name: String) extends Speaker with TailWagger with Runner {
    def speak(): String = "Woof!"
}

// 也可以重写 trait 的方法
class Cat extends Speaker with TailWagger with Runner {
    def speak(): String = "Meow"
    override def startRunning(): Unit = println("Yeah ... I don’t run")
    override def stopRunning(): Unit = println("No need to stop")
}

集合类(Collections classes)

推荐尽早学习 Scala 的这几种基础集合类型:

有多种方法可以填充列表

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

有多种序列集合类 Array, ArrayBuffer, Vector, List 等等,以 List 为例, 看看序列方法。

准备两个列表

val nums = (1 to 10).toList
val names = List("joel", "ed", "chris", "maurice")

foreach 方法

scala> names.foreach(println)
joel
ed
chris
maurice

filter 方法

scala> nums.filter(_ < 4).foreach(println)
1
2
3

map 方法

scala> val doubles = nums.map(_ * 2)
doubles: List[Int] = List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)

scala> val capNames = names.map(_.capitalize)
capNames: List[String] = List(Joel, Ed, Chris, Maurice)

scala> val lessThanFive = nums.map(_ < 5)
lessThanFive: List[Boolean] = List(true, true, true, true, false, false, false, false, false, false)

foldLeft 方法(最强大的集合方法之一)

scala> nums.foldLeft(0)(_ + _)
res9: Int = 55

scala> nums.foldLeft(1)(_ * _)
res10: Int = 3628800

foldLeft 方法的第一个入参是一个 seed 值,很容易理解,上面第一个例子计算了 nums 中元素的和,第二个例子计算了 nums 中元素的积。

还有很多(很多!)集合类方法,以后再谈。

元组(Tunples)

元组让你可以把异构元素放在一个小的容器里。一个元组包含 2 到 22 个值,每个值可以具有不同类型。

一个包含三种不同类型(分别是 Int, DoubleString)的元组(也称为 Tuple3)如下:

(11, 11.0, "Eleven")

元组在许多地方提供方便,例如调用其他语言时用作临时类(ad-hoc class)。例如,你可以从方法返回元组而不是类:

def getAaplInfo(): (String, BigDecimal, Long) = {
    // get the stock symbol, price, and volume
    ("AAPL", BigDecimal(123.45), 101202303L)
}

val t = getAaplInfo()

// 元组类型的值可以通过下划线数字访问
t._1
t._2
t._3

// 通过模式匹配提取元组的值
val (symbol, price, volume) = getAaplInfo()

当你要快速(和临时)将一些东西组合在一起时,适合用元组。但当你注意到相同的元组被反复使用时,最好声明一个专用类,例如:

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

参考