spark

Scala学习笔记1——Scala入门

2016-05-22  本文已影响1195人  26001a36aa12

从使用Scala解释器开始

输入任意的Scala表达式,比如输入1+2,解释器显示:res0:Int = 3

这行显示包括:

    一个由Scala解释器自动生成的变量名或者由你指定的变量名用来指向计算出来的结果(比如res0代表result0变量)

    一个冒号,后面紧跟个变量类型比如Int

    一个等于号=

    计算结果,本例为1+2的结果3

    resX变量名可以用在之后的表达式中,比如:此时res0=3,如果输入res0 *3,则显示res1: Int =9。

变量定义

Scala 定义了两种类型的变量val和var,val类似于Java中的final变量,一旦初始化之后,不可以重新复制(我们可以称它为常变量)。而var类似于一般的非final变量。可以任意重新赋值。

比如定义一个字符串常变量:

scala> val msg="Hello,World"

msg:String= Hello,World

这个表达式定义了一个msg变量,为字符串常量。它的类型为string(java.lang.string)。 可以看到我们在定义这个变量时并不需要像Java一样定义其类型,Scala 可以根据赋值的内容推算出变量的类型。这在Scala语言中成为“type inference”。当然如果你愿意,你也可以采用和Java一样的方法,明确指定变量的类型,如:

scala> val msg2:String="Hello again,world"

msg2:String= Hello again,world

不过这样写就显得不像Scala风格了。此外Scala语句也不需要以分号结尾。 如果在命令行中需要分多行输入,Scala解释器在新行前面显示|,表示该行接着上一行。比如:

scala> val msg3=    

           |"Hello world 3rd time"

msg3:String= Hello world 3rd time

函数定义

Scala既是面向对象的编程语言,也是面向函数的编程语言,因此函数在Scala语言中的地位和类是同等第一位的。

下面的代码定义了一个简单的函数求两个值的最大值:

scala>def max(x:Int,y:Int):Int ={    

          |if(x >y) x    

          |else

          | y    

          | }

max: (x: Int, y: Int)Int

Scala函数以def定义,然后是函数的名称(如max),然后是以逗号分隔的参数。Scala中变量类型是放在参数和变量的后面,以“:”隔开。这种做的一个好处是便于“type inference”。刚开始有些不习惯(如果你是Pascal程序员会觉的很亲切)。同样如果函数需要返回值,它的类型也是定义在参数的后面(实际上每个Scala函数都有返回值,只是有些返回值类型为Unit,类似为void类型)。

