Scala中函数的常见问题(Lambda表达式,匿名函数,高阶函数,函数嵌套,柯里化(Currying),隐式函数)

8 篇文章 1 订阅
4 篇文章 2 订阅

1、Scala函数简介

(1)函数是一组执行的语句。您可以将代码按功能分成一个个单独的函数。 如何在不同函数之间划分你的代码取决于你,但从逻辑上讲,通常每个函数执行一个特定的任务。
(2)Scala具有两种函数,术语 -方法和函数是可以互换的。Scala方法是具有名称,签名,可选地一些注释和一些字节码的类的一部分,作为Scala中的函数是可以分配给变量的完整对象。
换句话说,定义为某个对象的成员的函数称为方法。
(3)函数定义可以出现在源文件的任何位置,Scala允许嵌套函数定义,也就是其他函数定义中的函数定义。有一点要注意的是,Scala函数的名称可以包含符号,如:+,++,~,&,-, – ,\,/,:等的字符。

2、函数的声明与定义

(1)函数声明

Scala函数声明具有以下形式

def functionName ([list of parameters]) : [return type]

如果不使用等号和方法体,则隐式声明抽象(abstract)方法。

(2)函数定义

Scala函数定义具有以下形式 -
语法

def functionName ([list of parameters]) : [return type] = {
   function body
   return [expr]
}

这里,返回类型可以是任何有效的Scala数据类型,参数列表将是由逗号分隔的变量列表,参数列表和返回类型是可选的。与Java非常相似,返回语句可以与表达式一起使用,以防函数返回值。 以下是将两个整数相加并返回其总和的函数

语法

   def addInt( a:Int, b:Int ) : Int = {
      var sum:Int = 0
      sum = a + b
      return sum
   }
}

一个不返回任何东西的函数可以返回一个类似在Java中的void类型,并表示该函数不返回任何内容。 在Scala中不返回任何东西的函数称为过程。

语法

object Hello{
   def printMe( ) : Unit = {
      println("Hello, Scala!")
   }
}

(3)调用函数

Scala为调用方法提供了许多句法变体。以下是调用方法的标准方法 -

functionName( list of parameters )

如果使用对象的实例调用函数,那么可使用与Java类似的点符号,如下所示:

[instance.]functionName( list of parameters )

尝试以下示例程序来定义并调用相同的函数 -
示例 -

object Demo {
   def main(args: Array[String]) {
      println( "Returned Value : " + addInt(5,7) );
   }

   def addInt( a:Int, b:Int ) : Int = {
      var sum:Int = 0
      sum = a + b

      return sum
   }
}

将上述程序保存在源文件:Demo.scala中,使用以下命令编译和执行此程序。

$ scalac Demo.scala
$ scala Demo

Returned Value : 12

(4)参数操作

命名参数

  • 通常情况下,传入参数与函数定义的参数列表一一对应
  • 命名参数允许使用任意顺序传入参数
def printName(first:String, last:String) = {
    println(first + " " + last)
}
 
//Prints "John Smith"
printName("John","Smith")
printName(first = "John",last = "Smith")
printName(last = "Smith",first = "John")

参数缺省值

  • Scala函数允许指定参数的缺省值,从而允许在调用函数时不指明该参数
def printName(first:String="John", last:String="Smith") = {
    println(first + " " + last)
}
 
//Prints "John Smith"
printName()

(5)参数传递

传值调用(call-by-value)

示例

def square(x: Int): Int = { 
    println(x)    //3
    x * x             //计算3*3
}
square(1+2)   //先计算1+2

传值调用时,参数只在调用时计算一次,后续重复使用计算的结果

传名调用(call-by-name)

示例

def square(x: => Int): Int = { 
    println(x)    //计算1+2
    x * x             //计算(1+2)*(1+2)
}
square(1+2)   //调用时不计算

传名调用时,参数在调用时不会计算,只有真正用到参数时才计算

3、Lambda表达式

函数是接口

  • 一种只含有一个抽象方法声明的接口
  • 可以使用匿名内部类来实例化函数式接口的对象
  • 通过Lambda表达式可以进一步简化代码

Lambda运算符

  • 所有的lambda表达式都是用新的lambda运算符 " => ",可以叫他,“转到”或者 “成为”。运算符将表达式分为两部分,左边指定输入参数,右边是lambda的主体。

lambda语法

一个参数:param=>expr
多个参数:(param-list) =>expr

示例:
map(_._2) 等价于 map(t => t.2) 将t转化为t的第二个元素 (t是个2项以上的元组)
map(
._2, _) 等价与 map((x,y)=>x._2,y) 这会返回第二项为首后面项为旧元组的新元组
在这里插入图片描述

4、内置的四大函数式接口

消费接口:Consumer
供给型接口:Supplier
断言型接口:Predicate
函数型接口:Function<T,R>

