概览
Scala 名称来源于 scalable (可拓展的) ,主要设计者是 javac
之父 Martin Odersky 。
驱动着世界上最繁忙的网站,包括 Twitter、 Netflix、Tumblr、 LinkedIn、 Foursquare 等等。
以下是关于 Scala 的概览:
- 高级语言。
- 静态类型。
- 它的语法简洁但仍可读,我们称之为 impressive (表现力)。
- 支持面向对象编程范例。
- 支持函数式编程范例。
- 拥有完善的类型推断系统。
- Scala 源码编译生成
.class
文件在 JVM (Java 虚拟机) 上运行。 - 在 Scala 中可以轻易使用 Java 库。
安装
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") // 不必要的冗余
内建数据类型
- 数字类型: Byte、 Int、 Long、 Short、 Double、 Float 。
- 大数: BigInt、 BigDecimal 。
- 字符(串): String、 Char 。
字符串的小技巧
字符串拼接
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/then/else 结构
- try/catch/finally 表达式
- for 循环
-
独特结构
- for 表达式
- match 表达式
常见结构
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)
Seq
和 List
是两种线性集合。 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 的这几种基础集合类型:
List
ListBuffer
Vector
ArrayBuffer
Map
Set
有多种方法可以填充列表
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
, Double
和 String
)的元组(也称为 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)