Scala Tutorial中英对照:和Java交互,一切皆对象

这个文档,是翻译自官方的Scala Tutorial ,为了防止自己理解错误,保留原文以便参照。

由于篇幅所限,共分成三篇。
博客中格式不好调整,感兴趣的朋友可以下载PDF版本:http://download.csdn.net/source/1742639

1 介绍 Introduction

本文档是Scala语言和编译器的快速入门介绍,适合已经有一定编程经验,且希望了解Scala可以做什么的读者。我们假定本文的读者具有面向对象编程(Object-oriented programming,尤其是java相关)的基础知识。

(注意,本文档只是让读者对Scala有个概念,不是系统学习的材料)

This document gives a quick introduction to the Scala language and compiler. It

is intended for people who already have some programming experience and want

an overview of what they can do with Scala. A basic knowledge of object-oriented

programming, especially in Java, is assumed.

2 第一个例子 A first example

我们使用最经典的“Hello world”作为第一个例子,这个例子虽然并不是特别炫(fascinating),但它可以很好的展示Scala的用法,且无须涉及太多的语言特性。示例代码如下:

As a first example, we will use the standard Hello world program. It is not very fascinatingbut makes it easy to demonstrate the use of the Scala tools without knowing too much about the language. Here is how it looks:

object HelloWorld { def main(args: Array[String]) { println("Hello, world!") } }

Java程序员应该对示例代码的结构感到很熟悉:它包含一个main方法,其参数是一个字符串数组,用来接收命令行参数;main的方法体只有一句话,调用预定义的println方法输出“Hello world!”问候语。main方法不返回值(这是一个过程方法procedure method),因此,该方法不必声明返回值类型。

The structure of this program should be familiar to Java programmers: it consists of one method called main which takes the command line arguments, an array of strings, as parameter; the body of this method consists of a single call to the predefined method println with the friendly greeting as argument. The main method does not return a value (it is a procedure method). Therefore, it is not necessary to declare a return type.

对于包含main方法的object 声明,Java程序员可能要相对陌生一些。这种声明方式引入了一个通常被称为单例对象(singleton object)的概念,也就是有且仅有一个实例的类。因此,上例中的声明,在定义了一个名为的HelloWorld类的同时,还声明了该类的一个实例,实例的名字也叫HelloWorld。该实例在第一次被用到的时候即时(on demand)创建。

What is less familiar to Java programmers is the object declaration containing the main method. Such a declaration introduces what is commonly known as a singleton object , that is a class with a single instance. The declaration above thus declares both a class called HelloWorld and an instance of that class, also called HelloWorld. This instance is created on demand, the first time it is used.

细心(astute,机敏的,聪明的)的读者可能会注意到,main方法并没有声明为static。这是因为Scala中不存在静态成员(无论方法还是属性,methods or fields)这一概念,Scala使用前述的单例对象(singleton objects)中的成员来代替静态成员。

The astute reader might have noticed that the main method is not declared as static here. This is because static members (methods or fields) do not exist in Scala. Rather than defining static members, the Scala programmer declares these members in singleton objects.

2.1 编译该示例 Compiling the example

要编译上面写的例子,要scalac命令,这就是Scala的编译器。scalac的工作流程和多数编译器类似:从命令行上接收待编译的源文件名(source file)以及编译参数,生成一个或者多个目标文件(object files,或者叫对象文件)。Scala生成的目标文件是标准的java class文件。

To compile the example, we use scalac, the Scala compiler. scalac works like most compilers: it takes a source file as argument, maybe some options, and produces one or several object files. The object files it produces are standard Java class files.

假如我们将HelloWorld示例程序存放到HelloWorld.scala文件中,则可以用以下指令进行编译(大于号’>’表示shell/命令行 的提示符,不需要人工键入):

If we save the above program in a file called HelloWorld.scala, we can compile it by issuing the following command (the greater-than sign ‘>’ represents the shell prompt and should not be typed):

> scalac HelloWorld.scala

