scala函数式编程
函数式编程
静态关键字不是面向对象中的语法,scala中函数可理解为java中的静态方法
scala是完全面向函数的编程语言,scala中的类其实也是一个函数
这里的函数类似于java的静态方法,体现的是功能的封装
java中方法的声明 public static void(返回值类型) 方法名 (参数列表) throws 异常列表{ 方法体 return "123" } java中方法的调用 new User().test() scala中函数的声明: scala中没有public的概念,也没有静态的概念,所以为了和属性区分开,使用def关键字声明 scala中返回值类型需要放置在方法名的后面,使用冒号分隔 scala中参数列表的声明和java一致,但参数声明有变化: 参数名:参数类型。... scala中没有throws关键字,所以函数根本就不需要抛出异常 scala中函数也可以返回结果 // scala中最基本的函数声明 def test( name : String, age : Int ) : String = { // 方法体 println("name = " + name + ", age = " + age) return "123" } // 调用函数 test("zhangsan", 12)
声明
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def main(args: Array[String]): Unit = { def hello(name: String): Unit = { println(s"Hello $name") } hello("smile") //1.无参无返回值加Unit def f1(): Unit = { println("无参无返回值") } //f1() //2.有1个参,无返回值 def f2(name: String): Unit = { println("有1个参,无返回值") } //f2("kris") //3.多个参,无返回值 def f3(name: String, age: Int): Unit = { println("多个参,无返回值") } //f3("kris", 22) //4.无参有返回值 声明一个变量接收函数返回值或者直接打印 def f4(): String = { return "abc" } val result: String = f4() println(result) //println(f4()) //5.有参,有返回值 def f5(name: String): String = { return name } println(f5("kris")) }
def fun1(): String = { return "smile" } //println(fun1()) // 方法中如果使用了return关键字,那么方法一定要有返回值类型 // 如果不使用return关键字,那么返回值类型可以省略 // 方法可以自动推断方法返回值类型,根据方法体逻辑中的最后一行代码的结果来推断,所以可以把return省略掉 def fun2() = { "alex" } //println(fun2()) def fun3() : String = { "kris" } //println(fun3()) ////////////////////////////////return////////////////////////////////////// // 函数如果明确了返回值为Unit,那么方法体中如果使用return不起作用 def fun4(): Unit = { return "abc" } //println(fun4()) //() //如果明确方法没有返回值,也不希望自动推断,那么可以省略 = 等号 def fun5(){ "哈哈" } //println(fun5()) //() // 如果函数没有参数列表,那么参数小括号可以省略,并且调用时,不需要使用小括号 def fun6{ println("省略参数列表") } //fun6 //println(fun6) // 如果方法体的逻辑代码只有一行,那么花括号可以省略,为了区分方法名和方法体,需要使用等号连接 def fun7 = println("Hi") //fun7 //println(fun7) // 之所以使用def关键字来声明函数,就是为了在某些场合和变量区分开 def fun8 = "kris" println(fun8) // 匿名函数 ()->{}
// 在参数类型的后面增加星号,表示可变参数 // 可变参数必须放置在参数列表的后面 def func1(age: Int, name : String* ): Unit ={ println(age + ",\t" + name) } //func1(22, "alex", " smile", "kris") //22, WrappedArray(alex, smile, kris) // 给参数默认值,如果调用方法时,没有传递指定的参数,那么会采用默认值代替 // 如果参数了指定的参数,那么会将默认值覆盖掉 def func2(name : String = "kk", age: Int): Unit ={ println(s"$name, $age") } func2("kris", 22) //kris, 22 def func3(name : String, age : Int): Unit ={ println(s"$name, $age") } // 方法在调用时,传递的参数从左到右依次进行赋值 // 带名参数 func3(age = 21, name = "kk")
函数当成参数--即高阶函数
(参数类型)=>(返回值类型)
// 将函数当成参数 def func1(i: Int): Int ={ 20 + i } def func2(i: Int): Unit ={ 11 + i } def func3(i: Int): Unit ={ println("i:" + i) } // 将符合要求的函数(输入Int,输出Int)作为参数传递给另外一个函数 def func01(f: (Int) => Int): Unit ={ println(f(3)) } func01(func1)//23
柯里化 底层就是闭包
柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术
def test( a : Int, b:Int, c:Int )--->> def test( a:Int )(b:Int)(c:Int)
实现原理其实就是将每一个需要多个条件(参数)的复杂逻辑函数进行拆分,转换为单一条件(参数)的简单函数,通过简单函数的组合解决复杂的逻辑操作,类似于JavaEE中三层架构的感觉
def test( a:Int )((Int, Int)=>Int)(b:Int)
在柯里化的实现中,使用了闭包的功能,所谓的闭包就是将函数外的局部变量包含到函数内部的一种处理机制,这样可以避免函数外的变量被回收导致程序异常的情况。
柯里化
def func3(i: Int): Unit ={ println("i:" + i) } def func02() = { func3 _ //函数当成返回值,把方法返回而不是结果返回,加_ } func02()(4) //i:4 // 函数柯里化(主要目的是完成隐式转换);上面方式有点乱,声明的时候可以直接加两个括号!跟上边的方式是一样的;这种简化就是柯里化,把复杂的业务逻辑拆成一段一段,它不是一个函数; def func03()(i: Int): Unit ={ println(i) } func03()(4) //4
闭包
// 改变局部变量的生命周期,将变量包含到当前函数的内部,形成了一个闭包的效果;执行完f4,它的局部变量i就无效了(弹栈了),f5把它包进去 // scala中函数可以声明在任意的地方 def func04(i: Int)={ def func05(j: Int): Unit ={ println(i + j) } func05 _ } func04(10)(20) //30 def func06(i: Int)(j: Int): Unit ={ //按柯里化方式声明,柯里化底层就是闭包 println(i + j) } //func06(7)(3)
匿名函数
(参数)=>{方法体--把逻辑代码传进来了}
参数只用一次可省略加个_即可(作为占位符)
def f1(f:(String) => Unit): Unit ={ f("kris") } def f2(s: String): Unit ={ println("Hello " + s) } //f1(f2) 这种方式还要声明一个函数,太麻烦了,使用匿名函数 // 匿名函数 没有名称也不用声明; 参数=>方法体;可以传一段逻辑代码,扩展性强 遵循了OCP原则,扩展性开放 f1((x) => {println("Hello" + x)}) //Hello kris f1(println(_)) //kris 占位符 //f1((x) => {println(x)}) 进一步的省略,这两种写法是等同的
柯里化练习| 把函数作为参数传进来
Operator(1)(++)(1) -->> 2
def main(args: Array[String]): Unit = { def Operator(i: Int) = { //Operator应该返回一个函数;柯里化也就是闭包的概念,把一个完整的进行拆分一个个单一的函数;Operator并没有执行,最终的执行结果应该靠++决定,但需要用它把它返回即可 def Middle(f:(Int, Int)=>Int) = { //第二个括号即Middle,效果是1+1的结果打印出来,应该把一个算法传进来; def Inner(j: Int) ={ //第三个括号 f(i, j) //执行操作,传进两个参数 } Inner _ } Middle _ } def ++(i: Int, j: Int): Int = { //这里执行具体功能,++ i / j //加减乘除都可以;逻辑可以传进来,但主题架构没有变; } println(Operator(1)(++)(1)) //1 }
传函数还要自己声明---->再简化,使用匿名函数
//val i: Int = Operator(1)((x, y) => (x + y))(10) val i: Int = Operator(1)(_+_)(10) //参数只用了一次还可以省略 println(i) //11
//sum(3)(3)(println(_))-->> 6 def sum(i: Int) = { def middle(j: Int)={ def inner(f: (Int, Int) => Unit) = { f(i, j) } inner _ } middle _ } sum(3)(3)((x: Int, y: Int)=>{println(x+y)}) //6 //sum(3)(3)((x, y) => (println(x + y)))
递归
递归有可能栈溢出;(不是内存溢出),栈内存溢出---跟线程有关;压栈; 栈帧(即每块内存)
// 递归 // 1) 函数自己调用自己 // 2) 函数中一定要有跳出的逻辑 // 3) 函数自己调用自己时传递的参数应该有规律 // 4)scala中递归函数需要明确返回值类型 def test(num: Int): Int ={ if (num == 1){ 1 }else{ num*test(num-1) } } println(test(5)) //120
惰性函数
JIT及时编译器,可打乱程序执行顺序
内存加载顺序是由jvm决定,怎么快怎么来
windows、linux是线程优先级,苹果系统是分时
看一个应用场景
惰性计算(尽可能延迟表达式求值)是许多函数式编程语言的特性。惰性集合在需要时提供其元素,无需预先计算它们,这带来了一些好处。
首先,您可以将耗时的计算推迟到绝对需要的时候。其次,您可以创造无限个集合,只要它们继续收到请求,就会继续提供元素。
函数的惰性使用让您能够得到更高效的代码。Java 并没有为惰性提供原生支持,Scala提供了。
Java实现懒加载的代码,比如常用的单例模式懒汉式实现时就使用了上面类似的思路实现
当函数返回值被声明为lazy时,函数的执行将被推迟,直到我们首次对此取值,该函数才会执行。这种函数我们称之为惰性函数,在Java的某些框架代码中称之为懒加载(延迟加载)。 def main(args: Array[String]): Unit = { lazy val res = sum(10, 20) println("########################") println("res=" + res) } def sum(n1: Int, n2: Int): Int ={ println("sum() 执行了..") return n1 + n2 } /* 不加lazy sum() 执行了.. ######################## res=30 加lazy ######################## sum() 执行了.. res=30 */
注意事项和细节
1)lazy 不能修饰 var 类型的变量
2)不但是 在调用函数时,加了 lazy ,会导致函数的执行被推迟,我们在声明一个变量时,如果给声明了 lazy ,那么变量值得分配也会推迟。 比如 lazy val i = 10
过程
函数没返回值就是过程--就是一段逻辑
偏(一部分)函数(partial function) 也叫模式匹配
map函数的作用是将集合中所有的数据进行逻辑操作,数据总量不变。map不支持偏函数,collect支持;
偏函数会导致不符合条件的数据被过滤掉,所以数据总量发生变化;过滤filter、groupBy
① 在对符合某个条件,而不是所有情况进行逻辑操作时,使用偏函数是一个不错的选择
② 将包在大括号内的一组case语句封装为函数,我们称之为偏函数,它只对会作用于指定类型的参数或指定范围值的参数实施计算,超出范围的值会忽略.
③ 偏函数在Scala中是一个特质PartialFunction
偏函数简化形式
声明偏函数,需要重写特质中的方法,有的时候会略显麻烦,而Scala其实提供了简单的方法
def f2: PartialFunction[Any, Int] = { case i: Int => i + 1 // case语句可以自动转换为偏函数 } val list: List[Any] = List(1, 2, 3, 4, 5, "abc").collect(f2) println(list)
进一步的简化:
val list2 = List(1, 2, 3, 4, 5, "abc").collect { //偏函数,只对其中的一部分起作用 case i: Int => i + 2 //List(3, 4, 5, 6, 7) //再进一步的简化:不用声明函数直接写个case即可 } println(list2) //List(3, 4, 5, 6, 7
扁平化栗子:
val list = List(1,2,List(3,4), "abc") println(list.flatMap(x => List(x))) //List(1, 2, List(3, 4), abc) 没有真正的扁平化,1,2不能迭代; 区别对待,偏函数 println(list.flatMap{ // //case x: Int => List(x) case y: Iterable[Int] => y //List(1, 2, 3, 4, abc),case顺序如果调换下就是List(1, 2, List(3, 4), abc)这个结果了 case a => List(a) })
再比如:
val map = Map("a"->1, "b"->2) map.foreach{ case(k, v) => { println(k + "=>" + v) } }
===>
a => 1
b => 2
控制抽象
将一段代码(从形式上看),作为参数传递给高阶函数,在高阶函数内部执行这段代码. 其使用的形式如 breakable{} 。
满足如下条件:
① 参数是函数
② 函数参数没有输入值也没有返回值
可以将一段代码逻辑作为参数传递给函数(方法),有利于功能的扩展
Breaks.breakable { val list = List(1, 2, 3, 4) for (i <- list) { if (i == 3) { Breaks.break() } } }
模仿源码:
class Breaks() extends scala.AnyRef { def breakable(op : => scala.Unit) : scala.Unit = { /* compiled code */ }
把一段逻辑写进来就可以写{ } 花括号了;
test{ val list = List(1, 2, 3, 4) for (i <- list) { println(i) } } def test(f: => Unit)={ f //f()函数可以调用,scala中要求f加括号了才能加,没有加()也不能加 }
for (){
} //for如果不是关键字, for()()柯里化,把一段逻辑写进来就可以写{ } 花括号了;
异常
Scala提供try和catch块来处理异常。try块用于包含可能出错的代码。catch块用于处理try块中发生的异常。可以根据需要在程序中有任意数量的try...catch块。
语法处理上和Java类似,但是又不尽相同
Scala异常处理
ctrl+alt+t
/* Java异常处理回顾 try { // 可疑代码 val i = 0 val b = 10 val c = b / i // 执行代码时,会抛出ArithmeticException异常 } catch { case e: Exception => e.printStackTrace() } finally { // 最终要执行的代码 System.out.println("java finally") }*/
try { val r = 10 / 0 } catch { case ex: ArithmeticException => println("捕获了除数为零的算术异常") case ex: Exception => println("捕获了异常") } finally { // 最终要执行的代码 println("scala finally") }
1)我们将可疑代码封装在try块中。 在try块之后使用了一个catch处理程序来捕获异常。如果发生任何异常,catch处理程序将处理它,程序将不会异常终止。
2)Scala的异常的工作机制和Java一样,但是Scala没有“checked(编译期或受检)”异常,即Scala没有编译异常这个概念,异常都是在运行的时候捕获处理。
3)用throw关键字,抛出一个异常对象。所有异常都是Throwable的子类型。throw表达式是有类型的,就是Nothing,因为Nothing是所有类型的子类型,所以throw表达式可以用在需要类型的地方
4)在Scala里,借用了模式匹配的思想来做异常的匹配,因此,在catch的代码里,是一系列case子句来匹配异常。当匹配上后 => 有多条语句可以换行写,类似 java 的 switch case x: 代码块..
5)异常捕捉的机制与其他语言中一样,如果有异常发生,catch子句是按次序捕捉的。因此,在catch子句中,越具体的异常越要靠前,越普遍的异常越靠后,如果把越普遍的异常写在前,把具体的异常写在后,在scala中也不会报错,但这样是非常不好的编程风格。
6)finally子句用于执行不管是正常处理还是有异常发生时都需要执行的步骤,一般用于对象的清理工作,这点和Java一样。
7)Scala提供了throws关键字来声明异常。可以使用方法定义声明异常。 它向调用者函数提供了此方法可能引发此异常的信息。 它有助于调用函数处理并将该代码包含在try-catch块中,以避免程序异常终止。在scala中,可以使用throws注释来声明异常