Scala中部分应用函数里下划线与占位符的区别
嘿,这个问题我当初刚学Scala的时候也纠结了好久!咱们把这两个例子拆开,一步步搞清楚它们的本质~
一、someNumbers.foreach(println _):方法转函数的ETA展开
首先要明确:println是方法(def定义的),而foreach方法需要的参数是一个函数值(比如Int => Unit类型)。这里的下划线_不是单个参数的占位符,而是Scala里的ETA展开标记——它的作用是把整个println方法“打包”成一个和它参数列表匹配的函数,直接传递给foreach。
和普通参数占位符的区别
如果写成someNumbers.foreach(println(_)),这里的下划线是单个参数的占位符,相当于你手动写了一个匿名函数:
someNumbers.foreach(x => println(x))
而println _是直接把方法转换成函数,相当于告诉Scala:“把这个println方法变成一个可以直接作为参数传递的函数”。虽然这两种写法在这个场景下效果一样,但本质不同:
println(_)是创建了一个新的匿名函数,内部调用println方法;println _是直接将原方法转换为函数值,没有额外的匿名函数包装。
二、val addFunction = add _:同一概念的另一种场景
完全是同一个概念!add是一个方法(def add(x: Int, y: Int) = x + y),而addFunction是一个函数值(类型为(Int, Int) => Int,也就是Scala内置的Function2[Int, Int, Int])。
这里的下划线同样是ETA展开标记,作用是把方法add转换成对应的函数类型,让它可以被赋值给变量。它等价于手动写匿名函数:
val addFunction = (x: Int, y: Int) => add(x, y)
你可以理解为:下划线在这里“替代了整个参数列表”,告诉Scala“按照原方法的参数列表,生成一个对应的函数”。
总结两种下划线用法的核心差异
- 方法转函数(ETA展开):
方法名 _,下划线代表整个参数列表的占位,用于把def定义的方法转换成可以赋值、传递的函数值; - 匿名函数参数占位符:
_(单个或多个),每个下划线对应匿名函数的一个参数,按顺序匹配,比如list.map(_ * 2)等价于list.map(x => x * 2),(a, b) => a + b可以简写为_ + _。
额外补充:Scala有时候会自动做ETA展开,比如someNumbers.foreach(println)不用下划线也能运行,因为编译器能推断出需要把方法转成函数。但在赋值给变量的场景(比如val f = add _),必须加下划线,否则编译器会报错——因为方法不能直接赋值给变量,必须先转成函数值。
内容的提问来源于stack exchange,提问作者Jwan622

