函数和方法的参数问题
函数和方法当做函数或方法的参数
object MTest01 {
def main(args: Array[String]): Unit = {
//传入函数f2
println(m2(5,6,f2))
//传入方法,这个方法会被转换成函数
println(m2(5,6,m1))
//将方法转换成函数
val ff3 = m2 _
print(ff3(3,4))
//函数中传入函数,函数可以是函数也可以是方法
println(f3(3,5,f2))
println(f3(3,5,m1))
}
//方法
def m1(x:Int,y:Int):Int = x+y
//函数
val f1:Int =>Double = (value:Int) =>value.toDouble
//val f3:(Int,Int)=>Int = (x:Int,y:Int) => x+y//和下面定义是一样的
val f2 = (x:Int,y:Int) => x+y
//该函数第三个参数是一个函数,只要是接收两个Int类型的参数最终转换为Int类型的函数或方法都可以
val f3 = (x:Int, y:Int, ff:(Int, Int) => Int) => ff(x, y)
//方法m2接收两个参数: Int (Int,Int) => Int
//第二个参数只要是接收两个Int类型的参数最终转换为Int类型的函数或方法都可以
def m2(x:Int, y:Int, ff:(Int,Int) =>Int) = {
ff(x,y)}
}
总结:一个方法或函数可以当做另一个方法或函数的参数
函数和方法返回值
object MTest02 {
def main(args: Array[String]): Unit = {
//方法和函数之间的相互转换
def m2 = f1//方法转函数
println(m2(3,5))
val f2 = m1 _ //函数转方法
println(f2(3,5))
//方法的返回值是一个函数
val a = m3(3)
val b = a(3)
println(b)
//可以这样写
println(m3(3)(3))
}
//方法
def m1(x:Int, y:Int):Int = x+y
//函数
val f1 =(x:Int, y:Int) => x+y
//方法的返回值是一个函数
def m3(x:Int) = (y:Int) => x+y
}
总结:函数或方法可以作为函数或方法的返回值。
闭包
闭包是一个函数,返回值依赖于声明在函数外部的一个或多个变量。
闭包通常来讲可以简单的认为是可以访问一个函数里面局部变量的另外一个函数。
object CurryingTest01 {
def main(args: Array[String]): Unit = {
val result = bibao
result(7)
}
val bibao ={
var sum =0
val add_sum= (x:Int) => {
//函数add_sum为一个闭包它引用到函数外面定义的变量sum,
sum += x //定义这个函数的过程是将这个自由变量(sum)捕获而构成一个封闭的函数。
println(sum)
}
add_sum
}
}
总结:
-
闭包是一个函数,返回值依赖于声明在函数外部的一个或多个变量。
-
闭包通常来讲可以简单的认为是可以访问一个函数里面局部变量的另外一个函数。
柯里化
在计算机科学中,柯里化(Currying)是把接受多个参数的函数变成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果是一个新函数的技术。有时需要允许他人在函数上应用一些参数,然后又应用另外的一些参数。例如一个乘法函数,在一个场景需要选择乘数,另一个场景需要选择被乘数。所以科里化函数就是将多个参数分开写,写在不同的小括号中,而不是在一个小括号中用逗号隔开。
object CurryingTest01 {
def main(args: Array[String]): Unit = {
//普通方法
def method1(x:Int, y:Int)={
x + y
}
//普通方法的调用
println(method1(1,3))//必须传入全数参数
//普通方法 返回一个函数Int 到Int类型
def method2(x:Int):Int => Int ={
(y:Int) => x+y
}
//方法调用
val a = method2(3)
val b = a(5)//是一个函数
println(b)
//将上面方法改成柯里化
def method3(x:Int)(y:Int):Int={
x+y
}
//调用方式一
println(method3(1)(2))
//调用方式二
val c:Int => Int = method3(1)
val d:Int = c(2)
println(d)
}
}
object CurryingTest01 {
def main(args: Array[String]): Unit = {
//普通方法
def method1(x:Int, y:Int=10)={
//设置一个默认值
x + y
}
//普通方法的调用
println(method1(1,3))//传入全数参数
println(method1(3))//只传入一个参数,另一个就是默认参数
//柯里化方法
def method3(x:Int)(implicit y:Int=10):Int={
//必须加implicit关键词才能和上面的方法一样的效果
x+y
}
//调用方式一
println(method3(1)(2))
//调用方式二
val c = method3(1)//第二个参数使用默认值
println(c)
//方式三
implicit val d = 20//通过这样设置可以改变默认参数的值
println(method3(3))
//在柯里化方法中可以将非默认值参数转变成具有默认值的方法
def method4(x:Int)(y:Int) ={
x+y
}
def e = method4(3)
println("Hello: "+e(3))
//用函数接收,需要转换符号
val f = method4(3) _
println(f)
/**
* 柯里化的好处:
* 1,多个参数可以不用一次传完
* 2,其中一个参数使用implicit修饰的有默认值的参数,参数的值可以改变
*/
}
}
隐式转换
参数
案例一 隐式值
隐式值也叫隐式变量,将某个形参变量标记为implicit,所以编译器会在方法省略隐式参数的情况下去搜索作用域内的隐式值作为缺省参数
object Implicit_transform {
def main(args: Array[String]): Unit = {
//隐式参数
def m1(x:Int)(implicit y:Int=1) = x+y
println(m1(1)) //2
println(m1(1)(2))//3
println(m1(1)())//2
//隐式值
implicit val a =10//修改了隐式默认值参数
// implicit val b = 20 //不能同时定义两个implicit修饰的变量,否则会报ambiguous implicit values的错误
println(m1(1))//11 方法调用时,不使用小括号可以传递隐式值
println(m1(1)(2))//3
println(m1(1)())//2 方法调用时,使用小括号会导致隐式值无法传递
}
}
案例二
object ImplicitParamTest01 {
def main(args: Array[String]): Unit = {
def m1(x:Int)(implicit y:Int=1) = x+y
println(m1(1))//2
println(m1(1)(2))//3
import ImplicitParamTest02.a
println(a)//不知道问什么,只有加上这句代码下面才根据a的值来代替默认值
println(m1(1))//11
println(m1(1)(2))//3
}
}
object ImplicitParamTest02{
implicit val a = 10
}
案例三
object Implicit_transform {
def main(args: Array[String]): Unit = {
//Scala默认的情况下支持数值类型的自动转换
//byte->short->int->long
//Scala默认的情况下支持多态语法中的类型自动转换
//也允许开发人员自定义转换规则
//将两个无关的类型通过编程手段让他们可以自动转换
//隐式转换可以在保证OCP(open close principal)前提下,扩展功能
implicit def transform(d:Double):Int = {
d.toInt
}
/*
在相同作用域内,不能含有多个相同类型的转换规则
implicit def transform(d:Double):Int = {
d.toInt
}
*/
val i: Int = 5.0
println(i)
}
}
案例四 动态增加功能
如果需要为一个类增加一个方法,可以通过隐式转换来实现。(动态增加功能)比如想为MySQL类增加一个delete方法
在当前程序中,如果想要给MySQL类增加功能是非常简单的,但是在实际项目中,如果想要增加新的功能就会需要改变源代码,这是很难接受的。而且违背了软件开发的OCP开发原则 (开闭原则 open close priceple)
在这种情况下,可以通过隐式转换函数给类动态添加功能。
object Implicit_transform {
def main(args: Array[String]): Unit = {
implicit def transform(mySql: MySql):Operator={
new Operator
}
//通过隐式转换,MySql类型转换称Operator类型,使MySql对象具有了Operator对象的方法
val sql: MySql = new MySql
sql.delete()
sql.select()
}
}
class Operator {
def delete(): Unit ={
}
}
class MySql {
def select(): Unit = {
}
}
案例五 隐式类
在scala2.10后提供了隐式类,可以使用implicit声明类,隐式类的非常强大,同样可以扩展类的功能,比前面使用隐式转换丰富类库功能更加的方便,在集合中隐式类会发挥重要的作用。
隐式类使用有如下几个特点:
1)其所带的构造参数有且只能有一个
2)隐式类必须被定义在“类”或“伴生对象”或“包对象”里,即隐式类不能是 顶级的(top-level objects)。
3)隐式类不能是case class
4)作用域内不能有与之相同名称的标识符
object Implicit_transform {
def main(args: Array[String]): Unit = {
val user = new User
user.insert()
user.delete()
implicit class Person(u:User){
def delete(): Unit ={
}
}
class User{
def insert(): Unit ={
}
}
}
}
总结:
1,如果一个函数的参数被声明为implicit,那么表示当前这个参数会从运行环境中寻找对应类型的隐式值进行替代
2,如果运行环境中定义了多种不同类型的隐式变量值,那么最终代码会在运行的方法的参数类型进行匹配,而不是按照变量名进行匹配
3,如果不是在代码运行环境中直接定义的一个隐式变量值,那么就需要通过import进行引入
4,定义的隐式转换的变量或者方法都只能写在object中
5,引入的隐式转换(变量替换或类型转换)只有对使用implicit修饰或者支持隐式转换语法才有效
隐式解析机制
即编译器是如何查找到缺失信息的,解析具有以下两种规则:
首先会在当前代码作用域下查找隐式实体(隐式方法、隐式类、隐式对象)。
如果第一条规则查找隐式实体失败,会继续在隐式参数的类型的作用域里查找。类型的作用域是指与该类型相关联的全部伴生模块,一个隐式实体的类型T它的查找范围如下(第二种情况范围广且复杂在使用时,应当尽量避免出现):
a) 如果T被定义为T with A with B with C,那么A,B,C都是T的部分,在T的隐式解析过程中,它们的伴生对象都会被搜索。
b) 如果T是参数化类型,那么类型参数和与类型参数相关联的部分都算作T的部分,比如List[String]的隐式搜索会搜索List的伴生对象和String的伴生对象。
c) 如果T是一个单例类型p.T,即T是属于某个p对象内,那么这个p对象也会被搜索。
d) 如果T是个类型注入S#T,那么S和T都会被搜索。