该指令执行后,会在当前目录下生成几个class文件,其中一个是HelloWorld.class,该文件中包含一个可以直接被scala指令执行的类(class),具体操作参见后续章节。

This will generate a few class files in the current directory. One of them will be called HelloWorld.class , and contains a class which can be directly executed using the scala command, as the following section shows.

2.2 运行该示例 Running the example

代码编译通过以后,可以使用scala指令运行程序,scala指令和java指令的用法非常相似,甚至它们接受的命令行参数都是一样的。前面编译好的例子,可以用如下指令运行,并输出预期的问候语:

Once compiled, a Scala program can be run using the scala command. Its usage is very similar to the java command used to run Java programs, and accepts the same options. The above example can be executed using the following command, which produces the expected output:

> scala -classpath . HelloWorld

Hello, world!

3 Java 进行交互 Interaction with Java

和Java代码的交互能力,是Scala语言的强项之一。在Scala程序中,java.lang包下的类是默认全部引入的,其它包下的类则需要显式(explicitly)引入。

One of Scala’s strengths is that it makes it very easy to interact with Java code. All classes from the java.lang package are imported by default, while others need to be imported explicitly.

我们可以通过一个例子来展示Scala与Java的交互能力。假如,我们想获取系统当前时间,并按照某个国家(比如法国)的显示习惯进行格式化[1]

Let’s look at an example that demonstrates this. We want to obtain and format the current date according to the conventions used in a specific country, say France 1 .

我们知道,在Java的类库中已经实现了Date、DateFormat等功能强大的工具类,且Scala可以和Java进行无缝(seemlessly)的互操作,所以,想在Scala程序中使用这些功能,只需要引入这些Java类即可,无须从头重复实现相同的功能。

Java’s class libraries define powerful utility classes, such as Date and DateFormat. Since Scala interoperates seemlessly with Java, there is no need to implement equivalent classes in the Scala class library–we can simply import the classes of the corresponding Java packages:

import java.util.{Date, Locale}

import java.text.DateFormat

import java.text.DateFormat._

object FrenchDate {

def main(args: Array[String]) {

val now = new Date

val df = getDateInstance(LONG, Locale.FRANCE)

println(df format now)

}

}

Scala的import语句和Java中的import很想象,但Scala的语法更强大一些。比如,要想引入一个包中的多个类,在Scala中可以写在一行上,只需要把多个类名放到一个大括号中(curly braces, {})即可。此外,如果要引入一个包或者类中的所有名字,Scala使用下划线(underscore,_)而不是星号(asterisk,*),这是因为,在Scala中,星号是一个合法的标识符(比如:方法名),后面我们会遇到这种情况。

Scala’s import statement looks very similar to Java’s equivalent, however, it is more powerful. Multiple classes can be imported from the same package by enclosing them in curly braces as on the first line. Another difference is that when importing all the names of a package or class, one uses the underscore character (_) instead of the asterisk (*). That’s because the asterisk is a valid Scala identifier (e.g. method name), as we will see later.

因此,上例中第三行的import语句,引入了DateFormat类的所有成员,这样该类的静态方法getDateInstance和静态属性LONG对FrenchDate类来说,是直接可见的(directly visible)。

The import statement on the third line therefore imports all members of the DateFormat class. This makes the static method getDateInstance and the static field LONG directly visible.

在main方法中,我们首先创建一个Java的Date实例,该实例默认取得系统当前时间;接下来,我们使用从DateFormat类中引入的静态方法getDateInstance创建一个负责日期格式化的对象df,创建过程中,通过参数指定了本地化区域(Locale.FRANCE);最后,使用df将当前时间进行格式化并打印输出到控制台。这个方法的最后一行,体现了Scala语法中一种很有意思的特性(interesting property):如果一个方法只接受一个参数,那么可以使用infix语法,也就是说,下面的表达式:

df format now

和df.format(now)的语义完全相同,只是前者更加简洁。

Inside the main method we first create an instance of Java’s Date class which by default contains the current date. Next, we define a date format using the static getDateInstance method that we imported previously. Finally, we print the current date formatted according to the localized DateFormat instance. This last line shows an interesting property of Scala’s syntax. Methods taking one argument can be used with an infix syntax. That is, the expression

