3.1 把Scala当作简洁的Java

Scala拥有非常高的代码密度——键入更少却获得更多。我们从一段Java代码的例子开始:

ScalaForTheJavaEyes/Greetings.java

  1. //Java code
  2. public class Greetings {
  3. public static void main(String[] args) {
  4. for(int i = 1;i< 4; i++) {
  5. System.out.print(i + ",");
  6. }
  7. System.out.println("Scala Rocks!!!");
  8. }
  9. }

输出如下:

  1. 1,2,3,Scala Rocks!!!

Scala使上面的代码变得更简洁。首先,它并不关心是否使用分号。其次,在如此简单的例子里,把代码放到Greetings类里并没有什么真正的益处,因此,可以去掉。再次,没有必要指定变量i的类型。Scala很聪明,足以推演出i是一个整数。最后,Scala使用println输出字符串,不必键入System.out.println。把上面的代码简化成Scala,如下:

ScalaForTheJavaEyes/Greetings.scala

  1. for (i <- 1 to 3) {
  2. print(i + ",")
  3. }
  4. println("Scala Rocks!!!")

运行上面的Scala脚本,键入scala Greetings.scala,或是在IDE里运行。

你应该看到这样的输出:

  1. 1,2,3,Scala Rocks!!!

Scala的循环结构相当轻量级。只要说明索引i的值是从13即可。箭头(<-)左边定义了一个val,而不是var(参见下面的注解),右边是一个生成器表达式(generator expression)。每次循环都会创建一个新的val,用产生出来的连续值进行初始化。

val vs. var

不管是val还是var,都可以用来定义变量。用val定义的变量是不可变的,初始化之后,值就固定下来了。用var定义的变量是可变的,修改多少次都行。

这里的不变性指的是变量本身,而不是变量所引用的实例。比如说,如果写val buffer = new StringBuffer(),就不能把buffer指向其他的引用。但我们依然可以用诸如append()之类的方法来修改StringBuffer的实例。

另外,如果用val str = "hello"定义了一个String的实例,就不能再做修改了,因为String本身也是不可变的。要想让一个类的实例不可变,可以把它的所有字段都定义为val,然后只提供读取实例状态的方法,不提供修改的方法。

在Scala里,应该尽量优先使用val,而不是var;这可以提升不变性和函数式风格。

上面代码产生的范围包含了下界(1)和上界(3)。用until()方法替换to()方法,就可以从范围内排除上界。

ScalaForTheJavaEyes/GreetingsExclusiveUpper.scala

  1. for (i <- 1 until 3) {
  2. print(i + ",")
  3. }
  4. println("Scala Rocks!!!")

你会看到如下输出:

  1. 1,2,Scala Rocks!!!

是的,你没听错。我确实说to()是方法了。实际上,to()until()都是RichInt的方法①,这个类型是由Int隐式转换而来的,而Int是变量i的推演类型。这两个函数返回的是一个Range的实例。因此,调用1 to 3等价于1.to(3),但前者更优雅。在下面的注解中,我们会更多的讨论这一迷人的特性。

①我们会在3.2节“Java基本类型对应的Scala类”中讨论富封装器(rich wrapper)。


点和括号是可选的

如果方法有01个参数,点和括号是可以丢掉的。如果方法的参数多于一个,就必须使用括号,但是点仍然是可选的。你已经看到这样做的益处:a+b实际上是a.+(b)1 to 3实际上是1.to(3)

利用这样轻量级语法的优势,可以创建出读起来更自然的代码。比如,假设为类Car定义了一个turn()方法:

  1. def turn(direction: String) //…

用轻量级语法调用上面的方法,如下:

  1. car turn "right"

享受可选的点和括号,削减代码中的杂乱吧!

在上面的例子里,看上去是在循环迭代中给i重新赋了值。然而,i并不是一个var,而是一个val。每次循环都创建一个不同的val,名字叫做i。注意,我们不可能由于疏忽在循环里修改了i的值,因为i是不变的。这里,我们已经悄然向函数式风格迈进了一步。

使用foreach(),还可以用更贴近函数式的风格执行循环:

ScalaForTheJavaEyes/GreetingsForEach.scala

  1. (1 to 3).foreach(i => print(i + ","))
  2. println("Scala Rocks!!!")

输出如下:

  1. 1,2,3,Scala Rocks!!!

上面的例子很简洁,没有赋值。我们用到了Range类的foreach()方法。这个方法以一个函数值作为参数。所以,要在括号里面提供一段代码体,接收一个实参,在这个例子里面命名为i=>将左边的参数列表和右边的实现分离开来。