Scala与Java语法差异

2020-03-06 16:26:19 | 编辑 | 添加

1.=连续赋值的结果

在Java 中,赋值操作(像a = b)的值就是a 的值,因此像x = a = b 这样的多重赋值就可以出现,但是在Scala 中不能这样做。在Scala 中赋值操作的结果值是一个Unit—大概等价于一个Void。从结果上讲,将这种值赋值给另外一个变量有可能造成类型不匹配。

看一看下面这个例子。

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

当我们试着执行前面的代码时,就会得到如下编译错误:

SerialAssignments.scala:4: error: type mismatch;
found : Unit
required: Int
a = b = 3 //编译错误
one error found

这种表现行为至少有那么一点儿恼人。


2.Scala 的==

Java 的==对原始类型和对象有着不同的含义。对于基本类型,==意味着基于值的比较,而对于对象,它意味着基于个体身份(即引用)的比较。所以,如果a 和b 都是int,那么若两个变量的值相等,a==b 就是true。但是,如果它们都是对象的引用,a==b 为true当且仅当两个引用指向同一个实例,也就是说,两者是同一个身份。Java 的equals()方法提供了对象间基于值的比较,前提是相应的类对它做了正确的重载。


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


对于基于值的比较,在 Scala 中,可以使用简洁的==而不是equals()方法。如果要对引用做基于身份的比较,那么可以使用Scala 中的eq()方法。我们来看一个使用了这两种比较方法的例子。


val str1 = "hello"
val str2 = "hello"
val str3 = new String("hello")
println(str1 == str2) // 等价于Java 的str1.equals(str2)
println(str1 eq str2) // 等价于Java 的 str1 == str2
println(str1 == str3)
println(str1 eq str3)

str1 和str2 都指向同一个String 实例,因为Java 不会为第二个字符串字面量"hello"创建新的对象。然而,str3 指向另一个新建的String 实例。这3 个引用指向的对象所拥有的值是相等的,都是"hello"。因为str1 和str2 就是同一个对象(即引用相等),所以它们的值也相等。然而,str1 和str3 只是在值上相等,并不指向同一个对象。

下面的输出结果展示了上面的代码中使用==和eq 方法或操作符的语义:

true
true
true
false


Scala 对==的处理对于所有类型的行为都是一致的,避免了在Java 中使用==的语义混乱。但是,你必须注意到这和Java 中的语义相差很大,以避免可能的失误。


3.可有可无的分号

在涉及语句或者表达式的终止时,Scala 很厚道—分号(;)是可选的,这就能够减少代码中的噪声。我们可以在语句或者表达式的末尾放置一个分号,特别是,如果想要在同一行上放置多个语句或者表达式的话,但一定要小心。在同一行上写多个语句或者表达式可能会降低代码的可读性,就像下面这个例子:

val sample = new Sample; println(sample)

如果一行的末尾没有以一个中缀标记(如+、*、. )结尾,且不在括号或者方括号中,那么Scala 会自动补上分号。如果下一行的起始处能够开始一个语句或者表达式,那么这一行的末尾也会自动补上分号。

然而,Scala 在某些上下文中要求在{前有一个分号。如果没有写分号,那么最终效果可能会让人吃惊。让我们来看一个例子。

val list1 = new java.util.ArrayList[Int];
{
println("Created list1")
}
val list2 = new java.util.ArrayList[Int] {
println("Created list2")
}
println(list1.getClass)
println(list2.getClass)

输出结果如下:

Created list1
Created list2
class java.util.ArrayList
class Main$$anon$2$$anon$1

我们在定义 list1 的时候放置了一个分号,因此,紧随其后的{开启了一个新的代码块。然而,因为我们在定义list2 的时候没有写分号,所以Scala 会假定我们是在创建一个继承自ArrayList[Int]的匿名内部类。因此,list2 指向一个匿名内部类的实例,而不是ArrayList[Int]的一个实例。因此,如果是想在创建一个实例之后新建一个代码块,就要写上分号。


Java 强制写分号,但是Scala 给了是否使用分号的自由—要用好这个特性。没有那些分号,代码会变得简洁且噪声更少。不使用分号,就能开始享受Scala 优雅而轻量的语法。像前面所提到的例子那样,必须使用分号以避免潜在的歧义时要保留分号。


4.避免显式return

在Java 中,我们使用return 语句从方法返回结果,而这在Scala 中却不是一个好的实践。return 语句在Scala 中是隐式的,显式地放置一个return 命令会影响Scala 推断返回类型的能力。看一个例子。

def check1 = true
def check2: Boolean = return true
def check3: Boolean = true
println(check1)
println(check2)
println(check3)

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


即使你选择提供返回类型,也最好避免显式的 return 命令,check3()方法就是一个很好的示范—代码不嘈杂,然后你就会习惯Scala 中的惯例—最后一个表达式的结果将会自动被返回。


5.变量定义

在前面的代码中,我们使用了val。我们可以使用val 或var 定义变量。使用val 定义的变量是不可变的,即初始化后不能更改。然而,那些使用var 定义(不推荐使用)的变量是可变的,可以被改任意次。

不可变性(immutability)是作用在变量上,而不是作用在变量所引用的实例上的。例如,如果我们编写了val buffer = new StringBuffer(),就不能改变buffer 的引用。但是,我们可以使用StringBuffer 的方法(如append()方法)来修改所引用的实例。故而,对于一个只有val 引用的对象,不能假定它是完全不可变的。


另一方面,如果我们使用不可变类如String 定义了一个变量,如val str = "hello",就既不能改变引用也不能改变引用所指向的实例的状态。

使用 val 定义所有的字段,并且只提供允许读但不允许更改实例状态的方法,就可以使一个类的实例不可变。

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


6.Scala没有static

Scala 没有static 关键字,直接在一个类中允许static 字段和static 方法会破坏Scala 提供的纯面向对象模型。与此同时,Scala 通过单例对象和伴生对象完整支持类级别的操作和属性。

关于单例对象和伴生对象已经在Scala 类和对象的定义声明 做过讲解。