df format now

is just another, slightly less verbose way of writing the expression

df.format(now)

这虽然是一个很小的语法细节,但它具有深远的影响,本文后续的章节中将会有进一步的论述。

This might seem like a minor syntactic detail, but it has important consequences, one of which will be explored in the next section.

最后,从Java与Scala整合的角度看,值得一提的是,Scala中可以直接继承Java的类或者实现Java的接口。

To conclude this section about integration with Java, it should be noted that it is also possible to inherit from Java classes and implement Java interfaces directly in Scala.

4 一切皆对象 Everything is an object

Scala中的一切都是对象,从这个意义上说,Scala是纯粹的面向对象(pure object-oriented)的语言。在这一点上,Scala与Java不同,因为Java中,原子类型(primitive types)和引用类型是有区别的,而且Java中不能把函数(function)当做值(value)来对待。

Scala is a pure object-oriented language in the sense that everything is an object, including numbers or functions. It differs from Java in that respect, since Java distinguishes primitive types (such as boolean and int) from reference types, and does not enable one to manipulate functions as values.

4.1 数字是对象 Numbers are objects

因为数字是对象,所以数字也拥有自己的方法,如下的算术表达式:

1+2*3/x

实际上完全是由方法调用(method calls)构成的。前面章节已经提到过“单参数方法”的简化写法,所以,上述表达式实际上是下面这个表达式的等价简化写法:

(1).+(((2).*(3))./(x))

Since numbers are objects, they also have methods. And in fact, an arithmetic expression like the following:

1+2 3/x*

consists exclusively of method calls, because it is equivalent to the following expression, as we saw in the previous section:

(1).+(((2).*(3))./(x))

由此我们还可以看到:+, *等符号在Scala中是合法的标识符(和前面进行印证)。

This also means that +, *, etc. are valid identifiers in Scala.

在第二种写法中,加在数字上的括号是必须的,因为Scala的词法分析器使用贪婪算法(longest match,最长匹配)来匹配符号,所以,表达式:

1.+(2)

将被解释成:1.、+和2三个符号。虽然“1.”和“1”都是合法的符号,但“1.”的长度更长,所以被Scala的词法分析器优先选择,而“1.”等价于字面值(literal)“1.0”,这将产生一个Double浮点数而不是我们期望的Int整数。所以必须在数字上面加括号,就像这样:

(1).+(2)

以避免整数1被解释成Double类型。

The parentheses around the numbers in the second version are necessary because Scala’s lexer uses a longest match rule for tokens. Therefore, it would break the following expression:

1.+(2)

into the tokens 1., +, and 2. The reason that this tokenization is chosen is because 1. is a longer valid match than 1. The token 1. is interpreted as the literal 1.0, making it a Double rather than an Int. Writing the expression as:

(1).+(2)

prevents 1 from being interpreted as a Double.

4.2 函数是对象 Functions are objects

在Scala中,函数(functions)也是对象(objects),所以,函数可以当做参数进行传递,可以把函数存储在变量中,也可以把函数作为其他函数的返回值,Java程序员可能会觉得这是一项非常神奇的特性。这种将函数当做值进行操作的能力,是函数式编程(functional programming)最重要的特性(cornerstone,基石)之一。

Perhaps more surprising for the Java programmer, functions are also objects in Scala. It is therefore possible to pass functions as arguments, to store them in variables, and to return them from other functions. This ability to manipulate functions as values is one of the cornerstone of a very interesting programming paradigm called functional programming .

举一个简单的例子,就可以说明把函数当做值来操作的意义何在。假如我们要开发一个定时器,该定时器每秒钟执行一定的动作,我们如何把要执行的动作传给定时器?最直观的回答是:传一个实现动作的函数(function)。许多程序员,对这种函数传递模式并不陌生:在用户界面(user-interface)相关代码中,当事件被触发时,会调用预先注册的回调函数。

