scala
学习 Scala 的过程中,注意到一些如下特点:
- Scala 中既有函数式编程,又有面向对象编程,由于需要统一两者,所以不可避免带来复杂性。
- Scala 是在 JVM 的基础上构建的语言因而也就不可避免的烙上 Java 的印记,不过 Scala 改进了不少 Java 的弊病。
- Scala 提供了 Actor 模型的有别于 Java 的并发机制,这种无锁的并发编程会更容易且更加健壮。
- Scala 似乎有意避免与 Java 在语法上的重叠,所以会看到很多不一样的语法。
- Scala 用了大量操作符函数,将基本类型的四则运算也设计成操作符函数,带来统一,但如果过度使用操作符定义各种会难以理解。
变量定义十分简单,并且可以定义可变变量和不可变变量,Scala 推荐的方式是尽最大可能使用不可变变量,这会减小程序状态管理的复杂度。Scala 会尽最大可能去进行类型推导,因而在 Scala 中可以省略很多类型声明。
1
2
3
|
val msg = "Hello, world!"
val msg: String = "string with type declare"
var greeting = "Hello, world!"
|
val 声明的变量不能再赋值给其它的对象,单如果对象本身可变的话,依然可以改变值的内容。Scala 中变量的类型在变量名之后,这似乎是有意与 Java 不相同,除此之外,Scala 中的数组元素用 () 获取,类型参数用 [] 围住,函数的重复参数用 * 表示,以及在 Scala 中语句末尾的 ; 是可选的,Scala 推荐的方式是不写分号
1
2
3
4
5
6
7
8
9
|
val greetStrings = new Array[String](3)
greetStrings(0) = "Hello"
greetStrings(1) = ","
greetStrings(2) = "world!\n"
for (i <- 0 to 2)
println(greetStrings(i))
def echo(args: String*) =
for (arg <- args) println(arg)
|
Scala 中的除了 while 控制结构外,if、for、try、match 结构都是可以产生值的,在 Scala 中将它们称为表达式。在 Scala 中若方法无参数调用时可以省略 ()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
/* if 表达式 */
var filename = if (!arg.isEmpty) args(0) else "default.txt"
/* for 表达式 */
def scalaFiles =
for {
file <- filesHello
if file.getName.endsWith(".scala")
} yield file
/* match 表达式 */
val firstArg = if (args.length > 0) args(0) else ""
firstArg match {
case "salt" => println("pepper")
case "chips" => println("salsa")
case "eggs" => println("bacon")
case _ => println("huh?")
}
|
其中 for 表达式最为重要,它会被编译器转译为由 map、flatMap 及 filter 表达。
1
2
3
4
5
6
7
8
9
|
for (x <- expr1) yield expr2
将被转译为
expr1 .map(x => expr2)
for (x <- expr1; if expr2) yield expr3
将被转译为
for (x <- expr1; filter(x => expr2) yield expr3
最终得到
expr1 filter(x => expr2) map (x => expr3)
|
函数定义以 def 开头,在方法签名后边以等号连接方法体。Scala 函数可以不需要 return 语句,方法体的最后一个表达式的值自动作为函数的返回值,大部分时候不要写返回值的类型, Scala 能够自动推导类型。
1
|
def max(x: Int, y: Int): Int = { if (x > y) x else y }
|
Scala 中以 def 定义的函数并不是函数值,函数值以函数字面量的形式给出,可定义为变量或者传入函数中。
1
2
3
4
5
|
var increase = (x: Int) => x + 1
args.foreach((arg: String) => println(arg))
def sum(a: Int, b: Int, c: Int) = a + b + c
val sumValue = sum _
|
可以通过使用占位符的形式将一个定义的函数转为函数值,Scala 称之为部分应用函数。占位符还以用于定义函数值,直接将 _ 代替函数参数,多个下划线只带多个参数,而不是单个参数重复使用。
1
2
|
somNumbers.filter(_ > 0)
val f = (_ : Int) + (_ : Int)
|
Scala 中的基本类型也是类,不过编译之后又变回 Java 中的基本类型值,值得注意的是 Scala 中的 String 基本类型就是 Java 中的 String 类型。Scala 可以在基本类型中调用函数,这是通过隐式转换将基本类型转成富包装器。每个基本类型都对应一个富包装器。隐式转换是通过 implicit 方法实现的,当某个代码点需要的类型和所提供的类型不一致时,并查找隐式函数进行转换。
1
2
3
4
5
|
val bigger = 0 max 5
val range = 4 to 6
implicit def doubleToInt(x: Double) = x.toInt
val i: Int = 3.5
|
Scala 中可以定义许多的操作符,C/C++/Java 中常见的那些操作符。操作符的优先级是按照第一个操作符字符进行判断的,操作符的结合性由最后一个字符决定, : 字符结尾的操作符是右结合的,其它的都是左结合的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
a * b --> a.*(b)
a:::b --> b.:::(a)
//操作符优先级
(所有其他的特殊字符)
* / %
+ -
:
= !
<>
&
^
|
(所有字母)
(所有赋值操作符)
|
Scala 比 Java 更为面向对象的特点之一是 Scala 不能定义静态成员,而是代之以伴生对象。所有伴生对象上的方法类似于 Java 中类的静态方法,且可以访问其伴生类的所有私有定义,反过来也是如此。Scala 推荐的创建对象的方法是通过伴生对象的工厂方法生成。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class Rocket {
import Rocket.fuel
private def canGoHomeAgain = fuel > 20
}
object Rocket {
private def fuel = 10
def chooseStrategy(rocket: Rocket) {
if (rocket.canGoHomeAgain)
goHome()
else
pickAStar()
}
def goHome() {}
def pickAStar() {}
}
|
Scala 定义类的大括号之间的代码都会收集到类的主构造函数中,并在类被初始化时执行,并且类名后的参数称为类参数,在初始化需要提供给主构造函数。除了主构造函数还可定义 this 为名字的辅助构造函数,不过任何一个辅助构造函数最终都必须调用主构造函数。
1
2
3
4
5
|
class Rational(n: Int, d: Int) {
require(d != 0)
println("Created " + n + "/" + d)
def this(n: Int) = this(n, 1)
}
|
若调用 Scala 对象的方法,且方法仅有一个参数时,可以省略 . 以及 () 看起来就像英语的句子一样,这样做看起来和以操作符为方法名的方法调用就统一了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
class Wire {
private var actions: List[Action] = List()
def addAction(a: Action) = {
actions = a :: actions
}
}
a1 addAction andAction /* 相当于 a1.addAction(andAction) */
class Rational(n: Int, d: Int) {
private val number = n
private val denom = d
def +(that: Rational): Rational =
new Ration(
number * that.denom + that.number * denom,
denom * that.denom
)
}
val x = new Rational(3, 4)
val y = new Rational(5, 9)
x + y /* 相当于 x.+(y) */
|
Scala 具有与 Java 不同的混入方式——特质,特质类似于 Java 的接口,但可以提供实现, Scala 中用 with 来继承多个特质。特质具有一个特点就是其 super 调用是从后向前的,所以书写顺序会变得很重要。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
abstract class IntQueue {
def get(): Int
def put(x: Int)
}
import scala.collection.mutable.ArrayBuffer
class BasicIntQueue extends IntQueue {
private val buf = new ArrayBuffer[Int]
def get() = buf.remove(0)
def put(x: Int) { buf += x }
}
trait Incrementing extends IntQueue {
abstract override def put(x: Int) { super.put(x + 1) }
}
trait Doubling extends IntQueue {
abstract override def put(x: Int) { super.put(2 * x) }
}
val v = new BasicIntQueue with Incrementing with Doubling
v.put(3)
v.get() //--> 得到(3*2+1)=7
|
Scala 的函数具有柯里化的特点,柯里化的函数可以被用于多个参数列表,而不是一个。当以占位符 _ 的形式调用函数将会产生一个接收单一参数的函数值。
1
2
|
def curriedSum(x: Int)(y: Int) = x + y
val twoPlus = curriedSum(2)_ //此函数值的类型是 (Int) => Int
|
Scala 还有许多有趣的方面,比如类似于 Java 的类型参数、抽象成员、模式匹配、抽取器等,此处也没有介绍 Scala 的类型层次,集合的众多用法。如果有兴趣可以直接读<Scala 编程>一书。