写在开头,今年不经意间接触到了scala语言,之前一直在使用java语言,如今对scala比较有兴趣,最近用业余时间在学习这方面知识,已经看完《快学scala》正在看《scala编程》,这边文章是我在其余我的博客中看到的,感受对我理解scala中函数式编程颇有帮助,因此想翻译过来,让本身对这内容理解更透彻些。html
附上博客原文地址:Scala Functions vs Methods 如下是本文的翻译,不足之处还望指出帮助改正:java
scala中既有函数也有方法。大多数时候,咱们忽略了二者之间的不一样,可是有时咱们必须面对它们不是一样一件事这个事实。es6
在个人scala基本语法中我有提过在讨论中我使用方法和函数互相替换使用。那是简单化的。在大多数场景下,你能够忽略函数和方法二者之间的区别,仅仅把他们当作同一件事就能够,可是偶尔你可能会在某个场景下遇到他们不一样之处。与咱们对待质量和重量的见解十分类似。在咱们平常在地球表面生活,咱们把二者当作能够互换的单元,1Kg与2.2磅相同。可是它们也不是彻底同样,当宇航员在月球的表面行走,他的质量(千克)没有改变,可是他的重量(磅)已经变成在地球的六分之一了。编程
在千克与磅的对比中,相比你在月球表面走动,你更有可能遇到在某个场景下区分scala中函数和方法不一样之处。因此何时忽略二者之间的不一样,何时你又须要注意二者之间的不一样呢?一旦你理解这不一样之处你就能够回答这个问题了。app
一个scala 方法,在java中是类的一部分,它有一个名字,一个签名,可选的注解 和字节码。函数式编程
在scala中一个函数是一个完整的object,在scala中有一系列的特质表明各类各样的带参数的函数:Function0, Function1, Function2等等。 做为一个混入了这些特质之一的类的实例化,一个函数object有许多方法。这些方法之一就是apply方法,这个方法包含了实现这个函数的代码。 scala中apply有特殊的语法:若是你写一个函数名紧接着是一个带一系列参数列表的括号(或者仅有不带参数的括号),scala会将调用转换成对应名的object的apply方法。当咱们建立一个变量,值是一个函数的object以后咱们引用这个带着括号的变量,它将会自动转成这个函数object的apply方法的调用(译者注:看过scala语法的应该知道,就是隐式的调用了apply方法,这里翻译的有点拗口)。函数
当咱们把一个方法当作一个函数,好比咱们把它赋值给一个变量,scala确实会建立一个函数object,其中的apply方法调用原始的方法,而且是将这个object赋值给这个变量。定义一个函数的对象,把它赋值给一个变量,由于须要定义一个功能上与原始方法等价的函数,并实例化这个函数对象,这种方式的开销会更加消耗内存。所以,你不会想让每个方法都变成一个函数;可是函数会赋予你更增强大的功能,这功能不只仅是方法 同时在某些状况下所提供的功能值的咱们使用更多的内存(译者注:丫的,不直接说明白具体什么地方。哈哈,其实在后面)。学习
让咱们看看这个机制的详细细节。建立一个test.scala文件,内容以下:测试
class test { def m1(x:Int) = x+3 val f1 = (x:Int) => x+3 }
用scalac编译这个文件,获得编译后的文件,scala建立了两个文件:test.class 和 test$$anonfun$1.class。这个多余的奇怪文件是函数对象的匿名类,是scala建立用来对应赋值给f1的函数表达式函数。若是在你的测试类中使用更多的函数表达式,一样将会有更多的匿名类产生,即便你再一次将一样的函数表达式写了一遍。this
若是你用javap
运行这个测试类,你将会看到:
Compiled from "test.scala" public class test extends java.lang.Object implements scala.ScalaObject{ public test(); public scala.Function1 f1(); public int m1(int); public int $tag() throws java.rmi.RemoteException; }
使用javap
运行test$$anonfun$1
函数类,产出:
Compiled from "test.scala" public final class test$$anonfun$1 extends java.lang.Object implements scala.Function1,scala.ScalaObject{ public test$$anonfun$1(test); public final java.lang.Object apply(java.lang.Object); public final int apply(int); public int $tag() throws java.rmi.RemoteException; public scala.Function1 andThen(scala.Function1); public scala.Function1 compose(scala.Function1); public java.lang.String toString(); }
这个类实现了Function1的接口,咱们知道那是带一个参数的函数。你能够看到这个类中包含一把方法,包含apply方法。
你也能够经过使用一个存在的方法来定义一个函数,引用函数名 接着一个空格和一个下划线。修改test.scala 加上另外一行:
class test { def m1(x:Int) = x+3 val f1 = (x:Int) => x+3 val f2 = m1 _ }
这m1 _
语法 告诉scala 把m1
当作一个函数而不是经过调用那个方法来使用产生的值。做为另外一种选择,你能够详细的声明 类型f2
,这样你不须要包含一个末尾下划线:
val f2 : (Int) => Int = m1
通常状况下,若是scala指望一个函数类型,你能够传递一个方法名,让它自动转换成一个函数。例如,若是你调用一个接收一个函数做为它的参数的方法,你能够经过恰当的方法签名来提供这个参数,而不用包含末尾下划线。
回到咱们的测试文件上来,如今当你编译这个test.scala
,将会有两个匿名的类,一个是f1
类一个是f2
类。你可使用两个方式任意一个来定义f2
,都会产生一个惟一的类文件。
若是你使用javap
时 加上选项-c
,你能够获得第二个匿名类的源码,你能够看到测试类中的apply
方法调用了m1
的方法。
public final int apply(int); Code: 0: aload_0 1: getfield #17; //Field $outer:Ltest; 4: astore_2 5: aload_0 6: getfield #17; //Field $outer:Ltest; 9: iload_1 10: invokevirtual #51; //Method test.m1:(I)I 13: ireturn
让咱们瞄准scala的编译器,看看它是如何工做的。在接下来的例子中,加粗的黑体表示输入的文字,紧接着标注的是输出的文字。
scala> def m1(x:Int) = x+3 m1: (Int)Int
scala> val f1 = (x:Int) => x+3 f1: (Int) => Int = <function>
scala> val f2 = m1 _ f2: (Int) => Int = <function>
scala> m1(2) res0: Int = 5
scala> f1(2) res1: Int = 5
scala> f2(2) res2: Int = 5
注意这m1
和f1
的签名是不一样的,(Int)Int
对于一个方法签名来讲表明接受一个Int类型的参数,返回一个Int类型的值,这(Int) =>Int
签名意味着一个接受一个Int参数的函数,返回一个Int值。
在这点上,咱们彷佛有一个方法m1
和两个函数f1
和f2
,他们的功能都是同样的。可是f1
和f2
确实是两个变量,他们是由一个是实现了Function1
接口的生成类生成的实例。实例对象有不少m1
没有的方法。
scala> f1.toString res3: java.lang.String = <function> scala> (f1 andThen f2)(2) res4: Int = 8
由于m1
自己就是方法,不一样于f1
,你不能在它上面调用方法:
scala> m1.toString <console>:6: error: missing arguments for method m1 in object $iw; follow this method with `_' if you want to treat it as a partially applied function m1.toString ^
注意,每一次你引用一个方法做为一个函数,scala都会建立一个单独的对象。
scala> val f3 = m1 _ f3: (Int) => Int = <function> scala> f2 == f3 res6: Boolean = false
即便f2
和f3
都引用了m1
,二者作的事情是同样的,可是在scala中它们不认为是相等的,由于默认继承的比较方法是经过惟一标识去比较的,这是两个不一样的对象。若是你想让两个函数值相等,你须要确保引用它们引用相同的函数对象。
scala> val f4 = f2 f4: (Int) => Int = <function> scala> f2 == f4 res7: Boolean = true
这有几个例子来代表表达式m1 _
事实上是一个函数对象:
scala> m1 _ res8: (Int) => Int = <function> scala> (m1 _).toString res9: java.lang.String = <function> scala> (m1 _).apply(3) res10: Int = 6
scala 2.8.0版本中,表达式(m1 _)(3)
也会返回相同的值(之前的版本中有一个bug,会引发类型没匹配的错误)
方法与函数之间还有一些其余的不一样之处,方法能够是一个类型参数化,可是一个匿名的函数则没法作到:
scala> def m2[T](x:T) = x.toString.substring(0,4) m2: [T](T)java.lang.String scala> m2("abcdefg") res11: java.lang.String = abcd scala> m2(1234567) res12: java.lang.String = 1234
可是,若是你愿意将你的函数定义为一个明确的类的话,你一样能够类型参数化它:
scala> class myfunc[T] extends Function1[T,String] { | def apply(x:T) = x.toString.substring(0,4) | } defined class myfunc scala> val f5 = new myfunc[String] f5: myfunc[String] = <function> scala> f5("abcdefg") res13: java.lang.String = abcd scala> val f6 = new myfunc[Int] f6: myfunc[Int] = <function> scala> f6(1234567) res14: java.lang.String = 1234
因此让咱们继续,经过将除以2.2,将磅转换成千克(除非你是一个宇航员),可是当你在scala中混用函数和方法时,必定要记住他们不是在作同一件事。