As a very simple example of why it can be useful to use functions as values, let’s consider a timer function whose aim is to perform some action every second. How do we pass it the action to perform? Quite logically, as a function. This very simple kind of function passing should be familiar to many programmers: it is often used in user-interface code, to register call-back functions which get called when some event occurs.

下面的程序将实现简单定时器的功能,负责定时的函数(function)名为:oncePerSecond,它接受一个回调函数作为参数,该回调函数的类型记为:() => Unit,代表任何无参数、无返回值的函数(Unit和C/C++中的void类似)。程序的main方法调用定时函数,作为实参传进去的回调函数timeFlies,仅仅向终端打印一句话,所以,该程序的实际功能是:每秒钟在屏幕上打印一条信息:time flies like an arrow。

In the following program, the timer function is called oncePerSecond, and it gets a call-back function as argument. The type of this function is written () => Unit and is the type of all functions which take no arguments and return nothing (the type Unit is similar to void in C/C++). The main function of this program simply calls this timer function with a call-back which prints a sentence on the terminal. In other words, this program endlessly prints the sentence “time flies like an arrow” every second.

object Timer {

def oncePerSecond(callback: () => Unit) {

while (true ) { callback(); Thread sleep 1000 }

}

def timeFlies() {

println("time flies like an arrow...")

}

def main(args: Array[String]) {

oncePerSecond(timeFlies)

}

}

注意,程序中使用Scala预定义方法println实现字符串显示,而没有用System.out上的方法。

Note that in order to print the string, we used the predefined method println instead of using the one from System.out.

4.2.1 匿名函数 Anonymous functions

定时器的示例程序还可以做一些改进。首先,timeFlies函数只被用过一次,也就是当做回调函数传给oncePerSecond的时候,对于这种函数,在用到的时候即时构造更合理,因为可以省去定义和命名的麻烦,在Scala中,这样的函数称为匿名函数(anonymous functions),也就是没有名字的函数。使用匿名函数代替timeFlies函数后的程序代码如下:

While this program is easy to understand, it can be refined a bit. First of all, notice that the function timeFlies is only defined in order to be passed later to the oncePerSecond function. Having to name that function, which is only used once, might seem unnecessary, and it would in fact be nice to be able to construct this function just as it is passed to oncePerSecond. This is possible in Scala using anonymous functions , which are exactly that: functions without a name. The revised version of our timer program using an anonymous function instead of timeFlies looks like that:

object TimerAnonymous {

def oncePerSecond(callback: () => Unit) {

while (true ) { callback(); Thread sleep 1000 }

}

def main(args: Array[String]) {

oncePerSecond(() =>

println("time flies like an arrow..."))

}

}

代码中的右箭头‘=>’表明程序中存在一个匿名函数,箭头左边是匿名函数的参数列表,右边是函数体。在本例中,参数列表为空(箭头左边是一对空括号),而函数体和改造前定义的timeFlies函数保持一致。

The presence of an anonymous function in this example is revealed by the right arrow ‘=>’ which separates the function’s argument list from its body. In this example, the argument list is empty, as witnessed by the empty pair of parenthesis on the left of the arrow. The body of the function is the same as the one of timeFlies above.

5 Classes

前面已经说过,Scala是面向对象的语言,所以它有类(class)的概念[2] 。Scala中声明类的语法和Java类似,但有一点重要的差异,那就是Scala中的类定义可以带参数(parameters),下面定义的复数类(complex number)可以很清晰的展示这一特性:

As we have seen above, Scala is an object-oriented language, and as such it has a concept of class. 2 Classes in Scala are declared using a syntax which is close to Java’s syntax. One important difference is that classes in Scala can have parameters. This is illustrated in the following definition of complex numbers.

class Complex(real: Double, imaginary: Double) {

def re() = real

def im() = imaginary

}

该复数类可以接受两个参数,分别代表复数的实部和虚部,如果要创建Complex类的实例,则必须提供这两个参数,比如:new Complex(1.5, 2.3)。该类有两个方法:re和im,分别用于访问复数的实部和虚部。

