Scala Related 25 Mar 2014

基本语法

val 类似于java中final变量,一经定义初始化后,就不能改变

val msg = "Hello, world!"

var 定义一般变量,允许其值发生改变

var greeting = "Hello, world!"

定义函数

def max(x: Int, y: Int): Int = {
  if (x > y) x
  else y
}

当函数体只有一行时,可以简写成如下形式

def max2(x: Int, y: Int) = if (x > y) x else y

所有函数都通过def关键词来定义,接下来max是函数名,()中是参数列表,x和y是参数名,其对应的类型,通过:分割,参数列表后紧跟:返回值类型,然后是=函数体

// 定义一个无参无返回值的函数
def greet() = println("Hello, world!") 
// greet: ()Unit

greet:后括号代表无参数,Unit代表没有返回值,类似于java中的void

scala脚本

// hello.scala
println("Hello, world, from a script!")

使用 $ scala hello.scala 运行,结果显示

Hello, world, from a script!
// helloarg.scala
// Say hello to the first argument
println("Hello, "+ args(0) +"!")

使用 $ scala helloarg.scala planet 运行 planet是传递给脚本的参数,结果显示

Hello, planet!

循环和条件(Loop and Condition)

var i = 0
while (i < args.length) {
  println(args(i))
  i += 1
}
 
var i = 0
while (i < args.length) {
  if (i != 0)
    print(" ")
  print(args(i))
  i += 1
}
println()

然而这并不是scala的风格,而是类似于java,C等语言的风格

下面是使用foreach完成循环功能

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

在上面的代码块中,args是一个列表/数组对象,foreach方法中,可以认为是你传递了一个匿名的函数(function literal),它以arg为参数,println(arg)为函数体

function literal

A function with no name in Scala source code, specified with function literal syntax. For example, (x: Int, y: Int) => x + y.

Scala解析器默认将arg当作String类型,如果你需要明确指定,可以采用下面语句

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

如果你希望语句更简洁,当匿名函数的方法体只有一条语句,而且该语句只接收一个参数时,函数可以不指定函数名和参数,上述代码可以改写成下面形式

args.foreach(println)

再看另外一个例子

for (arg <- args)
  println(arg)

<-符号右边的args是列表/数组,而<-符号左边的arg是一个val,而不是var,由于它一直是一个val,所以只需要写成arg即可。

数组(Array)

在Scala中,你可以用new关键词来实例化对象,类实例等,在实例化时,我们可以使用值和类型来初始化它。例如下面例子

val big = new java.math.BigInteger("12345")

上面语句实例化了一个java实例java.math.BigInteger,并且使用“12345”来初始化它。

// Example 6.1
val greetStrings = new Array[String](3)
   
greetStrings(0) = "Hello"
greetStrings(1) = ", "
greetStrings(2) = "world!\n"
   
for (i <- 0 to 2)
  print(greetStrings(i))

上面例子中,greetStrings是一个Array[String]类型(an “array of string”)的实体,并且使用3来初始化该实体,表明该数组的长度为3,

如果你希望明确指定greetStrings的类型,可以使用如下语句

val greetStrings: Array[String] = new Array[String](3)

在Example 6.1接下来三行,为greetStrings填充数据,和Java不同的是,greetStrings(0) = "Hello",Scala使用了圆括号(),而不是方括号[],由于greetStrings是一个val变量,所以它是不能变化的,你不能将它重新赋予另外一个不同的数组,但你可以改变它元素的值,数组本身是可变的。 接下来最后两行,第一行中显示了Scala的一个重要的规则,如果一个方法只接收一个参数,你可以不用点和括号来调用,在这个例子中,0 to 2可以转换成(0).to(2)to方法是Int实体0的一个方法,它接收一个Int参数(2)

  注意,该语法只能用在明确指定了方法的拥有者,例如你不能使用类似与“print 10”的语句,但是“Console print 10”是合法的

Scala从技术上讲是没有操作符重载的,因为它没有传统意义上的操作符。而类似于+,-,*和/等符号都可以作为方法名,当你使用1+2的语句时,你实际上是在调用Int对象1的+方法,而2是传递给该方法的参数。所以可以将1+2改写为方法调用的形式,(1).+(2)

另外一个重要的特性表明了数组的访问为什么不是用方括号,而是用圆括号。当你在一个对象或实例上使用圆括号时,并为它传递了1个或多个参数,类似于greetStrings(1),Scala会将该语句转化成greetStrings.apply(1),所以在Scala中,数组的元素访问实际上也是一个方法调用。类似的,

greetStrings(0) = "Hello"

可以转化为

greetStrings.update(0, "Hello")

上面Example 6.1可以转化为方法调用的形式:

val greetStrings = new Array[String](3)
   
greetStrings.update(0, "Hello")
greetStrings.update(1, ", ")
greetStrings.update(2, "world!\n")
   
for (i <- 0.to(2))
  print(greetStrings.apply(i))

另外Scala还提供了数组的初始化的简单形式,

val numNames = Array("zero", "one", "two")

上面语句中,Array实际上是一个名为Arrayobject,而不是类实例,而上面语句实际上是调用的Array对象的apply方法,该方法是一个工厂方法,实例化了一个Array类型的实体,并返回该实体。该apply方法接收一系列的值用于填充数组。上面语句还可以改写为

val numNames2 = Array.apply("zero", "one", "two")

列表(List)

val oneTwoThree = List(1, 2, 3)

