跳转至

第3章 从Java到Scala

第3章 从Java到Scala

3.1 Scala:简洁的Java

减少样板代码

Scala具有非常高的代码密度——输入少量代码就可以完成许多功能。作为对比,来看一个Java代码的例子

public class Greetings {
    public static void main(String[] args) {
        for (int i = 1; i < 4; i++) 
            System.out.println(i + ",");
    }
    System.out.println("Scala Rocks!!!");
    }
}

使用Scala可以省去这段代码不少东西

  • 可以不使用分号
  • 不用把代码写在类Greetings
  • 不需要指定变量i的类型
  • 可以使用println而不使用System.out.前缀

下面是Java代码使用Scala简化后的代码。箭头(<-)的左边定义了一个val,右边是一个生成器表达式。每次迭代都会创建一个新的val,并使用所生成的值的元素相继对其进行初始化。

1,2,3,Scala Rocks!!!

使用val定义的变量是不可变的,即初始化后不能更改。然而,那些使用var定义的变量是可变的

不可变性(immutability)是作用在变量上,而不是作用在变量所引用的实例上的

例如,如果编写了val buffer = new StringBuffer(), 就不能改变buffer的引用。但是,可以使用StringBuffer.append()方法来修改所引用的实例。

sb: StringBuffer = a
res1: StringBuffer = a

在Scala中,应尽可能多地使用val,因为它可以提升不可变性,从而减少错误,也可以增益函数式风格。

3.2 原始类型对应的Scala类

和Java不同,Scala将所有的类型都视为对象。这就意味着,和调用对象上的方法一样,也可以在字面量上进行方法调用。

1.to(3)或者1 to 3中,需要用类似Java中自动装箱(autoboxing)的机制,以便可以在Int上调用to()方法。因为Int不能直接处理这种请求,所以Scala会自动应用intWrapper()方法将Int转换为scala.runtime.RichInt,然后调用RichInt上的to()方法。

诸如RichInt, RichDoubleRichBoolean这些类,可称为富包装类(rich wrapper class)。它们为Scala中的Java原始类型和String提供了便于使用的方法。

3.3 元祖和多重赋值

Scala的元组是一个不可变的对象序列,创建时使用逗号分隔。例如, ("Venkat", "zhenhua", "hello")就是表示一个3个对象的元组。可以将元组中的多个元素同时赋值给多个val或者var(多重赋值)。

firstName: String = Venkat
lastName: String = Subramaniam
emailAddress: String = venkats@agiledeveloper.com

也可以将元组整体赋值给一个变量,然后采用下划线加数字._i这种语法形式访问其中的元素。数字i表示在元组中元素的索引,元组的索引从1开始。

Venkat

3.4 灵活的参数和参数值

3.6 字符串和多行原始字符串

Scala中的字符串就是java.lang.String。可以用Java的方式使用String。Scala能够自动将String转化为scala.runtime.RichString。这种转化给String新增了一些有用的方法,如capitalize(), lines()reverse()方法。

s: String = Good

可以用一对3个双引号("""..."""), 创建一个跨行的字符串。Scala将3个双引号中间的内容保持原样,在Scala中这种字符串被称为原始字符串

val str = """In his famous inaugural speech, John F. Kennedy said "And so, my fellow Americans: ask not what your country can do speak to the citizens of the World..."""

3.7 字符串插值

在双引号前面的s的意思是s插值器(s-interpolator), 它会找到字符串中的表达式,并将其替换成对应的值。

discount: Int = 90
message: String = A discount of 90% has been applied

如果表达是是最简单的一个变量,那么在它的前面加上美元符号$。而对于更复杂的表达式,可以把它们放在大括号中。

price: Int = 90
totalPrice: String = The amount of discount is 81 dollars

美元符号被用作表达式的分隔符,如果说字符串中正好有一个$符号,那么其还可以被用作转义符。

totalPrice: String = The amount of discount is $81

