官方教程:Scala for Java Programmers
第一个示例
第一个示例是标准的Hello world程序:
object HelloWorld {
def main(args: Array[String]): Unit = {
println("Hello, world!")
}
}
Java程序员应该熟悉该程序的结构:它由一个main
方法组成,该方法以命令行参数(字符串数组)作为参数,方法的主体包括对预定义方法println
的一次调用。main
方法不返回值,因此其返回类型声明为Unit
(相当于Java的void
)。
Java程序员不太熟悉的是包含main
方法的object
声明。该声明引入了一个单例对象(singleton object),即具有单个实例的类。因此,上面的声明同时声明了一个名为HelloWorld
的类和该类的一个实例,也称为HelloWorld
。该实例是在第一次使用时按需创建的。
注意在这里main
方法并没有声明为static
,这是因为Scala中不存在静态成员(方法或字段)。Scala程序员在单例对象中声明这些成员,而不是定义静态成员。
编译该示例
使用Scala编译器scalac
编译该示例。scalac
将源文件作为参数,生成一个或多个标准Java类文件。
假设将上面的程序保存在HelloWorld.scala文件中,使用以下命令来编译:
$ scalac HelloWorld.scala
这将在当前目录下生成几个.class文件,其中一个名为HelloWorld.class。
运行该示例
编译完成后,可以使用scala
命令运行Scala程序。其用法与java
命令类似,并接受相同的选项(例如-classpath
)。使用以下命令来运行该示例:
$ scala HelloWorld
Hello, world!
与Java交互
Scala的优势之一是与Java代码交互非常容易。java.lang
包中的所有类都默认导入,而其他类则需要显式导入。
让我们看一个证明这一点的例子。我们希望获取当前日期并根据特定国家(例如法国)使用的约定来格式化。Java类库定义了强大的实用程序类,例如Date
和DateFormat
。由于Scala与Java无缝互操作,因此无需在Scala类库中实现等价类——可以简单地导入相应Java包中的类:
import java.util.{Date, Locale}
import java.text.DateFormat._
object FrenchDate {
def main(args: Array[String]): Unit = {
val now = new Date
val df = getDateInstance(LONG, Locale.FRANCE)
println(df format now)
}
}
Scala的import
语句与Java类似,但更加强大。可以从同一个包中导入多个类,方法是将它们括在花括号中(如第一行所示)。另一个区别是,在导入一个包或类的所有名称时,使用下划线(_)而不是星号(*),这是因为星号是一个有效的Scala标识符。第二行的import
语句导入了DateFormat
类的所有成员,因此静态方法getDateInstance
和静态成员LONG
直接可见。
最后一行展示了Scala语法的一个有趣的属性:接收一个参数的方法可以使用中缀语法。即df format now
等价于df.format(now)
。
值得注意的是,可以直接在Scala中继承Java类和实现Java接口。
一切皆对象
Scala是一种纯面向对象的语言,因为一切都是对象,包括数字或函数。在这方面与Java不同,因为Java区分原始类型(例如boolean
和int
)和引用类型。
数字是对象
由于数字是对象,它们也有方法。实际上,像1 + 2 * 3 / x
这样的算术表达式完全由方法调用组成,因为它等价于以下表达式:1.+(2.*(3)./(x))
(如上一节所述)。这也意味着+
、*
等是Scala中的有效标识符。
函数是对象
函数也是Scala
中的对象,因此可以将函数作为参数传递、存储在变量中、从其他函数返回。这种将函数作为值进行操作的能力是一种称为函数式编程的非常有趣的编程范式的基石之一。
作为一个非常简单的例子,说明为什么使用函数作为值很有用,考虑一个计时器函数,其目的是每秒执行一些操作。如何将要执行的操作传递给它?很合乎逻辑:作为一个函数。许多程序员应该熟悉这种非常简单的函数传递:它通常用于用户界面代码中,用于注册回调函数,这些函数在某些事件发生时被调用。
在下面的程序中,计时器函数oncePerSecond
接收一个回调函数作为参数,其类型写作() => Unit
,表示无参数无返回值的函数类型。该程序无限地每秒打印一句 “time flies like an arrow”。
object Timer {
def oncePerSecond(callback: () => Unit): Unit = {
while (true) { callback(); Thread sleep 1000 }
}
def timeFlies(): Unit = {
println("time flies like an arrow...")
}
def main(args: Array[String]): Unit = {
oncePerSecond(timeFlies)
}
}
匿名函数
在Scala中可以使用匿名函数,即没有名字的函数。下面的程序使用匿名函数代替timeFlies
函数:
object TimerAnonymous {
def oncePerSecond(callback: () => Unit): Unit = {
while (true) { callback(); Thread sleep 1000 }
}
def main(args: Array[String]): Unit = {
oncePerSecond(() => println("time flies like an arrow..."))
}
}
匿名函数使用右箭头=>
分隔参数表和主体。在该示例中,参数表为空,函数体与上面的timeFlies
相同。
类
Scala是一种面向对象的语言,因此具有类的概念。Scala中的类声明语法与Java接近。一个重要的区别是Scala中的类可以有参数(相当于构造函数参数)。下面的复数定义说明了这一点:
class Complex(real: Double, imaginary: Double) {
def re() = real
def im() = imaginary
}
Complex
类有两个参数,分别是复数的实部和虚部。创建Complex
类的实例时必须传递这些参数,例如:new Complex(1.5, 2.3)
。该类包含两个方法,称为re
和im
,分别用于访问这两个部分。
需要注意的是,这两个方法的返回类型没有明确给出。它将由编译器自动推断,编译器查看这些方法的右侧并推断它们都返回Double
类型的值。
编译器并不总是能够像这样推断类型,但没有一个简单的规则可以准确地知道何时能够推断。实际上,这通常不是问题,因为编译器在无法推断出未明确给出的类型时会报错。一个简单的规则:Scala初学者应该尝试省略看起来很容易从上下文中推断出来的类型声明,看编译器是否同意。一段时间后,程序员应该对何时省略类型以及何时显式指定有一个很好的感觉。
不带参数的方法
re
和im
方法的一个小问题是,调用时必须使用空括号,如下例所示:
object ComplexNumbers {
def main(args: Array[String]): Unit = {
val c = new Complex(1.2, 3.4)
println("imaginary part: " + c.im())
}
}