Consumer<String> consumer=(s)-> System.out.println(s.toUpperCase());
consumer.accept("testabc");
Supplier <Double>  supplier = () -> Math.random();
System.out.println(supplier.get());
Predicate<String> condition=s -> s.length()>4;
if(condition.test("hello")) System.out.println("true");
Function<String,String> toUpper=(s)->s.toUpperCase();
System.out.println(toUpper.apply("test"));

5、匿名函数

Scala 中定义匿名函数的语法很简单,箭头左边是参数列表,右边是函数体。使用匿名函数后,我们的代码变得更简洁了。

(1)单个参数
下面的表达式就定义了一个接受一个Int类型输入参数的匿名函数:

var inc = (x:Int) => x+1

上述定义的匿名函数,其实是下面这种写法的简写:

def add2 = new Function1[Int,Int]{  
    def apply(x:Int):Int = x+1;  
} 

以上实例的 inc 现在可作为一个函数,使用方式如下:

var x = inc(7)-1

(2)多个参数

同样我们可以在匿名函数中定义多个参数:

var mul = (x: Int, y: Int) => x*y

mul 现在可作为一个函数,使用方式如下:

println(mul(3, 4))

我们也可以不给匿名函数设置参数,如下所示:

var userDir = () => { System.getProperty("user.dir") }

userDir 现在可作为一个函数,使用方式如下:

println( userDir() )

实例

object Demo {
   def main(args: Array[String]) {
      println( "multiplier(1) value = " +  multiplier(1) )
      println( "multiplier(2) value = " +  multiplier(2) )
   }
   var factor = 3
   val multiplier = (i:Int) => i * factor
}

输出结果为:

multiplier(1) value = 3
multiplier(2) value = 6

(3)使用场景

  • 函数变量(常量)
    像我们前面举的例子里,都把匿名函数保存为了一个常量。
  • 在将一个匿名函数作为参数进行传递。这个在我们平时编码过程中使用极多,因为scala是函数式编程语言,函数是头等公民,函数经常需要作为参数传递。如果给每个函数,尤其是那种一次性使用的函数起个合适的名字,那简直要头疼死了。在这种情况下,匿名函数就是最佳选择。

6、高阶函数

定义:高阶函数可以将其他函数作为参数或者使用函数作为输出结果

函数作为参数的函数

def doSquare(f:Int=>Int,p:Int)=f(p)
def square(x:Int):Int=x*x
doSquare(square,square(2))

函数作为返回值的函数

//返回类型为函数(Int=>Int)
def doSquare()={
  (x:Int)=>x*x
}
doSquare()(2)

作为值的函数
可以像任何其他数据类型一样被传递和操作的函数,每当你想要给算法传入具体动作时这个特性就会变得非常有用。
定义函数时格式:
val 变量名 = (输入参数类型和个数)
=> 函数实现和返回值类型
“=”表示将函数赋给一个变量
“=>”左面表示输入参数名称、类型和个数,右边表示函数的实现和返回值类型

Scala中常用的一些高阶函数

map
foreach
filter
fold、foldLeft、foldRight
reduce
zip
flatten
flatMap

具体参考之前写的函数大全

7、函数嵌套

Scala函数内可以定义函数,函数内的函数也称局部函数或者内嵌函数
//使用函数嵌套实现阶乘运算

def factorial(i: Int): Int = {
      def fact(i: Int, accumulator: Int): Int = {
         if (i <= 1)
            accumulator
         else
            fact(i - 1, i * accumulator)
      }
      fact(i, 1)	//不能在factorial()之外调用
   }

7、柯里化(Currying)

柯里化(Currying)技术 Christopher Strachey 以逻辑学家 Haskell Curry 命名的(尽管它是 Moses Schnfinkel 和 Gottlob Frege 发明的)。它是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。

简单理解就是改变函数的表达形式但其功能特性不变,这对于第一次接触柯里化的人来讲,这样的一个技术貌似有点“鸡肋”,但如果你有丰富的JS编程经验的话,相信就一定会感受到柯里化其实是具有很高的实用性的。无论是在提高适用性还是在延迟执行或者固定易变因素等方面,柯里化技术都发挥着重要的作用。

方法可以定义多个参数列表,当使用较少的参数列表调用多参数列表的方法时,会产生一个新的函数,该函数接收剩余的参数列表作为其参数。这被称为柯里化

//单参数列表
def modN(n: Int,x: Int) = ((x % n) == 0)
//多参数列表
def modN(n: Int)(x: Int) = ((x % n) == 0)
//新函数接收剩余的参数列表作为其参数
def f1(x: Int) = modN(10)(x) 
def f2(n: Int) = modN(n)(10) 
def f3 = modN(10)(_)  

举例

