scala

学习 Scala 的过程中,注意到一些如下特点:

  1. Scala 中既有函数式编程,又有面向对象编程,由于需要统一两者,所以不可避免带来复杂性。
  2. Scala 是在 JVM 的基础上构建的语言因而也就不可避免的烙上 Java 的印记,不过 Scala 改进了不少 Java 的弊病。
  3. Scala 提供了 Actor 模型的有别于 Java 的并发机制,这种无锁的并发编程会更容易且更加健壮。
  4. Scala 似乎有意避免与 Java 在语法上的重叠,所以会看到很多不一样的语法。
  5. Scala 用了大量操作符函数,将基本类型的四则运算也设计成操作符函数,带来统一,但如果过度使用操作符定义各种会难以理解。

变量定义十分简单,并且可以定义可变变量和不可变变量,Scala 推荐的方式是尽最大可能使用不可变变量,这会减小程序状态管理的复杂度。Scala 会尽最大可能去进行类型推导,因而在 Scala 中可以省略很多类型声明。

val msg = "Hello, world!"
val msg: String = "string with type declare"
var greeting = "Hello, world!"

val 声明的变量不能再赋值给其它的对象,单如果对象本身可变的话,依然可以改变值的内容。Scala 中变量的类型在变量名之后,这似乎是有意与 Java 不相同,除此之外,Scala 中的数组元素用 () 获取,类型参数用 [] 围住,函数的重复参数用 * 表示,以及在 Scala 中语句末尾的 ; 是可选的,Scala 推荐的方式是不写分号

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 中若方法无参数调用时可以省略 ()

/* 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 表达。

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 能够自动推导类型。

def max(x: Int, y: Int): Int = { if (x > y) x else y }

Scala 中以 def 定义的函数并不是函数值,函数值以函数字面量的形式给出,可定义为变量或者传入函数中。

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 称之为部分应用函数。占位符还以用于定义函数值,直接将 _ 代替函数参数,多个下划线只带多个参数,而不是单个参数重复使用。

somNumbers.filter(_ > 0)
val f = (_ : Int) + (_ : Int)

Scala 中的基本类型也是类,不过编译之后又变回 Java 中的基本类型值,值得注意的是 Scala 中的 String 基本类型就是 Java 中的 String 类型。Scala 可以在基本类型中调用函数,这是通过隐式转换将基本类型转成富包装器。每个基本类型都对应一个富包装器。隐式转换是通过 implicit 方法实现的,当某个代码点需要的类型和所提供的类型不一致时,并查找隐式函数进行转换。

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 中常见的那些操作符。操作符的优先级是按照第一个操作符字符进行判断的,操作符的结合性由最后一个字符决定, : 字符结尾的操作符是右结合的,其它的都是左结合的。

a * b --> a.*(b)
a:::b --> b.:::(a)

//操作符优先级
(所有其他的特殊字符)
* / %
+ -
:
= !
<>
&
^
|
(所有字母)
(所有赋值操作符)

Scala 比 Java 更为面向对象的特点之一是 Scala 不能定义静态成员,而是代之以伴生对象。所有伴生对象上的方法类似于 Java 中类的静态方法,且可以访问其伴生类的所有私有定义,反过来也是如此。Scala 推荐的创建对象的方法是通过伴生对象的工厂方法生成。

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 为名字的辅助构造函数,不过任何一个辅助构造函数最终都必须调用主构造函数。

class Rational(n: Int, d: Int) {
  require(d != 0)
  println("Created " + n + "/" + d)
  def this(n: Int) = this(n, 1)
}

若调用 Scala 对象的方法,且方法仅有一个参数时,可以省略 . 以及 () 看起来就像英语的句子一样,这样做看起来和以操作符为方法名的方法调用就统一了。

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 调用是从后向前的,所以书写顺序会变得很重要。

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 的函数具有柯里化的特点,柯里化的函数可以被用于多个参数列表,而不是一个。当以占位符 _ 的形式调用函数将会产生一个接收单一参数的函数值。

def curriedSum(x: Int)(y: Int) = x + y
val twoPlus = curriedSum(2)_       //此函数值的类型是 (Int) => Int

Scala 还有许多有趣的方面,比如类似于 Java 的类型参数、抽象成员、模式匹配、抽取器等,此处也没有介绍 Scala 的类型层次,集合的众多用法。如果有兴趣可以直接读<Scala 编程>一书。

Leave a Reply

Your email address will not be published. Required fields are marked *