Scala的下一步
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Scala的下一步相关的知识,希望对你有一定的参考价值。
第七步:带类型的参数化数组
Scala里可以使用new实例化对象或类实例。当你在Scala里实例化对象,可以使用值和类型把它参数化:parameterize。参数化的意思是在你创建实例的时候“设置”它。通过把加在括号里的对象传递给实例的构造器的方式来用值参数化实例。例如,下面的Scala代码实例化一个新的java.math.BigInteger并使用值"12345"参数化:
val big = new java.math.BigInteger("12345")
通过在方括号里设定一个或更多类型来参数化实例。通过在方括号里设定一个或更多类型来参数化实例。代码3.1展示了一个例子。在这个例子中,greetStrings是类型Array[String](字串数组)的值,并被第一行代码里的值3参数化,使它的初始长度为3。如果把代码3.1里的代码作为脚本执行,你会看到另一个Hello, world!的祝词。请注意当你同时用类型和值去参数化实例的时候,类型首先在方括号中出现,然后跟着值在圆括号中。
val greetStrings = new Array[String](3)
greetStrings(0) = "Hello"
greetStrings(1) = ", "
greetStrings(2) = "world!\n"
for (i <- 0 to 2)
print(greetStrings(i))
正如前面提到的,Scala里的数组是通过把索引放在圆括号里面访问的,而不是像Java那样放在方括号里。所以数组的第零个元素是greetStrings(0),不是greetStrings[0]。
这三行代码演示了搞明白Scala如何看待val的意义的重要概念。当你用val定义一个变量,那么这个变量就不能重新赋值,但它指向的对象却仍可以暗自改变。所以在本例中,你不能把greetStrings重新赋值成不同的数组;greetStrings将永远指向那个它被初始化时候指向的同一个Array[String]实例。但是你能一遍遍修改那个Array[String]的元素,因此数组本身是可变的。
代码3.1的最后两行包含一个for表达式用来依次输出每个greetStrings数组元素。
for (i <- 0 to 2) print(greetStrings(i))
这个for表达式的第一行代码演示了Scala的另一个通用规则:如果方法仅带一个参数,你可以不带点或括号的调用它。本例中的to实际上是带一个Int参数的方法。代码0 to 2被转换成方法调用(0).to(2)。请注意这个语法仅在你显示指定方法调用的接受者时才起作用。不可以写 pringln 10,但是可以写成“Console println 10”。
从技术上讲,Scala没有操作符重载,因为它根本没有传统意义上的操作符。取而代之的是,诸如+,-,*和/这样的字符可以用来做方法名。因此,当第一步里你在Scala解释器里输入1 + 2,你实际上正在Int对象1上调用一个名为+的方法,并把2当作参数传给它。如图3.1所示,你也可以使用传统的方法调用语法把1 + 2替代写成(1).+(2)。
这里演示的另一重要思想可以让你看到为什么数组在Scala里是用括号访问的。与Java比,Scala很少有特例。数组和Scala里其他的类一样只是类的实现。当你在一个或多个值或变量外使用括号时,Scala会把它转换成对名为apply的方法调用。于是greetStrings(i)转换成greetStrings.apply(i)。所以Scala里访问数组的元素也只不过是跟其它的一样的方法调用。这个原则不仅仅局限于数组:任何对某些在括号中的参数的对象的应用将都被转换为对apply方法的调用。当然前提是这个类型实际定义过apply方法。所以这不是一个特例,而是一个通则。
与之相似的是,当对带有括号并包括一到若干参数的变量赋值时,编译器将把它转化为对带有括号里参数和等号右边的对象的update方法的调用。例如,
greetStrings(0) = "Hello"
将被转化为
greetStrings.update(0, "Hello")
因此,下列Scala代码与你在代码3.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在对待任何事上追求概念的简洁性,从数组到表达式,包括带有方法的对象。你不必记住太多特例,如Java里原始类型和相应的包装类间的,或者数组和正常的对象间的差别。而且这种统一并未损害重要的性能代价。Scala编译器使用Java数组,原始类型,及可存在于编译完成代码里的原生数学类型。尽管目前为止在这一步里你看到的例子编译运行良好,Scala提供了通常可以用在你真实代码里的更简洁的方法创造和初始化数组。它看起来就像展示在代码3.2中的样子。这行代码创建了长度为3的新数组,用传入的字串"zero","one"和"two"初始化。编译器推断数组的类型是Array[String] ,因为你把字串传给它。
val numNames = Array("zero", "one", "two")
你在代码3.2里实际做的就是调用了一个叫做apply的工厂方法,从而创造并返回了新的数组。apply方法带可变数量个参数被定义在Array的伴生对象:companion object上。。如果你是一个Java程序员,你可以认为这个就像在Array类上调用一个叫做apply的静态方法。更罗嗦的调用同样的apply方法的办法是:
val numNames2 = Array.apply("zero", "one", "two")
第八步:使用List
方法不应该有副作用是函数风格编程的一个很重要的理念。方法唯一的效果应该是计算并返回值。用这种方式工作的好处就是方法之间很少纠缠在一起,因此就更加可靠和可重用。另一个好处(静态类型语言里)是传入传出方法的所有东西都被类型检查器检查,因此逻辑错误会更有可能把自己表现为类型错误。把这个函数式编程的哲学应用到对象世界里意味着使对象不可变。
如你所见,Scala数组是一个所有对象都共享相同类型的可变序列。比方说Array[String]仅包含String。尽管实例化之后你无法改变Array的长度,它的元素值却是可变的。因此,Array是可变的对象。
说到共享相同类型的不可变对象序列,Scala的List类才是。和数组一样,List[String]包含的仅仅是String。Scala的List,scala.List,不同于Java的java.util.List,总是不可变的(而Java的List可变)。更通常的说法,Scala的List是设计给函数式风格的编程用的。创建一个List很简单。代码3.3做了展示:
val oneTwoThree = List(1, 2, 3)
代码3.3中的代码完成了一个新的叫做oneTwoThree的val,并已经用带有整数元素值1,2和3的新List[Int]初始化。因为List是不可变的,他们表现得有些像Java的String:当你在一个List上调用方法时,似乎这个名字指代的List看上去被改变了,而实际上它只是用新的值创建了一个List并返回。比方说,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最常用的操作符是发音为“cons”的‘::’。Cons把一个新元素组合到已有List的最前端,然后返回结果List。例如,若执行这个脚本:
val twoThree = list(2, 3) val oneTwoThree = 1 :: twoThree println(oneTwoThree)
你会看到:
List(1, 2, 3)
注意:表达式“1 :: twoThree”中,::是它右操作数,列表twoThree,的方法。你或许会疑惑::方法的关联性上有什么东西搞错了,不过这只是一个简单的需记住的规则:如果一个方法被用作操作符标注,如a * b,那么方法被左操作数调用,就像a.*(b)——除非方法名以冒号结尾。这种情况下,方法被右操作数调用。因此,1 :: twoThree里,::方法被twoThree调用,传入1,像这样:twoThree.::(1)。
由于定义空类的捷径是Nil,所以一种初始化新List的方法是把所有元素用cons操作符起来,Nil作为最后一个元素。比方说,下面的脚本将产生与之前那个同样的输出,“List(1, 2, 3)”
val oneTwoThree = 1 :: 2 :: 3 :: Nil println(oneTwoThree)
要在最后用到Nil的理由是::是定义在List类上的方法。如果你想只是写成1 :: 2 :: 3,由于3是Int类型,没有::方法,因此会导致编译失。
Scala的List包装了很多有用的方法,表格3.1罗列了其中的一些。列表的全部实力将在第十六章释放。
为什么列表不支持append?
类List没有提供append操作,因为随着列表变长append的耗时将呈线性增长,而使用::做前缀则仅花费常量时间。如果你想通过添加元素来构造列表,你的选择是把它们前缀进去,当你完成之后再调用reverse;或使用ListBuffer,一种提供append操作的可变列表,当你完成之后调用toList。ListBuffer将在22.2节中描述。
表格3.1 类型List的一些方法和作用
方法名 | 方法作用 |
List() 或 Nil | 空List |
List("Cool", "tools", "rule) | 创建带有三个值"Cool","tools"和"rule"的新List[String] |
val thrill = "Will"::"fill"::"until"::Nil | 创建带有三个值"Will","fill"和"until"的新List[String] |
List("a", "b") ::: List("c", "d") | 叠加两个列表(返回带"a","b","c"和"d"的新List[String]) |
thrill(2) | 返回在thrill列表上索引为2(基于0)的元素(返回"until") |
thrill.count(s => s.length == 4) | 计算长度为4的String元素个数(返回2) |
thrill.drop(2) | 返回去掉前2个元素的thrill列表(返回List("until")) |
thrill.dropRight(2) | 返回去掉后2个元素的thrill列表(返回List("Will")) |
thrill.exists(s => s == "until") | 判断是否有值为"until"的字串元素在thrill里(返回true) |
thrill.filter(s => s.length == 4) | 依次返回所有长度为4的元素组成的列表(返回List("Will", "fill")) |
thrill.forall(s => s.endsWith("1")) | 辨别是否thrill列表里所有元素都以"l"结尾(返回true) |
thrill.foreach(s => print(s)) | 对thrill列表每个字串执行print语句("Willfilluntil") |
thrill.foreach(print) | 与前相同,不过更简洁(同上) |
thrill.head | 返回thrill列表的第一个元素(返回"Will") |
thrill.init | 返回thrill列表除最后一个以外其他元素组成的列表(返回List("Will", "fill")) |
thrill.isEmpty | 说明thrill列表是否为空(返回false) |
thrill.last | 返回thrill列表的最后一个元素(返回"until") |
thrill.length | 返回thrill列表的元素数量(返回3) |
thrill.map(s => s + "y") | 返回由thrill列表里每一个String元素都加了"y"构成的列表(返回List("Willy", "filly", "untily")) |
thrill.mkString(", ") | 用列表的元素创建字串(返回"will, fill, until") |
thrill.remove(s => s.length == 4) | 返回去除了thrill列表中长度为4的元素后依次排列的元素列表(返回List("until")) |
thrill.reverse | 返回含有thrill列表的逆序元素的列表(返回List("until", "fill", "Will")) |
thrill.sort((s, t) => s.charAt(0).toLowerCase < t.charAt(0).toLowerCase) | 返回包括thrill列表所有元素,并且第一个字符小写按照字母顺序排列的列表(返回List("fill", "until", "Will")) |
thrill.tail | 返回除掉第一个元素的thrill列表(返回List("fill", "until")) |
第九步:使用Tuple
另一种有用的容器对象是元组:tuple。与列表一样,元组也是不可变的,但与列表不同,元组可以包含不同类型的元素。而列表应该是List[Int]或List[String]的样子,元组可以同时拥有Int和String。元组很有用,比方说,如果你需要在方法里返回多个对象。Java里你将经常创建一个JavaBean样子的类去装多个返回值,Scala里你可以简单地返回一个元组。而且这么做的确简单:实例化一个装有一些对象的新元组,只要把这些对象放在括号里,并用逗号分隔即可。一旦你已经实例化了一个元组,你可以用点号,下划线和一个基于1的元素索引访问它。代码3.4展示了一个例子:
val pair = (99, "Luftballons") println(pair._1) println(pair._2)
以上是关于Scala的下一步的主要内容,如果未能解决你的问题,请参考以下文章