Scala学习入门.docx

上传人:b****5 文档编号:7658827 上传时间:2023-01-25 格式:DOCX 页数:13 大小:29.01KB
下载 相关 举报
Scala学习入门.docx_第1页
第1页 / 共13页
Scala学习入门.docx_第2页
第2页 / 共13页
Scala学习入门.docx_第3页
第3页 / 共13页
Scala学习入门.docx_第4页
第4页 / 共13页
Scala学习入门.docx_第5页
第5页 / 共13页
点击查看更多>>
下载资源
资源描述

Scala学习入门.docx

《Scala学习入门.docx》由会员分享,可在线阅读,更多相关《Scala学习入门.docx(13页珍藏版)》请在冰豆网上搜索。

Scala学习入门.docx

Scala学习入门

 

Scala入门

 

本文源自MichelSchinz和PhilippHaller所写的AScalaTutorialforJavaprogrammers,由Bearice成中文,dongfengyee(东风雨)整理。

 

1简介

本文仅在对Scala语言和其编译器进行简要介绍。

本文的目的读者是那些已经具有一定编程经验,而想尝试一下Scala语言的人们。

要阅读本文,你应当具有基础的面向对象编程的概念,尤其是Java语言的。

2第一个Scala例子

作为学习Scala的第一步,我们将首先写一个标准的HelloWorld,这个虽然不是很有趣,但是它可以让你对Scala有一个最直观的认识而不需要太多关于这个语言的知识。

我们的Helloworld看起来像这样:

objectHelloWorld{

defmain(args:

Array[String]){

println("Hello,world!

")

}

}

程序的结构对Java程序员来说可能很令人怀念:

它由一个main函数来接受命令行参数,也就是一个String数组。

这个函数的唯一一行代码把我们的问候语传递给了一个叫println的预定义函数。

main函数不返回值(所以它是一个proceduremethod)。

所以,也不需要声明返回类型。

对于Java程序员比较陌生的是包含了main函数的object语句。

这样的语句定义了一个单例对象:

一个有且仅有一个实例的类。

object语句在定义了一个叫HelloWorld的类的同时还定义了一个叫HelloWorld的实例。

这个实例在第一次使用的时候会进行实例化。

聪明的读者可能会发现main函数并没有使用static修饰符,这是由于静态成员

(方法或者变量)在Scala中并不存在。

Scala从不定义静态成员,而通过定义

单例object取而代之。

 

2.1编译实例

我们使用Scala编译器“scalac”来编译Scala代码。

和大多数编译器一样,scalac接受源文件名和一些选项作为参数,生成一个或者多个目标文件。

scala编译生成的产物就是标准的Java类文件。

假设我们吧上述代码保存为文件HelloWorld.scala,我们使用下面的命令编译它(大于号“>”表示命令提示符,你不必输入它)

>scalacHelloWorld.scala

这将会在当前目录生成一系列.class文件。

其中的一个名为HelloWorld.class的文件中定义了一个可以直接使用scala命令执行的类。

下文中你可以看到这个例子。

2.2运行实例

一旦完成编译,Scala程序就可以使用scala命令执行了。

scala的用法和java很相似,并且连选项也大致相同。

上面的例子就可以使用下面的命令运行,这将会产生我们所期望的输出。

>scala-classpath.HelloWorld

Hello,world!

3Scala与Java交互

Scala的一个强项在于可以很简单的于已有的Java代码交互,所有

java.lang中的类都已经被自动导入了,而其他的类需要显式声明导入。

来看看演示代码吧。

我们希望对日期进行格式化处理,比如说用法国的格式。

Java类库定义了一系列很有用的类,比如Date和DateFormat。

由于Scala于

Java能够进行很好的交互,我们不需要在Scala类库中实现等效的代码,而只

需直接吧Java的相关类导入就可以了:

importjava.util.{Date,Locale}importjava.text.DateFormatimportjava.text.DateFormat._objectFrenchDate{

defmain(args:

Array[String]){

valnow=newDate

valdf=getDateInstance(LONG,Locale.FRANCE)

println(dfformatnow)

}

}

 

Scala的import语句看上去与Java的非常相似,但是它更加强大。

你可以使用大括号来导入同一个包里的多个类,就像上面代码中第一行所做的那样。

另一个不同点是当导入一个包中所有的类或者符号时,你应该使用下划线(_)而不是星号(*)。

这是由于星号在Scala中是一个有效的标识符(例如作为方法名称)。

这个例子我们稍后会遇到。

第三行的import语句导入了DataFormat类中的所有成员,这使得静态方法

getDateInstance和静态变量LONG可以被直接引用。

在main函数中,我们首先建立了一个Java的Date实例。

这个实例默认会包含当前时间。

接下来我们一个使用刚才导入的静态函数getDateInstance定义了日期格式。

最后我们将使用DataFotmat格式化好的日期打印了出来。

最后一行代码显示了Scala的一个有趣的语法:

只有一个参数的函数可以使用下面这样的表达式来表示:

dfformatnow其实就是下面的这个冗长的表达式的简洁写法df.format(now)

这看起来是一个语法细节,但是它导致一个重要的后果,我们将在下一节进行说明。

另外,我们还应当注意到Scala中可以直接继承或者实现Java中的接口和类。

 

4Scala:

万物皆对象

Scala作为一个纯面向对象的语言,于是在Scala中万物皆对象,包括数字和函数。

在这方面,Scala于Java存在很大不同:

Java区分原生类型(比如boolean和int)和引用类型,并且不能把函数当初变量操纵。

4.1数字和对象由于数字本身就是对象,所以他们也有方法。

事实上我们平时使用的算数表

达式(如下例)

1+2*3/x

是由方法调用组成的。

它等效于下面的表达式,我们在上一节见过这个描述。

(1).+((

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

这也意味着+,-,*,/在Scala中也是有效的名称。

 

在第二个表达式中的这些括号是必须的,因为Scala的分词器使用最长规则

来进行分词。

所以他会把下面的表达式:

1.+

(2)

理解成表达项1.,+,和2的组合。

这样的组合结果是由于1.是一个有效的表达项并且比表达项1要长,表达项1.会被当作1.0,使得它成为一个double而不是int。

而下面的表达式阻止了分析器错误的理解

(1).+

(2)

4.2函数与对象

函数在Scala语言里面也是一个对象,也许这对于Java程序员来说这比较令人惊讶。

于是吧函数作为参数进行传递、把它们存贮在变量中、或者当作另一个函数的返回值都是可能的。

吧函数当成值进行操作是函数型编程语言的基石。

为了解释为什么吧函数当作值进行操作是十分有用的,我们来考虑一个计时器函数。

这个函数的目的是每隔一段时间就执行某些操作。

那么如何吧我们要做的操作传入计时器呢?

于是我们想吧他当作一个函数。

这种目前的函数对于经常进行用户界面编程的程序员来说是最熟悉的:

注册一个回调函数以便在事件发生后得到通知。

在下面的程序中,计时器函数被叫做oncePerSceond,它接受一个回调函数作为参数。

这种函数的类型被写作()=>Unit,他们不接受任何参数也没有任何返回(Unit关键字类似于C/C++中的void)。

程序的主函数调用计时器并传递一个打印某个句子的函数作为回调。

换句话说,这个程序永无止境的每秒打印一个“timeflieslikeanarrow”。

objectTimer{

defoncePerSecond(callback:

()=>Unit){

while(true){callback();Threadsleep1000}

}

deftimeFlies(){

println("timeflieslikeanarrow...")

}

defmain(args:

Array[String]){

oncePerSecond(timeFlies)

}

}

注意,我们输出字符串时使用了一个预定义的函数println而不是使用System.out中的那个。

 

4.2.1匿名函数

我们可以吧这个程序改的更加易于理解。

首先我们发现定义函数timeFlies的唯一目的就是当作传给oncePerSecond的参数。

这么看来给这种只用一次的函数命名似乎没有什么太大的必要,事实上我们可以在用到这个函数的时候再定义它。

这些可以通过匿名函数在Scala中实现,匿名函数顾名思义就是没有名字的函数。

我们在新版的程序中将会使用一个匿名函数来代替原来的timeFlise函数,程序看起来像这样:

objectTimerAnonymous{

defoncePerSecond(callback:

()=>Unit){

while(true){callback();Threadsleep1000}

}

defmain(args:

Array[String]){

oncePerSecond(()=>

println("timeflieslikeanarrow..."))

}

}

本例中的匿名函数使用了一个箭头(=>)吧他的参数列表和代码分开。

在这里参数列表是空的,所以我们在右箭头的左边写上了一对空括号。

函数体内容与上面的timeFlise是相同的。

 

5Scala类

正如我们所见,Scala是一门面向对象的语言,因此它拥有很多关于“类”的描述。

Scala类使用和Java类似的语法进行定义。

但是一个重要的不同点在于Scala中的类可以拥有参数,这样就可以得出我们下面关于对复数类

(Complex)的定义:

classComplex(real:

Double,imaginary:

Double){

defre()=real

defim()=imaginary

}

我们的复数类(Complex)接受两个参数:

实部和虚部。

这些参数必须在实例化时进行传递,就像这样:

newComplex(1.5,2.3)。

类定义中包括两个叫做re

和im的方法,分别接受上面提到的两个参数。

值得注意的是这两个方法的返回类型并没有显式的声明出来。

他们会被编译器自动识别。

在本例中他们被识别为Double但是编译器并不总是像本例中的那样进行自动识别。

不幸的是关于什么时候识别,什么时候不识别的规则相当冗杂。

在实践中这通常不会成为一个问题,因为当编译器处理不了的时候会发出相当

 

的抱怨。

作为一个推荐的原则,Scala的新手们通常可以试着省略类型定义而让编译器通过上下文自己判断。

久而久之,新手们就可以感知到什么时候应该省略类型,什么时候不应该。

5.1无参方法

关于方法re和im还有一个小问题:

你必须在名字后面加上一对括号来调用它们。

请看下面的例子:

objectComplexNumbers{

defmain(args:

Array[String]){

valc=newComplex(1.2,3.4)

println("imaginarypart:

"+c.im())

}

}

你可能觉得吧这些函数当作变量使用,而不是当作函数进行调用,可能会更加令人感到舒服。

事实上我们可以通过定义无参函数在Scala做到这点。

这类函数与其他的具有0个参数的函数的不同点在于他们定义时不需要在名字后面加括弧,所以在使用时也不用加(但是无疑的,他们是函数),因此,我们的Complex类可以重新写成下面的样子;

classComplex(real:

Double,imaginary:

Double){

defre=real

defim=imaginary

}

5.2继承和覆盖

Scala中的所有类都继承一个父类,当没有显示声明父类时(就像上面定义的Complex一样),它们的父类隐形指定为scala.AnyRef。

在子类中覆盖父类的成员是可能的。

但是你需要通过override修饰符显示指定成员的覆盖。

这样的规则可以避免意外覆盖的情况发生。

作为演示,我们在Complex的定义中覆盖了Object的toString方法。

classComplex(real:

Double,imaginary:

Double){

defre=real

defim=imaginary

overridedeftoString()=

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

}

 

6Scala的模式匹配和条件类

树是在程序中常用的一个数据结构。

例如编译器和解析器常常吧程序表示为树;XML文档结构也是树状的;还有一些集合是基于树的,例如红黑树。

接下来我们将通过一个计算器程序来研究树在Scala中是如何表示和操纵的。

这个程序的目标是处理一些由整数常量、变量和加号组成的简单的算数表达式,例如1+2和(x+x)+(7+y)。

我们首先要决定如何表示这些表达式。

最自然的方法就是树了,树的节点表示操作符(在这里只有加法),而树的叶节点表示值(这里表示常数和变量)。

在Java中,这样的树可以表示为一个超类的树的集合,节点由不同子类的实例表示。

而在函数式语言中,我们可以使用代数类型(algebraicdata-type)来达到同样的目的。

Scala提供了一种介于两者之间的叫做条件类(CaseClasses)的东西。

abstractclassTree

caseclassSum(l:

Tree,r:

Tree)extendsTree

caseclassVar(n:

String)extendsTree

caseclassConst(v:

Int)extendsTree

我们实际上定义了三个条件类Sum,Var和Const。

这些类和普通类有若干不同:

1.实例化时可以省略new关键字(例如你可以使用Const(5)而不必使用

newConst(5))

2.参数的getter函数自动定义(例如你可以通过c.v来访问类Const的实

例c在实例化时获取的参数v)

3.拥有默认的预定义equals和hashCode实现,这些实现可以按照值区别类

实例是否相等,而不是通过用。

4.拥有默认的toString实现。

这些实现返回值的代码实现(例如表达式x+1

可以被表达成Sum(Var(x),Const

(1)))

5.条件类的实例可以通过模式匹配进行分析,我们接下来就要讲这个特性。

现在我们已经定义了表示我们算数表达式的数据类型,于是我们可以开始给他们定义对应的操作。

我们将会首先编写一个在上下文中下计算表达式的函数。

这里的上下文指的是变量与值的绑定关系。

例如表达式x+1在x=5上下文中应该得出结果6。

这样一来我们需要找到一个表示这种绑定关系的方法。

当然我们可以使用某种类似hash-table的数据结构,不过我们也可以直接使用函数!

一个上下文无非就是一个吧名称映射到值的函数。

例如上面给出的{x→5}的这个映射我们就可以在Scala中表示为:

 

{case"x"=>5}

这个定义了一个函数:

当参数等于字符串"x"时返回整数5,否则抛出异常。

在编写求值函数之前我们,我们需要给我们的上下文起个名字,以便在后面的代

码里面引用。

理所应当的我们使用了类型String=>Int,但是如果我们给这个

类型起个名字,将会让程序更加简单易读,而且更加容易维护。

在scala中,这

件事情可以通过以下代码完成:

typeEnvironment=String=>Int

从现在开始,类型Environment就当作String到Int的函数类型名来使用了。

现在我们可以开始定义求值函数了。

从概念上来说,这是很简单的一个过程:

个表达式之和等于两个表达式分别求值后再求和;变量的值可以从上下文中提

取;常量的值就是他本身。

在Scala中表达这个没有什么难度:

defeval(t:

Tree,env:

Environment):

Int=tmatch{caseSum(l,r)=>eval(l,env)+eval(r,env)caseVar(n)=>env(n)

caseConst(v)=>v

}

求值函数通过对树t进行模式匹配来完成工作。

直观的来看,上述代码的思路是十分清晰的:

1.第一个模式检查传入的树的根节点是否是一个Sum,如果是,它将会吧树的左边子树赋值给l,右边的子树赋值给r,然后按照箭头后面的代码进行处理;这里的代码可以(并且的确)使用了在左边匹配时所绑定的变量,比如这里的l和r。

2.如果第一个检查没有成功,表明传入的树不是Sum,程序继续检查他是不是一个Var;如果是,则吧变量名赋给n然后继续右边的操作。

3.如果第二个检查也失败了,表示t既不是Sum也不是Var,程序检查他是不是Const。

如果是着赋值变量并且继续。

4.最后,如果所有检查都失败了。

就抛出一个异常表示模式匹配失败。

这只

有在Tree的其他之类被定义时才可能发生。

我们可以看出模式匹配的基本思想就是试图对一个值进行多种模式的匹配,并且在匹配的同时将匹配值拆分成若干子项,最后对匹配值与其子项执行某些代码。

一个熟练的面向对象的程序员可能想知道为什么我们不吧eval定义为Tree或者其之类的成员函数。

我们事实上可以这么做。

因为Scala允许条件类象普通类那样定义成员。

决定是否使用模式匹配或者成员函数取决于程序员的喜好,不过这个取舍还和可扩展性有重要联系:

 

1.当你使用成员函数时,你可以通过继承Tree从而很容易的添加新的节点类型,但是另外一方面,添加新的操作也是很繁杂的工作,因为你不得不修改Tree的所有子类。

2.当你使用模式匹配是,形势正好逆转过来,添加新的节点类型要求你修改所有的对树使用模式匹配的函数,但是另一方面,添加一个新的操作只需

要再添加一个模式匹配函数就可以了。

下面我们来更详细的了解模式匹配,让我们再给表达式定义一个操作:

对符号求导数。

读者们也许想先记住下面关于此操作的若干规则:

1.和的导数等于导数的和,

2.如果符号等以求导的符号,则导数为1,否则为0.

3.参数的导数永远为0。

上述规则可以直接翻译成Scala代码:

defderive(t:

Tree,v:

String):

Tree=tmatch{

caseSum(l,r)=>Sum(derive(l,v),derive(r,v))

caseVar(n)if(v==n)=>Const

(1)

case_=>Const(0)

}

这个函数使用了两个关于模式匹配的功能,首先case语句可以拥有一个guard子句:

一个if条件表达式。

除非guard的条件成立,否则该模式不会成功匹配。

其次是通配符:

_。

这个模式表示和所有值匹配而不对任何变量赋值。

事实上我们还远没有触及模式匹配的全部精髓。

但是我们限于篇幅原因不得不再此停笔了。

下面我们看看这个两个函数是如何在一个实例上运行的。

为了达到这个目前我们写了一个简单的main函数来对表达式(x+x)+(7+y)进行若干操作:

首先计算当{x→5,y→7}时表达式的值,然后分别对x和y求导。

defmain(args:

Array[String]){

valexp:

Tree=Sum(Sum(Var("x"),Var("x")),Sum(Const(7),Var("y")))

valenv:

Environment={case"x"=>5case"y"=>7}

println("Expression:

"+exp)

println("Evaluationwithx=5,y=7:

"+eval(exp,env))

println("Derivativerelativetox:

\n"+derive(exp,"x"))

println("Derivativerelativetoy:

\n"+derive(exp,"y"))

}

执行程序,我们能得到以下输出:

Expression:

Sum(Sum(Var(x),Var(x)),Sum(Const(7),Var(y)))Evaluationwithx=5,y=7:

24

Derivativerelativetox:

 

Sum(Sum(Const

(1),Const

(1)),Sum(Const(0),Const(0)))Derivativerelativetoy:

Sum(Sum(Const(0),Const(0)),Sum(Const(0),Const

(1)))

通过研究程序输出,我们能看到求导的输出可以在被打印之前简化,使用模式匹配定义一个简化函数是挺有意思的(不过也需要一定的技巧)工作。

读者可以尝试自己完成这个函数。

 

7ScalaTrait

除了从父类集成代码外,Scala中的类还允许从一个或者多个traits中导入代码。

对于Java程序员来说理解traits的最好方法就是把他们当作可以包含代码的接口(interface)。

在Scala中,当一个类继承一个trait时,它就实现了这个trait的接口,同时还从这个trait中继承了所有的代码。

让我们通过一个典型的实例来看看这种trait机制是如何发挥作用的:

排序对象。

能够比较若干给定类型的对象在实际应用中是很有用的,比如在进行排序时。

在Java语言中可以比较的对象是通过实现Comparable接口完成的。

在Scala中我们可以通过吧Comparable定义为trait来做的比Java好一些。

我们吧这个trait叫做Ord。

在比较对象时,一下六种关系通常使用率最高:

小于、小于等于、等于、不等于、大于等于、大于。

但是把他们都定义一次无疑是很没用而且繁琐的。

尤其是六种关系中的四种其实是可以通过其他两种关系导出的。

例如给

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 农林牧渔 > 林学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1