Scala中的Function(函数)和Method(方法)
Posted 未来架构师
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Scala中的Function(函数)和Method(方法)相关的知识,希望对你有一定的参考价值。
Scala中是有Function和Method这两个概念的,在大多数人眼里,它们是同一个概念,实际上它们并非同一概念而是两个不同的概念。就如同质量和重量这两个词的概念,一个人的质量是恒定的(前提是你不长胖也不变瘦,而且各种正常),但是你的重量却是变化的(跟地心引力相关)所以那些个老公或者是男票是有钱人的妹子,你可以带上老公,老公带上钱,去马来西亚,泰国,新加坡,巴西这些靠近赤道的国家游玩,而不是去俄罗斯,冰岛,挪威,加拿大之类的国家游玩。在那里,你的重量会小一些,你会比较轻盈,适合各种跳跃拍照。或者你有更多钱,选择去月球,那就是真真正正的飘起来了。总之,质量跟重量不是同一个概念,只有在传说中的维度为45度的海平面上,你的重量跟质量才是等同的。
作为一个程序猿,你是没多少时间去关心你的质量跟重量的,因为你天天坐着,铁定会胖,你每天很忙也没有时间去不同地方验证你质量不变而重量随之变化。你应该关心的是这两个概念,Function和Method到底有啥区别,因为这关系到你的饭碗,更会直接影响到你的质量。
首先从外貌说起:
Scala中的方法跟java中的方法类似,是类的一个部分。有名字,有参数列表,有返回值类型,有方法体。在Scala中只不过表现形式稍稍不同,就像如下的定义:
def methodName(x: ParameterType1, y: ParameterType2, …): ReturnType = { method body }
Scala中的函数本质上是一个实实际际的对象,Scala中有一系列的trait(类似于java中的接口),代表着具体接受多少参数的函数类型,从Function0一直到Function22,所以你最多只能给一个函数传递22个参数。从后面的文章中,你会了解到方法是可以转化函数的,所以这有可能会导致一个很严重的后果,当然了这是有前提条件的。那就是你很牛逼,居然定义了一个超过22个参数的方法,而且还没有被开除,那么,你定义的这个方法将不能转化为函数。在这些Functionx的trait中,有一个apply方法,你定义的函数的函数体就会出现在apply方法体中,你调用函数实际是调用这个apply方法。函数的定义如下:(x: ParameterType1, y: ParameterType2,…) => { function body }
外表差异对比完之后,开始进行本质分析:
首先给一个示例代码:
class Test {
def m(x: Int) = x + 1
val f = (x: Int) => x + 1
}
在这个类里面,定义了一个名为m的方法,以及一个名为f的变量,该变量存储一个函数。m和f干的都是同一件事,就是返回一个比参数大一的整数。
对Test类进行编译之后,你会发现两个class文件,一个名为Test.class,另一个是Test$$anonfun$1.class。Test.class可以理解,Test$$anonfun$1.class似乎就有点神奇了,这是从哪冒出来的了。为了弄清楚这个问题,让我们对这两个class文件进行反编译,并进行分析。
对Test.class执行反编译之后你会看到如下代码:
public class Test {
public int m(int);
public scala.Function1<java.lang.Object, java.lang.Object> f();
public Test();
}
f变量实际上变成了一个方法,该方法返回一个Function1的对象。
再反编译Test$$anonfun$1.class看看:
public final class Test$$anonfun$1 extends scala.runtime.AbstractFunction1$mcII$sp implements scala.Serializable {
public static final long serialVersionUID;
public final int apply(int);
public int apply$mcII$sp(int);
public final java.lang.Object apply(java.lang.Object);
public Test$$anonfun$1(Test);
}
再执行一下javap -c Test$$anonfun$1.class,看看机器指令:
public final class scalaForImpatient.Test$$anonfun$1 extends scala.runtime.AbstractFunction1$mcII$sp implements scala.Serializable {
public static final long serialVersionUID;
public final int apply(int);
Code:
0: aload_0
1: iload_1
2: invokevirtual #18 // Method apply$mcII$sp:(I)I
5: ireturn
public int apply$mcII$sp(int);
Code:
0: iload_1
1: iconst_1
2: iadd
3: ireturn
public final java.lang.Object apply(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: invokestatic #29 // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
5: invokevirtual #31 // Method apply:(I)I
8: invokestatic #35 // Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
11: areturn
public Test$$anonfun$1(Test);
Code:
0: aload_0
1: invokespecial #42 // Method scala/runtime/AbstractFunction1$mcII$sp."<init>":()V
4: return
}
其实Test$$anonfun$1.class对应的就是Test.class中f方法返回的对象,也就是说函数实际上是一个对象。apply$mcII$sp(int)方法是函数体,apply方法调用了该方法,对函数的调用实际上是调用apply方法。
最后给出一些其他的区别:
一、函数是值,而方法不是。体现为可以将函数赋值给变量,而方法不可以。
val f1 = m //报错
val f1 = m _ //将方法转化为函数,并赋值给变量
二、方法转化为函数:
1.自动转化:
def m(x: Int) = x + 1
List(1, 2, 3).map(m) 等价于 List(1, 2, 3).map(m _)
2.手动转化:
val f1 = m _
三、重载的影响
def increment(n: Int) = n + 1
def increment(n: Int, step: Int) = n + step
这时候使用方法转化为函数时就必须带上类型了
val f1 = increment _: (Int => Int)
val f2 = increment _: ((Int, Int) => Int)
四、函数的equal问题:
val f1 = m _
val f2 = m _
val f3 = f1
f1和f2都是将方法m转化为函数,但是f1 != f2, f1 == f3。所以如果想函数相等
五、参数相关问题:
1、无参方法:
def p = println(“hello, scala”)
val fp = p _
实际调用:
p 或者 p() 或者 fp()
这里还需要注意一点,对方法名的调用就是实际的方法体的调用(当然,有参数的必须传递参数,无参数的可以省略括号),而对函数名的调用仅仅代表函数名本身,不会对函数体进行调用,哪怕是没有参数的函数。如果想达到调用函数体的目的,必须使用()包括参数。
2、多个参数方法转化为多元函数:
def plus(x: Int, y: Int): Int = x + y // plus: (x: Int, y: Int)Int
val fp = plus _ // (Int, Int) => Int = $$Lambda$xxxxxx [这里等同Functionx]
3、多个参数方法边柯里化函数:
def plus(x: Int)(y: Int): Int = x + y // plus: (x: Int)(y: Int)Int
val fp = plus _ // Int => (Int => Int) = $$Lambda$xxxxx[这里等同Functionx]
4、类型参数
在Scala中值是不能有类型的,所以如果将带有类型的方法转换为函数,必须指定具体的参数类型
def id[T](x: T): T = x // id: [T](x: T)T
val f1 = id _ // f1: Nothing => Nothing = $$Lambda$xxxx
调用f1(1)实际是报错的。
val f2 = id[Int] _ // f2: Int => Int = $$Lambda$xxxx
调用f2(1),可以运行。
5、序列参数
对于一个方法,使用了序列参数,转化为函数之后,必须使用Seq对象传递参数。
def exec(as: Int*) = as.sum // exec: (as: Int*)Int
exec(1, 2, 3) //6
def fun = exec _ //fun: Seq[Int] => Int
fun(1, 2, 3) //error
fun(Seq(1, 2, 3)) //6
6、默认参数
方法支持默认参数,但是函数会忽略参数默认值,所以函数不能省略参数。
def exec:(s: String, n: Int = 2) = s * n
exec(“hi”) //hihi
val fun = exec _ //fun: (String, Int) => String = $$Lambda$xxxx
fun(“hi”) //error
fun(“hi”, 2) //hihi
最后给出自己实现函数对象的方式:
val myFunction = (x: Int) => x + 1
等价于实现如下对象
object myFunction extends Function [Int, Int] {
def apply(x: Int) = x + 1
}
最最后声明一下,文章大部分内容来自网络,对,我就是抄的,抄的,抄的,抄了国外又抄国内,但是,我是进行了相应修改并验证。
以上是关于Scala中的Function(函数)和Method(方法)的主要内容,如果未能解决你的问题,请参考以下文章