s插值器只是用值去替换表达式,而不做任何格式处理。下面的表达式正确求值了,但是输出结果中小数点后有3位小数:

On ticket 10% save $2.512

为了对输出做格式化,而不只是插值,可以使用f插值器(f-interpolator)。字符串的格式化和Java中的printf函数遵循相同的规则,只是还可以和前面的例子一样嵌入表达式。改写前面的println语句,在表达式后面带上格式2.2f。注意,需要用一个额外的%转义已有的那个百分号。我们没有在product或者discount变量后放置任何格式相关的额符号,尽管我们本可以放相应的%s和%d。如果没有指定格式,那么f插值器将会假定格式是%s,也就是说直接转化为字符串。

On ticket 10% saves $2.51

3.9 操作符重载

技术上说,Scala没有操作符,所以操作符重载的意思是重载诸如+、-等符号。在Scala中,这些都是方法名。操作符利用了Scala宽松的方法调用语法––Scala不强制在对象引用和方法名中间使用点号(.)。

这两个特性结合在一起就给人一种操作符重载的幻觉。因此,当调用ref1+ref2,实际上是ref1.+(ref2),是在ref1上面调用+()方法。 下面的Complex类表示复数,提供了+操作符。Complex类定义了一个接收两个参数的构造器。

(1+2i) + (2-3i) = 3-1i

Scala没有操作符,所以没有在操作符上定义优先级,但是它在方法上定义了优先级。方法的第一个字符决定了优先级。如果在一个表达式中两个字符的优先级相同,那么在左边的方法优先级更高。下面是第一个字符的优先级从低到高的列表:

所有字符

|
^
&
<>
= !
:
+ -
* / %
其他特殊字符

在下面的代码中,我们在Complex中定义了加方法和乘方法。

Calling *
Calling +
-1-6i

3.10 Scala与Java的差异

赋值的结果

在Java中,赋值操作(像a=b)的返回值就是a的值,因此像x=a=b这样的连续赋值就可以出现,但是在Scala中不能这样做。在Scala中赋值操作的结果值是一个Unit———大概等价于一个Void

var a = 1
var b = 2
a = b = 3 //编译错误
Scala的==

Java的==对基本类型和引用类型有着不同的含义。对于基本类型,==意味着基于值的比较,对于引用类型,它意味着基于引用的比较。

Scala对==的处理和Java不同,它对所有类型都是一致的。在Scala中,==表示基于值的比较,而不论类型是什么。这是在类Any(Scala中所有类型都衍生自Any)中实现了final==()方法保证的。

如果要比较引用是否指向同一个对象,可以使用Scala中的eq()方法。

true
true
true
false

可有可无的分号

在涉及语句或者表达式的终止时,Scala很厚道——分号(;)是可选的,这就能够减少代码中的噪声。然而,Scala在某些上下文中要求在{前面有一个分号。

Created list1
Created List2
class java.util.ArrayList
class $line40.$read$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$anon$1

在定义list1的时候放置了一个分号,因此,紧随其后的{开启了一个新的代码块。然后,在定义list2的时候没有写分号,所以Scala会假定是在创建一个继承自ArrayList[Int]的匿名内部类。 避免显示return

在Java中,使用return语句从方法返回结果。而return语句在Scala中是隐式的,显式地放置一个return命令会影响Scala推断返回类型的能力。

在下面的代码中,Scala非常愉快地推断出了check1()方法地返回类型。但是由于在方法check2()中使用了一个显式的return,所以Scala没有推断出类型。在这种情况下,必须提供返回类型Boolean

即使你选择提供返回类型,也最好避免显式的return命令。check3方法就是一个很好的示范。

3.11 默认访问修饰符

在不使用任何访问修饰符的情况下,Scala默认类、字段和方法都是public的。

Started

Java的protected对任意包中的派生类和当前包的任意类是可见的。然而Scala的protected与C++类似,只有派生类能够访问。更近一步,派生类在访问protected成员的时候,成员的类型也需要一致。