Scala函数
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