- Scalaにはさまざま
面向对象和函数式
な機能があります。関数型プログラミング言語では、関数は一流の市民です- 通常、パラメータとしてメソッドに関数として渡すことができる式を呼び出します
- 関数をパラメーターとして受け取ったり、関数を返したりできる関数は、高階関数と呼ばれます。
- 高階関数には、値としての関数、パラメーターとしての関数、無名関数、クロージャ、カリー化などが含まれます。
値として、パラメーターとして、無名関数として機能-★★★★
- 完全な構文:
- val関数名:(パラメータータイプ)=>関数戻り値タイプ=(パラメーター名:パラメータータイプ)=>関数本体
- 省略構文:
- val関数名=(パラメーター名:パラメータータイプ)=>関数本体
- 記号の解釈
- =右側の関数を左側の変数に割り当てることを意味します
- =>左側は入力パラメーターの名前とタイプを表し、右側は関数の実装と戻り値のタイプを表します
- 匿名関数
- 関数を変数に割り当てない関数は、無名関数と呼ばれます。
- 機能する方法
- val関数名=メソッド名_
package cn.hanjiaxiaozhi.basic3
/**
* Author hanjiaxiaozhi
* Date 2020/7/13 16:24
* Desc 演示证明Scala中的函数的本质是对象
* 1.完整语法
* val 函数名 :(参数类型)=>返回值类型 = (参数名称:参数类型)=>{函数体}
* 2.简写语法
* val 函数名 = (参数名称:参数类型)=>{函数体}
*/
object FunctionDemo2_Scala {
def main(args: Array[String]): Unit = {
//根据我们之前学习的编程语言,如果一个东西是对象的话吗,那么应该有如下的特征:
//1.可以调用方法
//2.可以作为值赋值给变量接收
//3.可以当作参数被传递给方法
//那么如果能演示出上面的三点不就证明了函数是对象嘛!!!
//定义一些函数
val f1 = (x: Int) => x
val f2 = (x: Int, y: Int) => x + y
//证明1.函数可以调用方法--推出-->函数是对象
println(f1)//<function1>//直接打印函数,如果是对象的话,根据以前的经验,应该会调用对象的toString方法
println(f2)//<function2>
println(f1.toString())//<function1>//函数确实可以调用toString方法,那么如果和上面的打印结果一样,那么不就证明了我们的观点么!
println(f2.toString())//<function2>
//证明2.函数可以作为值赋值给变量接收--推出-->函数是对象
val f3 = f2 //f2是上面定义好的函数,现在赋值给f3变量了, 那么f3变量也成了函数
println(f3)//<function2>
//证明3.函数可以当作参数被传递给方法--推出-->函数是对象
val f4 = (x: Int, y: Int) => x + y
//val result: Int = myMethod(1,2,f4) //f4是一个函数,现在被当作参数传递给了myMethod方法的fun参数了
//也可以传递匿名函数
val result: Int = myMethod(1,2,(x: Int, y: Int) => x + y) //f4是一个函数,现在被当作参数传递给了myMethod方法的fun参数了
println(result)//3
val result2: Int = f4(1,2)
println(result2)//3
}
//定义一个方法,该方法接收2个int值,和1个函数,并在方法体中调用该函数,将2个int值传个该函数
def myMethod(a:Int,b:Int,fun:(Int, Int) => Int):Int ={
fun(a,b)//在方法体中调用函数,并将函数的计算结果作为myMethod方法的返回值
}
}
- もう一度確認する
package cn.hanjiaxiaozhi.highfunction
/**
* Author hanjiaxiaozhi
* Date 2020/7/19 9:52
* Desc 演示高阶函数--其实就是函数的一些高级用法
*/
object FunctionDemo1 {
def main(args: Array[String]): Unit = {
//准备数据
val list = List(1,2,3,4,5)
//定义一个函数,将传入的参数扩大10倍
val fun = (i:Int) => {
i * 10}
//1.函数作为值传递给另一个变量
val f = fun
//2.函数作为参数,传递给方法
val res: List[Int] = list.map(f)
println(res)//List(10, 20, 30, 40, 50)
//3.匿名函数,就是将没有定义名称的函数直接传递给方法
//val res2: List[Int] = list.map((i:Int) => {i * 10})//传入匿名函数
//val res2: List[Int] = list.map(i => i * 10)//传入简写的匿名函数
val res2: List[Int] = list.map(_*10)//传入简写的匿名函数
println(res2)
//4.定义一个方法并转换为函数然后进行传递
def m(i:Int):Int={
i*10
}
val f2 = m _ //方法转为函数
val res3: List[Int] = list.map(f2)
println(res3)
//5.直接传递方法给函数型参数,编译器会自动将方法转为函数
val res4: List[Int] = list.map(m)
println(res4)
//类似于
//list.foreach(i=>println(i))
//list.foreach(println _)
list.foreach(println)
}
}
閉鎖-★
Scalaのクロージャ
- クロージャとは
- クロージャは実際には関数ですが、関数は関数の外部で宣言された1つ以上の変数に依存します
- それ自体に属さない外部変数を含める(閉じる)関数です。
- 閉鎖は何を引き起こしますか
- 複数の呼び出しがあり、結果に一貫性がない可能性があります
- 閉鎖の問題を解決する方法
- 1.クロージャを避けます(関数で外部変数を使用しないようにしてください)
- 2.変数を定義し、valを使用します
package cn.hanjiaxiaozhi.highfunction
/**
* Author hanjiaxiaozhi
* Date 2020/7/19 10:18
* Desc 演示Scala函数的闭包
*/
object FunctionDemo2 {
def main(args: Array[String]): Unit = {
//定义一个变量,后续可以提供给函数使用
var b = 10 //建议使用val修饰
//定义一个函数,传入a并和函数外的变量b相加
val add = (a: Int) => {
a + b
}
//调用函数
val res1: Int = add(1)
println(res1) //11
//修改b的值
b = 100
//再次调用函数,传入同样的值
val res2: Int = add(1)
println(res2)//101
//观察上面的运行结果,我们会发现:
//两次调用同一个函数add,传入通用的参数1,得到的结果尽然不同!
//原因是因为函数add中用到了外部的不属于该add函数的变量b,形成了闭包现象
//而b的值不受函数add的控制,在外部可能会发生改变,所以add函数的两次调用结果不一样!
//问题如何解决?
//1.尽量避免使用闭包(也就是尽量不要在函数内部使用不属于该函数的变量)
//2.如果避免不了使用闭包,那么将用到的不属于该函数的变量用val修饰,如 val b = 10
//总结:闭包就是函数内部使用了外部的变量,闭包可能会产生多次调用结果不一致的情况,可以通过避免使用闭包或使用val修饰变量来解决
}
}
- 総括する:
- 閉鎖は
函数内部使用了外部的变量
、 - 閉鎖
可能会产生多次调用结果不一致的情况
、 - クロージャの使用を回避するか
val修饰变量
、解決する
- 閉鎖は
拡張機能-Javaのクロージャ-理解してください
package cn.hanjiaxiaozhi.highfunction;
/**
* Author hanjiaxiaozhi
* Date 2020/7/19 10:29
* Desc 演示Java中的闭包
*/
public class FunctionDemo_Java {
//Scala中的闭包指的是函数用到了外部的变量
//函数的本质是对象
//所以Java中的闭包指的是匿名内部类对象中使用到了外部的变量!(因为Java中函数的本质就是匿名内部类对象)
public static void main(String[] args) {
/*final*/ int b = 10;
int res1 = testAdd(1, new Fun() {
@Override
public int add(int a) {
return a + b;
//注意:匿名内部类对象中使用到了外部的变量,称作闭包
//注意:Java中的闭包,会自动将使用到的外部变量加final修饰
}
});
System.out.println(res1);//11
//尝试修改b的值
//b = 100;//这里不能对b就行修改,因为是闭包中使用的变量,默认已经加final了
//testAdd(1,(int a)->{return a+b;});
int res2 = testAdd(1, a -> a + b);
System.out.println(res2);//11
//总结:Java中的闭包,其实就是匿名内部类/函数中使用到了外部变量
//但是在Java中会把在闭包中用到的外部变量自动加final修饰,所以不能修改,也就避免了一些问题
}
//定义一个方法,接收一个int a和一个Fun类型的对象,并在方法体中调用fun.add(a)
public static int testAdd(int a, Fun fun) {
return fun.add(a);
}
}
interface Fun {
int add(int a);
}
カリー化
- 導入する
- Scalaのメソッド/関数は複数回呼び出すことができます。
每次只传递部分参数,返回一个函数,后面接着调用返回的函数传递其他的参数
- このようにして、いくつかのパラメーターを便利にバインドでき、残りのパラメーターは後で追加できます。これは実際にはカリー化のアイデアです
- カリー化は、scalaとsparkのソースコードで広く使用されています。その後のソースコードの読み取りを容易にするために、カリー化について学ぶ必要があります。
- カリー化とは
- カレーとは、複数のパラメーターを受け入れる関数/メソッドのパラメーターリストを複数のパラメーターリストに変換するプロセスを指します(パラメーターが渡されるたびに、残りのパラメーターを受け入れるために新しい関数が返されます)
- 例:fun(a:Int、b:Int)はfun(a:Int)(b:Int)になります
- カリー化の意味
- https://www.zhihu.com/question/20037482
- 関数がカレーされた後、提供できるパラメーターは1つだけで、他のパラメーターは関数の「環境」として作成できます。これにより、関数は元の状態に戻ることができます。つまり、パラメーターが入力され、値が出力されます。
- カリー化はまた呼び出すことができます
部分求值
。カリー化された関数はいくつかのパラメーターを受け取ります。これらのパラメーターを受け取った後、関数はすぐには評価されませんが、別の関数に戻り続けます。今渡されたパラメーターは、関数によって形成されたクロージャーに保存され、関数が実際に値が必要です。以前に渡されたすべてのパラメーターを評価に使用できます。 - カリー化を使用します
可以简化主函数的复杂度,提高主函数的自闭性,使代码模块化,减少耦合增强其可维护性,提高功能上的可扩张性、灵活性。可以编写出更加抽象、功能化和高效的代码
。 - カリー化は、関数を本体とするプログラミング言語の思考の発達の必然的な結果です。
- カリー化はScalaの多くの場所で使用されています。
如fold
- カリー化の役割:
可以对参数进行分批/归类传递
package cn.hanjiaxiaozhi.highfunction
/**
* Author hanjiaxiaozhi
* Date 2020/7/19 10:18
* Desc 演示Scala中的柯里化
*/
object FunctionDemo3 {
def main(args: Array[String]): Unit = {
println(add1(1, 2))//3
println(add2(1)(2))//3
//注意:柯里化的方法需要的多个参数可以分开传递
//前几次传递会返回一个函数,直到最后一次传递返回方法计算的结果
val tempFun: Int => Int = add2(1) //(b:Int) => 1 + b
val res: Int = tempFun(2)
//上面的分开调用和下面的等价
val myTempFun:Int => Int = (b:Int) => 1 + b
val res2: Int = myTempFun(2)
println(res)//3
println(res2)//3
}
//定义一个普通的方法,实现2个数相加
def add1(a:Int,b:Int):Int={
a + b
}
//定义一个柯里化的方法,实现2个数相加
//柯里化指的是:将一次接收多个参数的方法转为了分多次接收
def add2(a:Int)(b:Int):Int={
a + b
}
//柯里化的作用:可以对参数进行分批/归类传递
//如之前学习的 fold/foldLeft方法
//fold(初始值)(函数)
//val list = List[Int](1, 2, 3, 4, 5, 6, 7, 8, 9)//和为45
//需求对list中的元素求和,并给定初始值
//val res1: Int = list.fold(0)(_+_)
//上面的fold就是典型的柯里化方式定义的方法
//传递参数的时候,很容易进行区分,第一个是括号里的初始化值,第二个括号里的是函数
//当然我们以后开发自己很少写这样的,都是源码中会偶尔定义这样的柯里化方法
//我们调用的时候知道该方法是一个柯里化方法即可
}