/* 阅读建议 —— 读本文之前,
* 假设你使用过java,假设你使用过python
* 假设你了解不同的编程语言有不同的特性和使用场景
*/
首先,不要被SCALA的奇葩语法吓到了!scala也是一种函数式编程,它把java语言脚本化了,给人的感觉就是“所见即所得”,这一特性类似于Linux下的bash脚本,还有python语言也是脚本语言。
scala结合了java和python的优点,能静能动,很灵活。scala的语法很简洁,功能也很强大,但是代码很精简。实现同样的功能,代码量比java语言要少很多。scala代码的可读性也不错,虽然scala语法给人的感觉诡异、奇葩,但是熟悉之后就不奇葩了呀,就像你刚开始学C语言或者Java的时候也觉得语法诡异,但是熟悉之后就不觉得诡异了呀,对不对?
个人来说,我更愿意使用scala来写spark的应用。
————————————————————
【定义函数,创建函数】
(1)定义函数时带小括号,但是小括号里无参,那么调用此函数时带不带括号都OK,
scala解释器示例:
Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_111).
Type in expressions for evaluation. Or try :help.
// 函数名带小括号, 无参。冒号: 声明了返回值类型为字符串
scala> def func1(): String = {
| var s = "hello scala~"
| s
| }
func1: ()String // 等号=意味着函数有返回值,若无等号则返回空(没有返回值)
scala> func1
res11: String = hello scala~
scala> print(func1)
hello scala~
scala> print(func1())
hello scala~
(2)定义函数时不带小括号,那么调用此函数时就不可以带小括号!
在scala解释器的示例如下:
scala> def func2: String = {
| var s2 = "hello again"
| s2
| }
func2: String
scala> print(func2)
hello again
scala> print(func2())
<console>:13: error: not enough arguments for method apply: (index: Int)Char in class StringOps.
Unspecified value parameter index.
print(func2()) //定义函数时无(),调用时用()则报错!
^
(3)给函数传参
(3.1)按照定义函数时的参数顺序逐个传入。 IntelliJ IDEA的完整示例如下
object TestClosPack {
def main(args: Array[String]){
val str = test_args(2,1) //调用函数,按定义时的参数顺序传参
for(s <- str){print(s)}
}
def test_args(a:Int, b:Int)={"|scala|"* (a+b)}
}
输出结果:
|scala||scala||scala|
(3.2)按照参数名称,但不按照参数顺序传入. IntelliJ IDEA的示例如下
object TestClosPack {
def main(args: Array[String]){
val str = test_args(b=1,a=2) //调用函数,指定参数名称, 但不按照定义时的顺序传参
for(s <- str){print(s)}
}
def test_args(a:Int, b:Int)={"|scala|"* (a+b)}
}
输出结果:
|scala||scala||scala|
(4)匿名函数
语法:使用=>符号。箭头的左边是参数列表,右边是函数体,参数的类型可省略,Scala的类型推测系统会推测出参数的类型。使用匿名函数能让我们的代码变得简洁。
示例一:指定参数类型
// (a: Int, b:Int) => a * b返回一个匿名函数,把此函数赋给变量multiply
scala> var multiply = (a: Int, b:Int) => a * b
multiply: (Int, Int) => Int = <function2>
scala> multiply(2,4)//调用匿名函数时,要加小括号,在小括号里面传参
res16: Int = 8
示例二:省略参数
scala> var currentUser = () => System.getProperty("user.name") //获取此系统的当前用户
currentUser: () => String = <function0>
scala> currentUser()
res21: String = tonykidkid
scala> var currentMillsTime = () => System.currentTimeMillis //获取当前系统(毫秒)时间戳
currentMillsTime: () => Long = <function0>
scala> currentMillsTime()//调用匿名函数时,要加小括号
res22: Long = 1492871662593
————————————————————
【函数的返回值问题】
1,声明函数的返回值类型,则在函数返回时一定要符合返回类型
2,可以不声明函数返回值类型,scala解释器会自动推断返回的数据类型
3,递归函数必须显式声明返回的类型
4,定义函数时没有等号= 则没有返回值。这类函数相当于一个执行过程
1,示例:
scala> def func3(a:Int) :Int = {
| a+10 // 两个整数相加,结果还是整数
| }
func3: (a: Int)Int // 意思是:形式参数的数据类型是Int,返回类型是Int
scala> func3(5)
res16: Int = 15
scala> print(func3(5))
15
scala> def func4(a:Int) :Int = { //以“: Int”方式声明了返回值类型为整数
| a + "10"
| }
<console>:12: error: type mismatch;
found : String
required: Int
a + "10" //错误:整数和字符串相加得到字符串,但是函数要求返回值类型是整数
^
2,定义函数时不声明返回类型 示例:
scala> def func5(a:Int) = {//未声明返回的类型,则自动推断,若无法推断则会报错
| a + "10"
| }
func5: (a: Int)String // 意思是:形式参数的数据类型是Int,返回类型是String
scala> func5 (3)
res18: String = 310
scala> :type func5(3) //查看数据类型使用:type命令
String
3,递归函数必须声明返回值类型,以斐波那契数列为例:
/**斐波那契数列 ——第一个数是0,第二个数是1,
* 从第三个数开始,每个数都是它前两个的和: 0,1,1,2,3,5,8,13,21...
*/
scala> def fiboNums(num: Int): Int = { //递归函数必须声明返回值类型
| if (num == 1) {return 0}
| if (num == 2) { 1}
| else {
| var res = fiboNums(num - 2) + fiboNums(num - 1)
| res
| }
| }
fiboNums: (num: Int)Int
scala> fiboNums(5)
res0: Int = 3
//非递归函数可以不声明返回类型
scala> def sum_fibo_nums(num: Int) = {
| import scala.collection.mutable.ArrayBuffer
| var list_fibo = ArrayBuffer[Int]()
| list_fibo.clear()
| for (n <- 1 to num) {
| var res = fiboNums(n)
| printf("第%d个斐波那契数: %d\n", n, res)
| list_fibo += res //把每个数都追加到数组中
| }
| printf("前%d个斐波那契数的和为:%d",num,list_fibo.sum)
| }
sum_fibo_nums: (num: Int)Unit
scala> fiboNums(5)
res0: Int = 3
scala> sum_fibo_nums(5)
第1个斐波那契数: 0
第2个斐波那契数: 1
第3个斐波那契数: 1
第4个斐波那契数: 2
第5个斐波那契数: 3
前5个斐波那契数的和为:7
4,示例
scala> def no_equals_symbol(a:Int, b:Int) {
| var c = a + b
| c
| }
no_equals_symbol: (a: Int, b: Int)Unit //定义函数时无等号=,则无返回值
scala> print(no_equals_symbol(1,2))
()
————————————————————
【scala如何解析函数的参数?两种方式】
1,传值调用(call-by-value):进入函数内部前,就已将参数表达式的值计算完毕
传值调用最好理解,就是在定义函数时声明参数及其数据类型,这意味着调用此函数时必须传一个确定类型的值作为参数
语法:使用 : 符号来设置传值调用,请看示例:
scala> def passvalue(arg:Long) = { //传给函数参数是指定类型的值
| println("In function 'passvalue'")
| println("Value of parameter 'arg' is: " + arg)
| }
passvalue: (arg: Long)Unit
scala> def get_milis_time() = {
| println("get milisecond in function 'get_milis_time()'")
| System.currentTimeMillis
| }
get_milis_time: ()Long
//从输出内容的顺序可知,在进入passvalue函数内部之前,此函数的参数就已经有值了
scala> passvalue( get_milis_time() )
get milisecond in function 'get_milis_time()'
In function 'passvalue'
Value of parameter 'arg' is: 1492838244412
2,传名调用(call-by-name):只有在函数内部才计算参数表达式的值
语法:在变量名和变量的数据类型之间使用=>符号来设置传名调用。在scala解释器示例如下
scala> def passname( para : => Long) = { //传给函数的参数是一个函数
| println("In function 'passname'")
| println("the value of parameter 'para' is: " + para)
| }
passname: (para: => Long)Unit
scala> def get_milis_time() = {
| println("get milisecond in function 'get_milis_time()'")
| System.currentTimeMillis
| }
get_milis_time: ()Long
//表达式的值只有在passname函数内部才计算
scala> passname( get_milis_time() )
In function 'passname'
get milisecond in function 'get_milis_time()'
the value of parameter 'para' is: 1492835629778
3,传值调用和传名调用混合使用
scala> def duplicate(a:String,b:Int, func1:(String,Int)=>String, func2:(String,Int)=>String)= {
| var s = ""
| if (b>0) {
| s = func1(a,b)
| }
| if (b<=0) {
| s = func2(a,b)
| }
| s
| }
duplicate: (a: String, b: Int, func1: (String, Int) => String, func2: (String, Int) => String)String
scala> var f1 = (s:String, i:Int) => s * i
f1: (String, Int) => String = <function2>
scala> var f2 = (s:String, i:Int) => "i<0 Cannot duplicate"
f2: (String, Int) => String = <function2>
scala> duplicate("hi~", 3, f1, f2)
res9: String = hi~hi~hi~
scala> duplicate("hi~", -3, f1, f2)
res10: String = i<0 Cannot duplicate
这中混合使用,其实是一个高阶函数的用法,高阶函数特点是,传给函数的参数类型是函数
————————————————————
【闭包】
闭包是个函数,其返回值由定义在函数外部的一个或多个变量决定
scala> val factor = 3
factor: Int = 3
// 匿名函数里面有个形参I,还有个定义在函数外部的变量factor
scala> val multiply = (I:Int) => I * factor
multiply: Int => Int = <function1>
scala> multiply(2)
res0: Int = 6
在intelliJ IDEA中的完整示例:
object TestClosPack { // scala的这种object用法相当于java中的单实例模式
def main(args: Array[String]){
var factor = 3.14
val multi = (i: Int) => i * factor
println("multi(3) value is:" + multi(3))
println("multi(10) value is: " + multi(10))
}
}
输出:
multi(3) value is:9.42
multi(10) value is: 31.400000000000002
————————————————————
【函数的变长参数】
变长的参数意味着传入函数的参数列表,其长度可变,即可以有1个或多个参数
语法:参数类型后面加一个符号*
示例一:
scala> def get_sum(a:Int*) = {
| var s = 0
| for (x <- a) s += x;
| s
| }
get_sum: (a: Int*)Int
scala> get_sum(1,3,5,7) //可以往里传多个值
res17: Int = 16
示例二:
scala> def get_string(str:String*) = {
| var i = 0
| for (s <- str) {
| println("string value " + i + ": "+ s )
| i += 1
| }
| }
get_string: (str: String*)Unit
scala> get_string("My","name", "is", "Tonykidkid")
string value 0: My
string value 1: name
string value 2: is
string value 3: Tonykidkid
————————————————————
【柯里化的四种写法】
科里化就是把普通的接收2个参数的函数,转化成接收1个参数的函数。调用科里化函数的时候,需要分两步:先传一个参数进去,再紧接着传第二个参数;就是分两次传参每次传1个参数。请看以下代码示例的注释
scala> def replicate(x:Int, y:String) = { //尚未科里化,这是普通的函数写法
| y * x
| }
replicate: (x: Int, y: String)String
scala> replicate(3, "scalA") // 调用普通函数,一次性接收2个参数
res28: String = scalAscalAscalA
//科里化函数的第一种写法
scala> def currying_1(x:Int) = { //不显式声明返回类型,scala会自动推断出返回类型
| y:String => y * x
| }
currying_1: (x: Int)String => String // =>符号意味着返回的是一个函数
// 调用时传入第一个参数3,返回一个新的函数;继续向新函数传入第二个参数”scalA”,返回最终结果
scala> currying_1(3)(“scalA")
res29: String = scalAscalAscalA
// 第二种写法:显式声明返回类型——函数
scala> def curry_2(x:Int) : String => String = { // explicitly declare returned type
| y => y *x
| }
curry_2: (x: Int)String => String // =>符号左边的String对应的是y,=>符号右边的String就对应着y*x
scala> :type curry_2(3)//验证返回类型,确实是个函数
String => String
// 调用curry_2(3)会返回一个新的函数, 该函数以String为参数类型,再调用此函数时要传参
scala> curry_2(3)("scalA ")
res30: String = "scalA scalA scalA "
// 第三种写法
scala> def curry_3rd(x:Int)(y:String) = {
| y * x
| }
curry_3rd: (x: Int)(y: String)String
scala> curry_3rd(3)("good!") //效果等同于上面两种写法
res31: String = good!good!good!
// 第四种写法
scala> def curry_4th(x:Int)(y:String) : String = {
| y * x
| }
curry_4th: (x: Int)(y: String)String
scala> curry_4th(3)("good!")
res35: String = good!good!good!