This complex class takes two arguments, which are the real and imaginary part of the complex. These arguments must be passed when creating an instance of class Complex, as follows: new Complex(1.5, 2.3). The class contains two methods, called re and im, which give access to these two parts.

需要注意的是,这两个方法的返回值都没有显式定义。在编译过程中,编译器可以根据函数定义的右部(right-hand ),推断(infer,deduce)出两个函数的返回值都是Double类型。

It should be noted that the return type of these two methods is not given explicitly. It will be inferred automatically by the compiler, which looks at the right-hand side of these methods and deduces that both return a value of type Double.

但编译器并非在任何情况下都能准确推导出数据类型,而且,很难用一套简单的规则来定义什么情况下可以,什么情况下不可以。不过,对于没有显式指定类型,且无法推导出类型的表达式,编译器会给出提示信息,所以,推导规则的复杂性,对实际编程的影响不大。对于初学者,可以遵循这样一条原则:当上下文看起来比较容易推导出数据类型时,就应该忽略类型声明,并尝试是否能够编译通过,不行就修改。这样用上一段时间,程序员会积累足够的经验,从而可以比较自如的决定何时应该省略类型声明,而何时应该显式声明类型。

The compiler is not always able to infer types like it does here, and there is unfortunately no simple rule to know exactly when it will be, and when not. In practice, this is usually not a problem since the compiler complains when it is not able to infer a type which was not given explicitly. As a simple rule, beginner Scala programmers should try to omit type declarations which seem to be easy to deduce from the context, and see if the compiler agrees. After some time, the programmer should get a good feeling about when to omit types, and when to specify them explicitly.

5.1 无参方法- Methods without arguments

Complex类中的re和im方法有个小问题,那就是调用这两个方法时,需要在方法名后面跟上一对空括号,就像下面的例子一样:

A small problem of the methods re and im is that, in order to call them, one has to put an empty pair of parenthesis after their name, as the following example shows:

object ComplexNumbers {

def main(args: Array[String]) {

val c= new Complex(1.2, 3.4)

println("imaginary part: " + c.im())

}

}

如果能够省掉这些方法后面的空括号,就像访问类属性(fields)一样访问类的方法,则程序会更加简洁。这在Scala中是可行的,只需将方法显式定义为没有参数(without arguments)即可。无参方法和零参方法(methods with zero arguments)的差异在于:无参方法在声明和调用时,均无须在方法名后面加括号。所以,前面的Complex类可以重写如下:

It would be nicer to be able to access the real and imaginary parts like if they were fields, without putting the empty pair of parenthesis. This is perfectly doable in Scala, simply by defining them as methods without arguments . Such methods differ from methods with zero arguments in that they don’t have parenthesis after their name, neither in their definition nor in their use. Our Complex class can be rewritten as follows:

class Complex(real: Double, imaginary: Double) {

def re = real

def im = imaginary

}

5.2 继承和方法重写 Inheritance and overriding

Scala中的所有类都继承自某一个父类(或者说超类,super-class),若没有显式指定父类(比如前面的Complex类),则默认继承自scala.AnyRef。

All classes in Scala inherit from a super-class. When no super-class is specified, as in the Complex example of previous section, scala.AnyRef is implicitly used.

在Scala中可以重写(overriding)从父类继承的方法,但必须使用override 修饰符来显式声明,这样可以避免无意间的方法覆盖(accidental overriding)。例如,前面定义的Complex类中,我们可以重写从Object类中继承的toString方法,代码如下:

It is possible to override methods inherited from a super-class in Scala. It is however mandatory to explicitly specify that a method overrides another one using the override modifier, in order to avoid accidental overriding. As an example, our Complex class can be augmented with a redefinition of the toString method inherited from Object.

class Complex(real: Double, imaginary: Double) {

def re = real

def im = imaginary

override def toString() =

"" +re + (if (im < 0) "" else "+") + im + "i"

}


[1] 其它说法语的国家和地区,比如瑞士的部分地区通常也使用相同的格式。

[2] 严格的说,的确有些面向对象的语言没有类(class)的概念,但Scala并非其中之一。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值