8.4 使用List

SetMap都有可变和不变的实现,List与它们不同,只有不变实现。通过使用head方法,Scala将访问List的第一个元素变得更容易更快速。除了第一个元素之外所有元素都可以用tail方法访问。访问List的最后一个元素需要遍历List,所以,同访问headtail相比,这个操作的代价会更大。因此,List的大部分操作都是围绕着headtail操作进行构建的。

继续RSS源的例子,我们用List维护了一个有序的RSS源容器:

  1. val feeds = List("blog.toolshed.com", "pragdave.pragprog.com",
  2. "dimsumthinking.com/blog")

这样就创建了一个List[String]的实例。可以用从0list.length-1之间的索引访问List的元素②。访问第一个元素既可以用feeds(0),也可以用head()

②调用feeds(1),用的是Listapply()方法。

  1. println("First feed: " + feeds.head)
  2. println("Second feed: " + feeds(1))

上面代码的输出如下:

  1. First feed: blog.toolshed.com
  2. Second feed: pragdave.pragprog.com

如果要在前面加个元素,也就是说,将其置于List的前端,可以用特殊的方法::()a :: list可以读作“在List前添加a”。这个方法是一个List的操作,虽然List在操作符之后;参见8.4节“方法名约定”,了解其运作的细节。

  1. val prefixedList = "forums.pragprog.com/forums/87" :: feeds
  2. println("First Feed In Prefixed: " + prefixedList.head)

上面代码的输出如下:

  1. First Feed In Prefixed: forums.pragprog.com/forums/87

假定我们要把一个List,比如说listA,附加到另一个后面,比如说List。用:::()方法就可以把一个list附加到listA前。代码是这样的:list ::: listA,可以读作“在listA前添加list”。因为List是不变的,所以这两个List都不会受到影响。Scala会根据二者的元素创建出一个新的List。下面是个附加的例子:

  1. val feedsWithForums =
  2. feeds ::: List("forums.pragprog.com/forums/87", "forums.pragprog.
  3. com/forums/55")
  4. println("First feed in feeds with forum: " + feedsWithForums.head)
  5. println("Last feed in feeds with forum: " + feedsWithForums.last)

输出如下:

  1. First feed in feeds with forum: blog.toolshed.com
  2. Last feed in feeds with forum: forums.pragprog.com/forums/55

再说一遍,:::()方法调用的是操作符后面那个List的方法。

要把元素添加到List后面,也可以用相同的:::()方法。先把要添加的元素放入一个List,而后把原来的List放在它前面:

  1. val appendedList = feeds ::: List("agiledeveloper.com/blog")
  2. println("Last Feed In Appended: " + appendedList.last)

输出应该是这样:

  1. Last Feed In Appended: agiledeveloper.com/blog

注意,把一个元素或一个List加到另一个List的后面,实际上是调用后面那个List的前缀运算符。这么做的原因是访问List的头元素比遍历到最后一个元素快得多。得到相同结果,却有更好的性能。

filter()方法可以选择出满足某些条件的RSS源,用forall()方法可以检查是否所有RSS源都满足某一特定条件,而想了解是否存在RSS源满足特定条件,可以用exists()

  1. println("Feeds with blog: " + feeds.filter( _ contains "blog" ).
  2. mkString(", "))
  3. println("All feeds have com: " + feeds.forall( _ contains "com" ))
  4. println("All feeds have dave: " + feeds.forall( _ contains "dave" ))
  5. println("Any feed has dave: " + feeds.exists( _ contains "dave" ))
  6. println("Any feed has bill: " + feeds.exists( _ contains "bill" ))

结果如下:

  1. Feeds with blog: blog.toolshed.com, dimsumthinking.com/blog
  2. All feeds have com: true
  3. All feeds have dave: false
  4. Any feed has dave: true
  5. Any feed has bill: false

假定我们要知道显示每个RSS源名字所需的字符数,可以使用map()方法处理每个元素,获得一个结果的List,如下:

  1. println("Feed url lengths: " + feeds.map( _.length ).mkString(", "))

结果如下:

  1. Feed url lengths: 17, 21, 23

如果对所有RSS源总的字符数感兴趣,可以用foldLeft(),像这样:

  1. val total = feeds.foldLeft(0) { (total, feed) => total + feed.length }
  2. println("Total length of feed urls: " + total )

上面代码的输出如下:

  1. Total length of feed urls: 61

注意,虽然上面代码执行了求和,但却没有处理任何可变的状态。这是纯函数式的风格。随着方法在List的元素里前进,会叠加一个新更新的值,不过,这并不会修改任何东西。

foldLeft()方法会从List的最左端开始为List的每个元素调用给定的函数值(代码块)。它会传递两个参数给函数值。第一个参数是对之前元素执行函数值的部分结果。(这就是它称为折叠(folding)的原因——好像将List叠入这些计算的结果里。)第二个参数是List的元素。部分结果的初始值是方法的参数(本例是0)。foldLeft()方法形成了一个元素链,从左边开始,将函数值计算的部分结果从一个元素带入下一个。类似的,foldRight()做相同的事,不过是从右边开始。

为了让上面的方法更简洁一些,Scala提供了另一个方法③。/:()方法等价于foldLeft()\:()等价于foldRight()。用/:改写上面的代码,如下:

③你要么跟我一样爱它的简洁,要么恨它。我认为这里没有中间地带。

  1. val total2 = (0 /: feeds) { (total, feed) => total + feed.length }
  2. println("Total length of feed urls: " + total2 )

上面代码的输出如下:

  1. Total length of feed urls: 61

多使用一些Scala的习惯用法,可以让代码更简洁,如下:

  1. val total3 = (0 /: feeds) { _ + _.length }
  2. println("Total length of feed urls: " + total3 )

输出如下:

  1. Total length of feed urls: 61

这里已经展示了List的一些有趣的方法。List还有几个方法,可提供更多的能力。更完整的文档,请参考附录A。