您的位置:

List.foreach详解

一、List.foreach数据表

List.foreach是Scala集合库中非常常用的方法,在列表、数组、集合等数据结构上都得到了广泛应用。这个方法非常重要,因此我们建立一张表格来展示List.foreach在不同数据结构上的表现:

数据结构 表现
List 对List中的每个元素执行指定的操作
Array 对Array中的每个元素执行指定的操作
Map 对Map中的每个键值对执行指定的操作
Set 对Set中的每个元素执行指定的操作
Stream 对Stream中的每个元素执行指定的操作

二、List.foreach和for循环

Scala中也可以使用for循环对列表进行迭代,然而使用List.foreach方法要比for循环更为简洁、优美。下面是一个简单的示例:

val list = List(1, 2, 3, 4, 5)
list.foreach(e => println(e))
for (e <- list) println(e)

list.foreach(e => println(e))的意思是对list中的每个元素e执行println(e)操作。而for循环则要写成for(e <- list) println(e)。看起来,List.foreach要比for循环更加简洁明了。

三、List.foreach和Stream.foreach

Scala标准库中,只有Stream使用了类似于惰性求值的技术,其他的集合都是严格求值,但是在不同情境下,Stream的惰性求值有时可能会造成一定的影响。举个例子,让我们来看一下对一个List和一个Stream进行操作:

val list = List(1,2,3,4,5,6,7,8,9,10)
val stream = Stream.from(1).take(10)
list.foreach(println)
stream.foreach(println)

上述代码会输出列表中的十个数和Stream中的无限序列,这种情况下使用List和Stream的foreach方法没有什么区别。但是,让我们考虑对这两种数据结构进行筛选操作的情况:

val list = List(1,2,3,4,5,6,7,8,9,10)
val stream = Stream.from(1)
println(list.filter(_%2 == 0))
println(stream.filter(_%2 == 0).take(10))

上述代码会筛选出列表中的偶数,并输出以2为步长的无限序列的前10个偶数。这里,Stream的惰性求值机制使得其可以对无限序列进行操作,而对于列表,则需要将整个列表筛选一遍,再输出结果。因此,当需要对无限或非常大的序列进行操作时,使用Stream.foreach方法更为合适。

四、List.foreach中的break

Scala中,List.foreach方法是不支持break的,但是我们可以通过抛异常来中断foreach的执行,这个做法也被称为“异常跳转”。下面是一个示例:

import scala.util.control.Breaks._
val list = List(1,2,3,4,5,6,7,8,9,10)
breakable {
  for (i <- list) {
    if (i > 5) break()
    println(i)
  }
}

这段代码会输出1至5的整数。breakable方法会将其内部的代码块作为一个整体,在内部执行break方法时,将抛出一个BreakControl异常,从而中断循环。然而,这种做法的可读性和健康性都值得商榷,因为它的行为偏向于不稳定和难以维护。

五、List.foreach跳出循环

如果我们需要在List.foreach循环中跳出循环,可以使用return语句。下面是一个示例:

val list = List(1,2,3,4,5,6,7,8,9,10)
list.foreach(item => {
  if (item == 5) return
  println(item)
})

上述代码将输出整数1到4,当循环执行到5时,会跳出循环而直接返回上层函数。然而在使用return语句时,必须将其放在foreach方法的代码块中,在Scala中即便在lambda式中使用return也会直接报错。

六、List.foreach移除对象

在遍历一个List时,如果需要移除某个元素,可以使用List.filterNot方法或者List.cloneDropWhile方法。下面是一个示例:

val list = scala.collection.mutable.ListBuffer(1,2,3,4,5)
list.foreach(item => {
  if (item % 2 == 0) list -= item
})
println(list.toList)

上述代码中我们将1至5的整数存到ListBuffer中,遍历这个ListBuffer,如果其中的一个数是偶数,则将其从ListBuffer中移除。最终将剩余的数字输出。 ListBuffer支持移除元素的操作,这样遍历时就可以方便的实现对元素的移除操作。

七、List.foreach详解

在Scala中用foreach方法来遍历一个List的元素十分简单易懂,即:对于一个List列表,我们可以对它使用foreach方法来遍历其中的每个元素,并对每个元素执行一个指定的操作。下面是一个示例:

  val list = List("apple","banana","orange","watermelon")
  list.foreach(fruit => println(fruit))

除了上述的语法外,Scala也允许我们通过方法引用的方式来指定操作。通常我们使用lambda表达式来指定一个操作,但我们也可以使用方法引用。举个例子,如果我们定义如下的一个方法:

  def printFruit(fruit: String) = println(fruit)

那么我们就可以通过方法名的方式来引用它,并在foreach方法中使用

  val list = List("apple","banana","orange","watermelon")
  list.foreach(printFruit)

八、List.foreach能用break中断吗

前文提到,List.foreach方法并不支持break的使用。但我们也可以借助Scala中的一些函数式方法来将foreach转为其他形式的方法。例如可以使用takeWhile来实现循环终止的效果。

val list = List(1,2,3,4,5,6,7,8,9,10)
list.takeWhile(item => {
  if (item > 5) false
  else {
    println(item)
    true
  }
})

上述代码输出了1~5的整数,并在6处终止了循环。其中,takeWhile方法的作用是保留满足条件的元素,一旦遇到不满足条件的元素就结束整个遍历。我们可以将截止策略存放在takeWhile方法的lambda表达式中,然后在该表达式中通过if-else控制何时退出遍历。这种方式可以避免throw异常的情况,同时代码也更为优美易读。

九、List.foreach内存泄漏

在Scala中,如果在遍历一个List操作时,执行的任务有明显的副作用,并且列表非常长,那么很容易就出现内存泄漏的情况。例如下面这个示例:

val list = List.range(1, 1000000)
list.foreach(item => item * 2)

虽然上述代码只是简单地对每个元素乘2,但在实际执行时,它会消耗掉大量内存。这是因为在Scala中,一般来说,执行一些操作时,都会生成一些中间数据并保留在内存中。而这些中间数据会占用大量的内存。这样的问题可以使用Stream避免,另一种方法是使用Iterator方法,它可以按需生成数据。

val list = List.range(1, 1000000)
list.iterator.foreach(item => item * 2)

上述代码使用了list.iterator方法而不是list.foreach方法,这样就可以实现每次只生成一个元素,在其它地方不存储任何数据。这样,在数据量比较大时,我们可以选择使用Iterator来遍历。