8.4 使用List
Set
和Map
都有可变和不变的实现,List
与它们不同,只有不变实现。通过使用head
方法,Scala将访问List
的第一个元素变得更容易更快速。除了第一个元素之外所有元素都可以用tail
方法访问。访问List
的最后一个元素需要遍历List
,所以,同访问head
和tail
相比,这个操作的代价会更大。因此,List
的大部分操作都是围绕着head
和tail
操作进行构建的。
继续RSS源的例子,我们用List
维护了一个有序的RSS源容器:
val feeds = List("blog.toolshed.com", "pragdave.pragprog.com",
"dimsumthinking.com/blog")
这样就创建了一个List[String]
的实例。可以用从0
到list.length-1
之间的索引访问List
的元素②。访问第一个元素既可以用feeds(0)
,也可以用head()
:
②调用
feeds(1)
,用的是List
的apply()
方法。
println("First feed: " + feeds.head)
println("Second feed: " + feeds(1))
上面代码的输出如下:
First feed: blog.toolshed.com
Second feed: pragdave.pragprog.com
如果要在前面加个元素,也就是说,将其置于List
的前端,可以用特殊的方法::()
。a :: list
可以读作“在List
前添加a
”。这个方法是一个List
的操作,虽然List
在操作符之后;参见8.4节“方法名约定”,了解其运作的细节。
val prefixedList = "forums.pragprog.com/forums/87" :: feeds
println("First Feed In Prefixed: " + prefixedList.head)
上面代码的输出如下:
First Feed In Prefixed: forums.pragprog.com/forums/87
假定我们要把一个List
,比如说listA
,附加到另一个后面,比如说List
。用:::()
方法就可以把一个list
附加到listA
前。代码是这样的:list ::: listA
,可以读作“在listA
前添加list
”。因为List
是不变的,所以这两个List
都不会受到影响。Scala
会根据二者的元素创建出一个新的List
。下面是个附加的例子:
val feedsWithForums =
feeds ::: List("forums.pragprog.com/forums/87", "forums.pragprog.
com/forums/55")
println("First feed in feeds with forum: " + feedsWithForums.head)
println("Last feed in feeds with forum: " + feedsWithForums.last)
输出如下:
First feed in feeds with forum: blog.toolshed.com
Last feed in feeds with forum: forums.pragprog.com/forums/55
再说一遍,:::()
方法调用的是操作符后面那个List
的方法。
要把元素添加到List
后面,也可以用相同的:::()
方法。先把要添加的元素放入一个List
,而后把原来的List
放在它前面:
val appendedList = feeds ::: List("agiledeveloper.com/blog")
println("Last Feed In Appended: " + appendedList.last)
输出应该是这样:
Last Feed In Appended: agiledeveloper.com/blog
注意,把一个元素或一个List
加到另一个List
的后面,实际上是调用后面那个List
的前缀运算符。这么做的原因是访问List
的头元素比遍历到最后一个元素快得多。得到相同结果,却有更好的性能。
用filter()
方法可以选择出满足某些条件的RSS源,用forall()
方法可以检查是否所有RSS源都满足某一特定条件,而想了解是否存在RSS源满足特定条件,可以用exists()
。
println("Feeds with blog: " + feeds.filter( _ contains "blog" ).
mkString(", "))
println("All feeds have com: " + feeds.forall( _ contains "com" ))
println("All feeds have dave: " + feeds.forall( _ contains "dave" ))
println("Any feed has dave: " + feeds.exists( _ contains "dave" ))
println("Any feed has bill: " + feeds.exists( _ contains "bill" ))
结果如下:
Feeds with blog: blog.toolshed.com, dimsumthinking.com/blog
All feeds have com: true
All feeds have dave: false
Any feed has dave: true
Any feed has bill: false
假定我们要知道显示每个RSS源名字所需的字符数,可以使用map()
方法处理每个元素,获得一个结果的List
,如下:
println("Feed url lengths: " + feeds.map( _.length ).mkString(", "))
结果如下:
Feed url lengths: 17, 21, 23
如果对所有RSS源总的字符数感兴趣,可以用foldLeft()
,像这样:
val total = feeds.foldLeft(0) { (total, feed) => total + feed.length }
println("Total length of feed urls: " + total )
上面代码的输出如下:
Total length of feed urls: 61
注意,虽然上面代码执行了求和,但却没有处理任何可变的状态。这是纯函数式的风格。随着方法在List
的元素里前进,会叠加一个新更新的值,不过,这并不会修改任何东西。
foldLeft()
方法会从List
的最左端开始为List
的每个元素调用给定的函数值(代码块)。它会传递两个参数给函数值。第一个参数是对之前元素执行函数值的部分结果。(这就是它称为折叠(folding)的原因——好像将List
叠入这些计算的结果里。)第二个参数是List
的元素。部分结果的初始值是方法的参数(本例是0
)。foldLeft()
方法形成了一个元素链,从左边开始,将函数值计算的部分结果从一个元素带入下一个。类似的,foldRight()
做相同的事,不过是从右边开始。
为了让上面的方法更简洁一些,Scala提供了另一个方法③。/:()
方法等价于foldLeft()
,\:()
等价于foldRight()
。用/:
改写上面的代码,如下:
③你要么跟我一样爱它的简洁,要么恨它。我认为这里没有中间地带。
val total2 = (0 /: feeds) { (total, feed) => total + feed.length }
println("Total length of feed urls: " + total2 )
上面代码的输出如下:
Total length of feed urls: 61
多使用一些Scala的习惯用法,可以让代码更简洁,如下:
val total3 = (0 /: feeds) { _ + _.length }
println("Total length of feed urls: " + total3 )
输出如下:
Total length of feed urls: 61
这里已经展示了List
的一些有趣的方法。List
还有几个方法,可提供更多的能力。更完整的文档,请参考附录A。