在scala中定义2个整数相乘运算的函数是非常简单的,具体如下:

 def multiplie2par(x:Int,y:Int)=x*y

使用柯里化技术可以将上述2个整数的乘法函数改修为接受一个参数的函数,只是该函数返回是一个以原有第二个参数为参数的函数。

 def multiplie1par(x:Int)=(y:Int)=>x*y

上述使用柯里化技术得到等价新函数,在scala语言中看起来还不是很简洁,在scala中可以进一步简化为:

 def multiplie1par1(x:Int)(y:Int)=x*y

作用

在scala柯里化中,闭包也发挥着重要的作用。所谓的闭包就是变量出了函数的定义域外在其他代码块还能其作用,这样的情况称之为闭包。就上述讨论的案例而言,如果没有闭包作用,那么转换后函数其实返回的匿名函数是无法在与第一个参数x相关结合的,自然也就无法保证其所实现的功能是跟原来一致的。

8、隐式函数

(1)隐式参数

定义一个普通变量,使用 “implicit” 关键字修饰,定义一个函数调用这个变量当参数时,此时这个参数就被称为 隐式参数
隐式参数的作用:减少提供函数参数的数量,让某些参数拥有隐藏的值(隐式变量)

  • implict只能修改最尾部的参数列表,应用于其全部参数
  • Scala可自动传递正确类型的隐式值
  • 通常与柯里化函数结合使用

举例

def sum(x:Int)(implicit y:Int)=x+y
implicit var a=10	//将作为Int类型隐式值自动传递
sum(10)	//20

注意
a.在一个类中,定义同一类型的隐式参数只能有一个,即函数参数列表中同一类型的隐式参数也只能为一个,多了会报错
b.在一个类中,不同类型的隐式参数可以传入到函数当中
c.scala中函数的传参级别: 函数列表传参 > 隐式参数 > 默认值

(2)隐式函数

1)定义
通常我们所说的隐式函数也称为 隐式转换,是使用 implicit 修饰的函数

2)作用: 可以通过一个隐式函数将一种类型转变为另一种类型

3)应用场景

a.类型转换,隐式转换为期望类型

implicit def doubleToInt(obj:Double) = obj.toInt   //定义隐式函数
val a:Int = 3.5    //写的时候scala就调用了隐式函数 doubleToInt 进行类型转化 

常用的类型转换

implicit def doubleToString (obj:Double):String=obj.toString
implicit def intToString(obj:Int):String=obj.toString
implicit def stringToInt(obj:String):Int=Integer.parseInt(obj)

b.类型增强
比如说 1+true 这个表达式,我们想让 true当作 1计算, false当作 0计算。
Int 类型与boolen类型是无法相加的,编译器肯定会报错

此时,我们可以定义一个隐式函数,使 “true=1,false=0” ,此时便可提供一个隐式函数,使得Int+Boolean成立

implicit def booleanToInt(obj:Boolean)=if(obj) 1 else 0
//测试
val a=1+true
println(a)  //2

: 隐式转换的发生时机

  • 当方法中的参数的类型与目标类型不一致时

  • 当对象调用类中不存在的方法或成员时,编译器会自动将对象进行隐式转换

(3)隐式类

1)定义
隐式类指的是用implicit关键字修饰的类。在对应的作用域内,带有这个关键字的类的主构造函数可用于隐式转换。
2)案例
现在有一个需求:有一个 Person 类,含有work()方法,有一个 Student 类,含有study()方法,在不使用继承的情况下,要求创建一个Person类的对象,可以直接调用Student类里的study()方法?

使用隐式类 ,将Student类定义为隐式类,这样在 new Person对象的时候, 编译器会默认在Student的主构造方法中传入Person对象,再转型为Student对象,这样新建的Person对象就有了双重身份,可以随便调用这两个类中的方法
代码如下:
创建一个scala class文件: Person

class Person() {    //定义一个主类:Person 有个work方法
  def work()={
    println("人每天都要工作")
  }
}

object Test1{
 implicit class Student(obj:Person){   //在object中定义一个隐式类:Student 有个study方法
   def study()={               //圈重点:主构造方法传入的类型必须要有----->  Person          
     println("喜欢学习scala")
   }
 }

  def main(args: Array[String]): Unit = {
    val person: Person = new Person    //在main方法中new一个Person对象
    person.work()
    person.study()   //可以看到可以直接调用Student中的study()方法
  }
}

  • 创建隐式类,主构造函数中必须要引入一个目标类当参数,且隐式类必须要在 object 中创建
  • 本文是将隐式类与目标类写在了一个scala文件中,当隐式类写在其他文件中,调用时引入该隐式类所在的包即可

参考文章:https://www.yiibai.com/scala/scala_functions.html
https://www.cnblogs.com/duanxz/p/9567127.html
https://blog.csdn.net/and52696686/article/details/107618593

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值