上面已经提到,Scala中的数组Array是可变的,而列表List是不可变的,这一点与Java中的List不同,Java中的List是可变的。

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(1, 2) and List(3, 4) were not mutated.

Thus, List(1, 2, 3, 4) is a new list.

由于List是不可变的,所以它的行为String比较类似。List拥有一个名为“:::”的方法,它将创建一个新的List并将两个List的内容依次填充到新List

另外一个List常用的方法是“::”

val twoThree = List(2, 3)
val oneTwoThree = 1 :: twoThree
println(oneTwoThree)

程序运行结果是

List(1, 2, 3)

“::”方法是在List前添加一个元素,在上面例子里就是在twoThree添加一个元素1。“::”是一个右运算符,它的拥有者是方法右边的twoThree,而不是1。有一个简单的规则来,如果一个方法被当作操作符来使用,那么它是从左到右调用,如a * b,就是a.*(b),而当方法以":"结尾时,该方法从右往左调用,如下面例子,输出结果同上面的例子,Nil是一个空列表,再其前面依次添加3,2,1,最后形成List(1,2,3),":::"也是从右向左调用。

val oneTwoThree = 1 :: 2 :: 3 :: Nil
println(oneTwoThree)

一些List的方法和应用

What is it?What it does?
List() or NilThe empty List
List("Cool", "tools", "rule")Creates a new List[String] with the three values "Cool", "tools", and "rule"
val thrill = "Will" :: "fill" :: "until" :: NilCreates a new List[String] with the three values "Will", "fill", and "until"
List("a", "b") ::: List("c", "d")Concatenates two lists (returns a new List[String] with values "a", "b", "c", and "d")
thrill(2)Returns the element at index 2 (zero based) of the thrill list (returns "until")
thrill.count(s => s.length == 4)Counts the number of string elements in thrill that have length 4 (returns 2)
thrill.drop(2)Returns the thrill list without its first 2 elements (returns List("until"))
thrill.dropRight(2)Returns the thrill list without its rightmost 2 elements (returns List("Will"))
thrill.exists(s => s == "until")Determines whether a string element exists in thrill that has the value "until" (returns true)
thrill.filter(s => s.length == 4)Returns a list of all elements, in order, of the thrill list that have length 4 (returns List("Will", "fill"))
thrill.forall(s => s.endsWith("l"))Indicates whether all elements in the thrill list end with the letter "l" (returns true)
thrill.foreach(s => print(s))Executes the print statement on each of the strings in the thrill list (prints "Willfilluntil")
thrill.foreach(print)Same as the previous, but more concise (also prints "Willfilluntil")
thrill.headReturns the first element in the thrill list (returns "Will")
thrill.initReturns a list of all but the last element in the thrill list (returns List("Will", "fill"))
thrill.isEmptyIndicates whether the thrill list is empty (returns false)
thrill.lastReturns the last element in the thrill list (returns "until")
thrill.lengthReturns the number of elements in the thrill list (returns 3)
thrill.map(s => s + "y")Returns a list resulting from adding a "y" to each string element in the thrill list (returns List("Willy", "filly", "untily"))
thrill.mkString(", ")Makes a string with the elements of the list (returns "Will, fill, until")
thrill.remove(s => s.length == 4)Returns a list of all elements, in order, of the thrill list except those that have length 4 (returns List("until"))
thrill.reverseReturns a list containing all elements of the thrill list in reverse order (returns List("until", "fill", "Will"))
thrill.sort((s, t) => s.charAt(0).toLowerCase < t.charAt(0).toLowerCase) Returns a list containing all elements of the thrill list in alphabetical order of the first character lowercased (returns List("fill", "until", "Will"))
thrill.tailReturns the thrill list minus its first element (returns List("fill", "until"))

元组(Tuple)

另外一个有用的容器类型是元组tuple。类似于List,元组是不可变的,但和List不同的是Tuple可以存放不同类型的数据,而List[Int]只能存放Int类型的数据,Tuple在你希望在一个方法中返回多个值时,比较有用。在Java中,如果你希望返回多个值,你可能会创建一个Java Bean来保存这几个值,然后方法返回该Bean,但在Scala中你只需要使用Tuple即可,一旦创建了Tuple,你可以使用基于1的顺序索引访问Tuple的元素。

val pair = (99, "Luftballons")
println(pair._1)
println(pair._2)

第一行构建了一个Tuple,它有两个元素,一个是Int类型的99,一个String类型的Luftballons,第二行代码中的“.”与方法调用和属性访问时的”.”一致,_1_2是元组pair的两个字段,你可以通过pairt._1的形式来访问对应字段。一个Tuple的类型实际上取决于元素的个数和元素的类型,(99, “Luftballons”)的实际类型是Tuple2[Int, String],另外,这里访问元组元素的方式不和List一样,采用list(7),是由于apply方法总是返回相同类型的值,而元组内的元素类型不一致,所以采用了字段访问的方式。

Set and Map

Case Classes

当定义一个case class时,scala编译器会自动做以下事情:

  • 构造方法中得每一个参数都会默认被声明为val, 除非你显式声明为var(不建议)
  • apply方法自动被添加到伴生对象中,这样,你可以使用People("mengke", "M")来构建实例,而不必使用new
  • 提供一个unapply方法用于pattern match
  • 提供toString, equals, hashCode以及copy方法

Scala程序开发人员的态度

  注意,该语法只能用在明确指定了方法的拥有者,例如你不能使用类似与“print 10”的语句,但是“Console print 10”是合法的