在这本书中,我们希望证明Scala是优美的,现代的,富于表现力的编程语言。为了证明这一点,在这第一章节中,我们将直接介绍Scala的主要特性。在这之后,这本书会以一个更传统的“入门”章节开始。
本书假设你曾用过一种像java那样的语言,准备来看一系列Scala的例子,来感受一下这门语言吧。
概述
在进入具体示例之前,这里有一些关于Scala的重要事情需要知道:
- 它是一门高级语言
- 它是静态类型的
- 它的语法是简洁的但仍然易于阅读 — 我们称之为富有表现力
- 它支持面向对象编程范式
- 它支持函数式编程范式
- 它有一个复杂的类型推理系统
- Scala代码编译成在Java虚拟机上运行的 .class 文件
- 在Scala中很容易使用Java类库
Hello World
自从《C编程语言》一书问世以来,编程书中以“Hello World”作为示例开始已经成为传统,不要失望,这里有用Scala完成这个示例的一种方式:
object Hello extends App {
println("Hello,world.")
}
你把这段代码保存在命名为 Hello.scala 的一个文件中之后,你可以使用 scalac 编译它。
$ scalac Hello.scala
如果你是从Java 来到Scala的,scalac 就像 javac , 这个命令产生两个文件:
- Hello$.class
- Hello.class
这些与你用 javac 创建的 .class 字节码文件是一样的,它们已经准备好运行在JVM上了。你可以使用 scala 运行 Helllo 程序:
$ scala Hello
在接下来的课程中,我们会分享更多“Hello World”的例子,现在先不作介绍。
The Scala REPL
The Scala REPL (“Read-Evaluate-Print-Loop”) 是一个命令行解释器,你可以将它作为一个“游乐场”来测试你的Scala代码。我们在这里很早的介绍它,因此你可以在下面的代码示例中使用它:
在你的操作系统的命令行输入 scala 就可以进入一个 REPL 会话,你会看到这样的东西:
$ scala
Welcome to Scala 2.13.0 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_131).
Type in expressions for evaluation. Or try :help.
scala>
REPL是一个解释器,它就在那里等待你输入一些东西。在REPL中,你输入一些Scala表达式来看看它是如何运行的:
scala> val x = 1
x: Int = 1
scala> val y = x + 1
y: Int = 2
就像上面显示的,每次你在REPL输入一行表达式后,会在下一行输出结果。
变量的两种类型
Scala有两种类型的变量:
- val 表示一个不可变的变量,类似Java中的 final, 应该被优先选择使用。
- var 表示一个可变的变量,只有在有特定原因需要使用它时才使用它。
示例:
val x = 1 //immutable
var y = 0 //mutable
声明变量类型
在Scala中,你在创建变量时通常不会声明它们的类型:
val x = 1
val s = "a string"
val p = new Person("Regina")
当你这样做时,Scala通常可以为你推断出数据类型。就像在这个REPL中显示的:
scala> val x = 1
val x: Int = 1
scala> val s = "a string"
val s: String = a string
这个特性被称为 类型推断,它是一个帮助你保持代码简洁的好方法。你也可以明确的声明一个变量的类型,但通常不是必须的:
val x: Int = 1
val s: String = "a string"
val p: Person = new Person("Regina")
正如你看到的,上面代码有些不必要的冗长。
控制结构
下面是Scala控制结构的快速浏览。
if/else
Scala的 if/else 控制结构与其他语言是相似的。
if (test1) {
doA()
} else if (test2) {
doB()
} else if (test3) {
doC()
} else {
doD()
}
然而,与Java和很多其他语言不同的是,这里的 if/else结构返回一个值,因此,你可以把它用作三元运算符:
val x = if (a < b) a else b
match 表达式
Scala有一个 match 表达式,它的最基本用法像 Java 的 switch 语句:
val result = i match {
case 1 => "one"
case 2 => "two"
case _ => "not 1 or 2"
}
match 表达式不只限于整数,它可用于任何数据类型,包括布尔型:
val booleanAsString = bool match {
case true => "true"
case false => "false"
}
下面有个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 p: Person => "Person"
case _ => "Unknown"
}
强大的匹配表达式是Scala的一大特性,在本书的后面我们会展示更多它的示例。
try/catch
Scala的 try/catch 控制结构可以捕获异常,它与Java类似,但它的语法是与 match 表达式一致的:
try {
writeToFile(text)
} catch {
case fnfe: FileNotFoundException => println(fnfe)
case ioe: IOException => println(ioe)
}
for 循环及其表达式
Scala 的 for 循环,在本书中,通常写成如下的样子:
for (arg <- args) println(arg)
// "x to y" syntax
for (i <- 0 to 5) println(i)
// "x to y by" syntax
for (i <- 0 to 10 by 2) println(i)
你也可以在for循环中添加 yield 关键字变成 for 表达式,这个表达式会产生一个结果。
下面的一个 for 表达式,按照 1 到 5 的顺序,把每个值加倍:
val x = for (i <- 1 to 5) yield i * 2
下面的一个 for 表达式 遍历一个字符串列表:
val fruits = List("apple", "banana", "lime", "orange")
val fruitLengths = for {
f <- fruits
if f.length > 4
} yield f.length
因为Scala代码通常是很直观地,我们可以想象你能够猜出这段代码是如何运行的,即使你没有看多 for 表达式或Scala List 。
while 和 do/while
Scala 也有 while 和 do/while ,下面是它的一般语法:
// while loop
while(condition) {
statement(a)
statement(b)
}
// do-while
do {
statement(a)
statement(b)
}
while(condition)
类
下面是一个Scala 类的例子:
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()
请注意,这里不需要创建 set 和 get 方法来访问类中的字段。
下面有个更复杂的类,这个 Pizza 类在本书的后面还会见到:
class Pizza (
var crustSize: CrustSize,
var crustType: CrustType,
val toppings: ArrayBuffer[Topping]
) {
def addTopping(t: Topping): Unit = toppings += t
def removeTopping(t: Topping): Unit = toppings -= t
def removeAllToppings(): Unit = toppings.clear()
}
在上面的代码中,ArrayBuffer 就像 Java 中的 ArrayList。CrustSize, CrustType, 和Topping 类没有展示,但没有看到这些类,你也很可能是理解代码是如何运行的。
Scala 方法
就像别的 OOP(面向对象) 语言,Scala 类有方法,下面是Scala 方法的语法:
def sum(a: Int, b: Int): Int = a + b
def concatenate(s1: String, s2: String): String = s1 + s2
你不必必须声明一个方法的返回类型,所以按照下面的写法也是完全合法的,如果你更喜欢如此:
def sum(a: Int, b: Int) = a + b
def concatenate(s1: String, s2: String) = s1 + s2
下面是你如何调用方法:
val x = sum(1, 2)
val y = concatenate("foo", "bar")
你可以用方法做更多的事情,例如给方法参数提供默认的值,但是,现在上面的就是一个好的开始哦。
特质
在Scala中,特质是很有趣的。它们可以让你把代码分解成更小的模块单元。为了展示特质,下面有个从书中后面拿过来的例子:
给定了三个特质:
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 类,扩展自上面的这些特质同时为 speak( ) 方法提供方法体。
class Dog(name: String) extends Speaker with TailWagger with Runner {
def speak(): String = "Woof!"
}
类似的,下面是一个 Cat 类,展示了如何去重写多个特质的方法:
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")
}
如果上面的代码你觉得有意义,太好了,你对特质很满意,如果觉得没有意义,别担心,在后面我们会详细的解释它。
集合类
如果你是从Java转到Scala,并且准备真正的深入学习Scala, 很可能在Scala中使用Java类库,并且一些人这样做了几周或几个月同时对Scala也感到很满意。但强烈推荐你尽快地学习基本的Scala集合类 ---- List, ListBuffer, Vector, ArrayBuffer, Map, 和 Set。Scala集合类的一个很大的好处是它们提供了很多强大的方法,你会很快地开始使用它们来精简你的代码。
填充列表
有时创建填充了数据的样本列表是很有用的,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方法,后接foreach方法:
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)
即使没有任何解释你也可以明白map是如何运行的:它将你提供的算法应用于集合中的每个元素,为每个元素返回一个新的转换后的值。
如果你准备好了,来看其中一个最强大的集合方法,就是 foldLeft:
scala> nums.foldLeft(0)(_ + _)
res0: Int = 55
scala> nums.foldLeft(1)(_ * _)
res1: Int = 3628800
如果你知道foldLeft的第一个参数是种子值,你可以猜到,第一个例子输出的是 nums 中数的和,第二个例子输出的是nums 中数的积。
Scala集合类还有许多(许多哦!)可以使用的方法,会在本书后面的集合部分展示。很希望上面的让你了解到它的强大。
元组
元组让你可以把一些不同类型的元素集合放入到一个小容器中。元组可以包含 0 到 22 个值,即使类型全不一样。
例如,给定一个 Person 类:
class Person(var name: String)
你可以创建一个元组包含三种不同的类型:
val t = (11, "Eleven", new Person("Eleven"))
你可以通过序号访问元组中的元素:
t._1
t._2
t._3
还可以将元组字段分配给变量:
val (num, string, person) = (11, "Eleven", new Person("Eleven"))
元组对于那些需要把一些小“包”先暂时放在一起的时候是很有用的。
还没有展示的
虽然上面已经介绍了不少,但是还有很多内容没有介绍:
- 字符串和内置数值类型
- 包和导入
- 如何在Scala中使用Java集合类
- 如何在Scala中使用Java库
- 如何构建Scala项目
- 如何在Scala中执行单元测试
- 如何编写Scala shell 脚本
- Map,Set 和其他集合类
- 面向对象编程
- 函数式编程
- 使用Future的并发
- 等等。。。。。。
如果你觉得看过的上面的不错,希望后面的也得到你的喜欢。
一点背景
Scala是由 Martin Odersky 创造,他曾在 Niklaus Wirth 手下学习,后者创建了Pascal和其他几种语言, Mr. Odersky 是 Generic Java的共同设计者之一,也被称为javac编译器之父。