此外每个Scala表达式都有返回结果(这一点和Java,C#等语言不同),比如Scala的if else语句也是有返回值的,因此函数返回结果无需使用return语句。实际上在Scala代码应当尽量避免使用return语句。函数的最后一个表达式的值就可以作为函数的结果作为返回值。

同样由于Scala的“type inference”特点,本例其实无需指定返回值的类型。对于大多数函数Scala都可以推测出函数返回值的类型,但目前来说回溯函数(函数调用自身)还是需要指明返回结果类型的。

下面在定义个“没有”返回结果的函数(其它语言可能称这种无返回值的函数为程式)。

scala> def greet()= println("hello,world")

greet: ()Unit

greet函数的返回值类型为Unit 表示该函数不返回任何有意义的值,Unit类似于Java中的void类型。这种类型的函数主要用来获得函数的“副作用”,比如本函数的副作用是打印招呼语。

Scala脚本

Scala本身是设计用来编写大型应用的,但它也可以作为脚本语言来执行,脚本为一系列Scala表达式构成以完成某个任务,比如前面的Hello World脚本,你也可以使用脚本来实现一些比如复制文件,创建目录之类的任务。

Scala中的while循环

下面的代码使用while实现一个循环:

var i=0

while(i < args.length) {  println (args(i))  i+=1}

这里要注意的是Scala不支持++i和i++运算符,因此需要使用i += 1来加一。 这段代码看起来和Java代码差不多,实际上while也是一个函数,你自动可以利用scala语言的扩展性,实现while语句,使它看起来和Scala语言自带的关键字一样调用。

Scala访问数组的语法是使用()而非[]。

这里介绍了使用while来实现循环,但这种实现循环的方法并不是最好的Scala风格,在下一步介绍使用一种更好的方法来避免通过索引来枚举数组元素。

foreach 和 for 来实现迭代

第五步使用while来实现循环,和使用Java实现无太大差异,而Scala是面向函数的语言,更好的方法是采用“函数式”风格来编写代码。比如上面的循环,使用foreach方法如下:

args.foreach(arg => println(arg))

该表达式,调用args的foreach方法,传入一个参数,这个参数类型也是一个函数(lambda表达式,和C#中概念类似)。这段代码可以再写的精简些,你可以利用Scala支持的缩写形式,如果一个函数只有一个参数并且只包含一个表达式,那么你无需明确指明参数。因此上面的代码可以写成:

args.foreach( println)

Scala中也提供了一个称为“for comprehension”,它比Java中的for功能更强大。“for comprehension”(可称之为for表达式)将在后面介绍,这里先使用for来实现前面的例子:

for(arg <-args)  println(arg)

使用类型参数化数组

在Scala中你可以使用new来实例化一个类。当你创建一个对象的实例时,你可以使用数值或类型参数。如果使用类型参数,它的作用类似Java或.Net的Generic类型。所不同的是Scala使用方括号来指明数据类型参数,而非尖括号。比如:

val greetStrings = new Array[String](3)

greetStrings(0)= "Hello"

greetStrings(1)= ","

greetStrings(2)= "world!\n"

for(i<-0to2) print(greetStrings(i))

可以看到Scala使用[]来为数组指明类型化参数,本例使用String类型,数组使用()而非[]来指明数组的索引。其中的for表达式中使用到0 to 2,这个表达式演示了Scala的一个基本规则,如果一个方法只有一个参数,你可以不用括号和.来调用这个方法

因此这里的0 to 2, 其实为(0).to(2)调用的为整数类型的to方法,to方法使用一个参数。Scala中所有的基本数据类型也是对象(和Java不同),因此0可以有方法(实际上调用的是RichInt的方法),这种只有一个参数的方法可以使用操作符的写法(不用.和括号),实际上Scala中表达式1+2,最终解释为(1).+(2)+也是Int的一个方法,和Java不同的是,Scala对方法的名称没有太多的限制,你可以使用符合作为方法的名称。

这里也说明为什么Scala中使用()来访问数组元素,在Scala中,数组和其它普遍的类定义一样,没有什么特别之处,当你在某个值后面使用()时,Scala将其翻译成对应对象的apply方法。因此本例中greetStrings(1)其实调用greetString.apply(1)方法。这种表达方法不仅仅只限于数组,对于任何对象,如果在其后面使用(),都将调用该对象的apply方法。同样的如果对某个使用()的对象赋值,比如:

greetStrings(0)="Hello"

Scala将这种赋值转换为该对象的update方法, 也就是greetStrings.update(0,”hello”)。因此上面的例子,使用传统的方法调用可以写成:

val greetStrings =new Array[String](3)

greetStrings.update(0,"Hello")

greetStrings.update(1,",")

greetStrings.update(2,"world!\n")

for(i<-0to2) print(greetStrings.apply(i))

从这点来说,数组在Scala中并不某种特殊的数据类型,和普通的类没有什么不同。

不过Scala还是提供了初始化数组的简单的方法,比如什么的例子数组可以使用如下代码:

val greetStrings =Array("Hello",",","World\n")

这里使用()其实还是调用Array类的关联对象Array的apply方法,也就是:

val greetStrings =Array.apply("Hello",",","World\n")

Lists

Scala也是一个面向函数的编程语言,面向函数的编程语言的一个特点是,调用某个方法不应该有任何副作用参数一定,调用该方法后,返回一定的结果,而不会去修改程序的其它状态(副作用)。这样做的一个好处是方法和方法之间关联性较小,从而方法变得更可靠和重用性高。使用这个原则也就意味着就变量的设成不可修改的,这也就避免了多线程访问的互锁问题

前面介绍的数组,它的元素是可以被修改的。如果需要使用不可以修改的序列,Scala中提供了Lists类。和Java的List不同,Scala的Lists对象是不可修改的。它被设计用来满足函数编程风格的代码。它有点像Java的String,String也是不可以修改的,如果需要可以修改的String对像,可以使用StringBuilder类。

比如下面的代码:

val oneTwo =List(1,2)

val threeFour =List(3,4)

val oneTwoThreeFour=oneTwo ::: threeFour

println (oneTwo +" and "+ threeFour +" were not mutated.")

println ("Thus, "+ oneTwoThreeFour +" is a new list")

定义了两个List对象oneTwo和threeFour,然后通过:::操作符(其实为:::方法)将两个列表链接起来。实际上由于List的不可以修改特性,Scala创建了一个新的List对象oneTwoThreeFour来保存两个列表连接后的值。

List也提供了一个::方法用来向List中添加一个元素,::方法(操作符)是右操作符,也就是使用::右边的对象来调用它的::方法,Scala中规定所有以:开头的操作符都是右操作符,因此如果你自己定义以:开头的方法(操作符)也是右操作符。

如下面使用常量创建一个列表:

valoneTowThree =1::2::3:: Nil

println(oneTowThree)

调用空列表对象Nil的::方法 也就是:

valoneTowThree =  Nil.::(3).::(2).::(1)

Scala 的List类还定义其它很多很有用的方法,比如head、last、length、reverse、tail等。这里就不一一说明了,具体可以参考List的文档。

Tuples

Scala中另外一个很有用的容器类为Tuples,和List不同的是,Tuples可以包含不同类型的数据,而List只能包含同类型的数据。Tuples在方法需要返回多个结果时非常有用。(Tuple对应到数学的矢量的概念)。

一旦定义了一个元组,可以使用._和索引来访问员组的元素(矢量的分量,注意和数组不同的是,元组的索引从1开始)。

val pair=(99,"Luftballons")

println(pair._1)

println(pair._2)

元组的实际类型取决于它的分量的类型,比如上面pair的类型实际为Tuple2[Int,String],而(‘u’,’r’,”the”,1,4,”me”)的类型为Tuple6[Char,Char,String,Int,Int,String]。

目前Scala支持的元组的最大长度为22。如果有需要,你可以自己扩展更长的元组。

Sets和Maps

Scala语言的一个设计目标是让程序员可以同时利用面向对象和面向函数的方法编写代码,因此它提供的集合类分成了可以修改的集合类和不可以修改的集合类两大类型。比如Array总是可以修改内容的,而List总是不可以修改内容的。类似的情况,Scala也提供了两种Sets和Map集合类。

比如 Scala API 定义了Set的基Trait类型Set(Trait的概念类似于Java中的Interface,所不同的Scala中的Trait可以有方法的实现),分两个包定义Mutable(可变)和Immutable(不可变),使用同样名称的子Trait。下图为Trait和类的基础关系:

Set

使用Set的基本方法如下:

varjetSet = Set ("Boeing","Airbus")jetSet +="Lear"println(jetSet.contains("Cessna"))

缺省情况Set为Immutable Set,如果你需要使用可修改的集合类(Set类型),你可以使用全路径来指明Set,比如scala.collection.mutalbe.Set。

Scala提供的另外一个类型为Map类型,Scala也提供了Mutable和Immutable两种 Map 类型,如下图所示。

Map

Map的基本用法如下(Map类似于其它语言中的关联数组如PHP)

val romanNumeral = Map (1->"I",2->"II",3->"III",4->"IV",5->"V")

println (romanNumeral(4))

学习识别函数编程风格

Scala语言的一个特点是支持面向函数编程,因此学习Scala的一个重要方面是改变之前的指令式编程思想(尤其是来自Java或C#背景的程序员),观念要向函数式编程转变。首先在看代码上要认识哪种是指令编程,哪种是函数式编程。实现这种思想上的转变,不仅仅会使你成为一个更好的Scala程序员,同时也会扩展你的视野,使你成为一个更好的程序员。

一个简单的原则,如果代码中含有var类型的变量,这段代码就是传统的指令式编程,如果代码只有val变量,这段代码就很有可能是函数式代码,因此学会函数式编程关键是不使用vars来编写代码

来看一个简单的例子:

def printArgs( args: Array[String]):Unit ={   

        var i=0

        while(i < args.length) { println (args(i))

        i+=1}

}

来自Java背景的程序员开始写Scala代码很有可能写成上面的实现。我们试着去除vars变量,可以写成跟符合函数式编程的代码:

def printArgs( args: Array[String]):Unit ={for( arg <- args)      println(arg)}

或者更简化为:

defprintArgs( args: Array[String]):Unit ={args.foreach(println)}

这个例子也说明了尽量少用vars的好处,代码更简洁和明了,从而也可以减少错误的发生。因此Scala编程的一个基本原则上,能不用vars,尽量不用vars,能不用mutable变量,尽量不用mutable变量,能避免函数的副作用,尽量不产生副作用。

读取文件

使用脚本实现某个任务,通常需要读取文件,本节介绍Scala读写文件的基本方法。比如下面的例子读取文件的每行,把该行字符长度添加到行首:

import scala.io.Source

if(args.length>0){

for( line <- Source.fromFile(args(0)).getLines())   

println(line.length+" "+ line)

}

else Console.err.println("Please enter filename")

可以看到Scala引入包的方式和Java类似,也是通过import语句。文件相关的类定义在scala.io包中。 如果需要引入多个类,Scala使用 “_” 而非 “*”.

上一篇下一篇

猜你喜欢

热点阅读