一 高阶函数(很重要!!!)
- 函数的参数或者返回值是函数的函数或者方法
- 函数的参数和返回值都是函数的函数或者方法
使用高阶函数的一个原因是减少冗余的代码 ,比如说需要写几个方法以通过不同方式来提升员工工资,若不使用高阶函数,代码可能会很冗余臃肿 ,如下:
object SalaryRaiser {
def smallPromotion(salaries: List[Double]): List[Double] =
salaries.map(salary => salary * 1.1)
def greatPromotion(salaries: List[Double]): List[Double] =
salaries.map(salary => salary * math.log(salary))
def hugePromotion(salaries: List[Double]): List[Double] =
salaries.map(salary => salary * salary)
}
使用高阶函数以后 ,可以使用在函数中传入逻辑性代码(函数)即可,减少了代码的冗余性
1 函数或者方法的参数是函数 ,函数的返回值是函数
比如说 map filter 方法
val arr = Array(1,2,3,4)
arr.map(e=>(e*10,1))
arr.filter(x=>x%2==0)
def opt(str:String,f:String=>String): String ={
f(str)
}
-----没有参数的方法
def m: String => String ={
x:String=>x
}
def main(args: Array[String]): Unit = {
----传入的是匿名函数
println(opt("abc", x => x.toUpperCase))
println(opt("abd", x => x + " " + x))
----调用方法 返回值 函数
val f: String => String = m
----调用返回的函数
val str: String = f("abc")
}
二 偏函数
偏函数是一个特质,是专门用来处理一种类型数据用的
1 例如 ,将集合中的所有的 Int 类型的数据都加上1
-----过滤器形式
val arr = Array(1,2,3,"hello","zss",6)
arr.filter(_.isInstanceOf[Int]).map(_.asInstanceOf[Int]+1).foreach(println(_)) -2 3 4 7
-----匹配模式
val array = arr.map(e => e match {
case x: Int => x + 1
case _ =>
})
array.filter(_.isInstanceOf[Int]).foreach(println) ---2 3 4 7 ---过滤出所有整数并都加1
----使用偏函数 ,泛型1输入的数据类型 泛型2要处理的数据类型
val pp = new PartialFunction[Any,Int] {
override def isDefinedAt(x: Any): Boolean = {
x.isInstanceOf[Int]
}
override def apply(v1: Any): Int = {
v1.asInstanceOf[Int]+1
}
}
arr.collect(pp).foreach(println(_)) ----2 3 4 7
---------偏函数简写方式-------------------
arr.collect({case x:Int=>x*x}).foreach(println) --- 1 4 9 36
2 偏函数的执行流程 :
- 遍历 list 中的每个元素
- 调用 val e = if(isDefinedAt){apply}
- 每得到一个 e 就会将 e 存储在一个新的集合中,返回
使用偏函数就不要使用 map 方法了
3 偏函数总结
1) 使用构建特质的实现类(使用的方式是PartialFunction的匿名子类)
2) PartialFunction 是个特质(使用偏函数的时候临时写在待处理的数据下的)
3) 构建偏函数时,参数形式[Any,Int] 是泛型 ,第一个参数表示待处理数据的类型 ,第二个参数表示返回参数类型
4) 当使用偏函数时 ,会遍历集合的所有元素 ,编译器执行流程时先执行 isDefindAt() ,如果为 true ,就会执行 apply,构建一个新的 Int 对象返回 .
5) 执行 isDefinedAt() 为 false ,就过滤掉这个元素 ,即不构建新的 Int 对象
6) map 函数不支持偏函数 ,因为 map 底层的机制就是所有循环遍历 ,无法过滤处理原来集合的元素
7) collect 函数支持偏函数
4 偏函数的简写形式
val list = List(2, 4, 6, 8, "cat")
-----定义一个偏函数------
def myPartialFunction: PartialFunction[Any, Int] = {
case x: Int => x * x
}
list.collect(myPartialFunction).foreach(println)
-----简写方式-------
list.collect({
case x:Int=>x*x
}).foreach(println) ---4 16 36 64
三 匹配模式(重要!!!)
模式匹配 ,其实类似于 Java 中的 swich case 语法 ,即对一个值进行条件判断,然后针对不同的条件 ,进行不同的处理.
但是Scala 的模式匹配的功能比 Java 的 swich case 语法的功能要更大 ,Java 的 swich case 语法只能对值进行匹配,但是 Scala 的模式匹配除了可以对值进行匹配之外 ,还可以对类型进行匹配 ,对 Array 和 List 的元素情况进行匹配 ,对 case class进行匹配 ,甚至对有值或没值进行匹配 .
而且对于 Spark 来说 ,Scala 的模式匹配功能也是极其重要的 ,在 spark 源码中大量的使用了模式匹配功能 .
----基本语法----
_ match{
case_2 => TODO
case_1 => TODO
case_ =>
}
def test1(arg: Any) = arg match {
case x: Int => println("输入的参数是一个Int类型")
case x: (String, Int) => println(s"输入的是一个元组:$x")
case arr: Array[Int] => println(s"这是一个数组: " + arr(0))
case _ => println("我不知道你是个啥")
1 匹配模式可以对字符串进行匹配
object Match01 {
----定义一个方法---------
def test1(arr:Array[String]):Unit={
------生成一个随机的整数数据,随机取出数组中的一个元素
val index:Int = Random.nextInt(arr.length)
arr(index) match {
case "didi" => println("滴滴")
case "xiaohuance" => println("小黄车")
case "dache" => println("打车")
}
}
------主方法,主入口-----------
def main(args: Array[String]): Unit = {
val arr = Array[String]("didi","xiaohuance","dache")
test1(arr) -----随机拿取arr里面的数据 ,然后进行匹配,将匹配到的值打印出来
}
}
2 数据类型的匹配
-----数据类型的匹配
------生成一个随机的整数数据 ,随机取出数组中的一个元素
def test2(arr:Array[Any]){
val index:Int = Random.nextInt(arr.length)
arr(index) match {
case m:Map[_,_] => "Map:" + m.size
case t: (_, _) => "Tuple2: " + t._1 + "," + t._2
case x:Int => ???
case x:String=> print("哈哈哈")
case x:Double => ???
case x:Boolean=> ???
case Person=> ???
case _ => ???
}
}
3 元组的匹配
-----定义一个方法------
def tupleMatch(arg:Any):Unit = {
arg match {
case (x:Int ,y:Int) => print(s"IntType:$x , $y") ----获取整数类型的值
case (x:String ,y:String) => print(s"StringType:$x , $y") --获取字符类型的值
case (x,y,z) => print(s"$x ,$y,$z") ----获取指定值
case _ => print(s"不知道是啥") ---获取其他不知道是什么类型的值
}
}
4 List 和 Array的匹配
--------------Array-------------
val arr: Array[Any] = Array[Any]("abc",12,3,4,5,6)
arr match {
case Array(x,_,_,_,_,_) => print(s"$x")
case Array("abc",_*) =>print("这个数组是以字符串abc开头 : "+arr.length)
case _ => println("hah")
}
--------------List-------------
def main(args: Array[String]): Unit = {
val list = List(1,3,5,6,7)
list match {
case 2::x::y => println(s"x=${x},y=${y}") ----第一步
case List(x,y,z,d) => println("match") ----第二步
case x::y::c::d::z => println(s"x=${x},y=${z}") ---第三步得出结果 : x=1,y=List(7)
case _ => println("no match")
}
}
5 样例类和样例对象的匹配
object MatchCase01 {
------匹配样例类-----
def test3(arg:Any) = arg match {
case Cat(_) => ("这是指小喵喵"+arg)
case Dog(_) => ("这是指小旺旺"+arg)
case _ => ("这是什么")
}
------主入口,主方法---测试-----
def main(args: Array[String]): Unit = {
println(test3(Cat("喵..."))) ----这是指小喵喵Cat(喵...)
println(test3(Dog("汪..."))) ----这是指小旺旺Dog(汪...)
}
}
-----在 MatchCase01这个类中直接定义抽象类和样例类------------
abstract class Animal {}
case class Cat(name:String) extends Animal
case class Dog(name:String) extends Animal
6 Option的匹配
object MatchOption01 {
def main(args: Array[String]): Unit = {
val mp = Map("zss"->16,"lii"->18,"zwu"->20)
val res: Int = mp.get("feifei") match {
case Some(v) => v
case None => -1
}
println(res) --没有匹配到值,所有返回 -1
}
}
四 闭包
是一个函数 ,可能匿名或具有名称, 在定义中包含了自由变量 ,函数中包含了环境信息 ,以绑定其引用的自由变量 .
函数内部的变量不在其作用域时 ,仍然可以从外部进行访问
函数在定义的时候就要有需要的值
五 柯里化详解(重要!!!)
语法 : 函数有多个参数列表
意义 : 方便数据的演变 ,后面的参数可以借助前面的参数推演 ,flodLeft 的实现
1 有多个参数列表的函数就是柯里化函数 ,所谓的参数列表就是使用小括号括起来的函数参数列表
2 柯里化(curry) 最大的意义在于把多个参数的function 等价转化成多个单参数function的级联 , 这样所有的函数就都统一了,方便做 lambda 演算 . 在 Scala 里, curry 化对类型推演也有帮助 ,scala 的类型推演是局部的 ,在同一个参数列表中后面的参数不能借助前面的参数类型进行推演 ,curry 化之后 ,放在两个参数列表里 ,后面一个参数列表里的参数可以借助前面一个参数列表里的参数类型进行推演 .这就是为什么 foldLeft 这种函数的定义都是 curry的形式 .
class A{
class B
}
object F{
def m(a:A ,b:a.B) ----这种就是错误的示范
def m2(a:A)(b:a.B) ----这样是正确的,可以由 A 推演出 b ----多个参数列表的表达形式
}
----add(x:Int)(implicit y:Int ,z:Int) --有多个参数列表 ,柯里化
六 隐式详解(重要!!!)
核心/本质是装饰模式 , 将增强的方法用 implicit 修饰 ,自己传的隐式转换优先级最高 .
使用 implicit 修饰的内容是隐式内容 ,隐式的特点就是在上下文中找 ,遇到适应的类型会自动的应用
意义:
1) 使得静态类型动态化
2) 为现有类库添加功能
3) 隐式的代理增强一个类或者一个方法
分类 : 隐式参数 隐式函数 隐式类
1 定义隐式变量
object MyValue {
------表达形式1---------
implicit val age:Int = 19
implicit val name:String = "lisi"
------表达形式2----------
----这个方法的所有参数是隐式参数 --------(implicit y:Int ,z:Int)
----可以使用柯里化来定义个别的隐式参数,柯里化有多个参数列表,多个小括号括起来的参数列表
----add(x:Int)(implicit y:Int ,z:Int) --x :是正常参数 y/z :都是隐式参数
def myShow(implicit name:String ,age:Int):Unit ={
println(name + " " + age)
}
--------测试---------
def main(args: Array[String]): Unit = {
new Show().myShow("reba",12)
---context中只能有一个匹配类型的数据
---如果找到多个匹配类型的数据就会报错
import MyValue._ ----导入隐式函数,在 MyValue 的上下文中寻找隐式数据
new Show().myShow ---lisi 19
}
}
2 隐式函数
函数处理的数据类型 => 返回的数据类型 ,当遇到对应的类型 => 类型处理的时候隐式函数会自动的应用
增强一个类
添加不存在的函数 ,implicit 作用于函数 ,称之为隐式函数
-----定义一个只有 add 方法的类----
class User1{
val add = (x:Int ,y:Int) => {
x+y
}
}
-----定义一个增强的类 ,定义一个减法方法------
class Advice(val user:User){
val substract = (x:Int ,y:Int) =>{
x-y
}
}
-----写一个隐式转换的方法类------
将 User1 类隐式转换成增强的类 ,这样我们被增强的类既有自己的方法,也有增强类的方法
这样就实现了隐式函数对一个类的增强的目的
object ImplicitAdvice{
-----将 User1 类隐式转换成增强类,实现对 User 类增强的目的
implicit def advice(user:User) = {
new Advice(user)
}
}
-------测试-------
object Test1{
def main (args:Array[String]):Unit = {
val user = new User1
val sum:Int = user.add(1,2) ----调用add方法
import ImplicitAdvice._ ----导入隐式函数
val res:Int = user.substract(3,2) ---调用substract方法
println(sum) --3
println(res) --1
}
}
3 隐式类(增强一个类型)
隐式类约束 :
隐式类必须有一个带一个参数的主构造器
必须定义在另一个 class/object/trait 里面(不能独立定义)
隐式类构造器只能带一个不是implicit修饰的参数
作用域中不能有与隐式类类型相同的成员变量 ,函数以及 object 名称
------------定义一个类---------
class B {
val add=(x:Int ,y:Int) => {
x+y
}
}
-----------定义一个隐式类--------
object Implicit01 {
-------隐式类只能对一种类型的类进行增强 ,B类或子类增强-----
implicit class RichB(b:B){
def multiply(x:Int ,y:Int) = {
x*y
}
}
}
------------演示增强效果-------
object Test01 {
def main(args: Array[String]): Unit = {
val b = new B //B类 以及他的子类
println(b.add(12, 1)) ---- 13 调用普通类方法
import Implicit01._ -----导入隐式类
println(b.multiply(12, 2)) ----24 调用隐式类方法
}
}
4 隐式注意
- 如果代码不需要隐式转换就可以通过编译 ,则不会引入隐式转换
- 隐式转换只会匹配一次,即隐式转换最多发生一次
- 存在二义性的隐式转换会报错(就是导入两个隐式转换时 ,代码不知道应用哪个,所有就会报错)
七 泛型详解
泛型类似于 java 中的泛型 ,泛型可以定义在类上 / 方法上和属性上 ,用于约束类型.
如果我们要求函数的参数可以接受任意类型 ,也可以使用泛型 ,这个类型可以代表任意的数据类型 .
1 泛型类
-------定义一个泛型类,指定这个类是 kv 的模式--------
class GenericClass[K,V](k:K,v:V) {
def show():Unit ={
println(k + "--->" + v)
}
}
--------定义一个普通的类--------
object Test01 {
def main(args: Array[String]): Unit = {
------不需要指定类型 自动推断
val gc = new GenericClass("zss", 99.99)
------指定类型
val gc1 = new GenericClass[String, String]("jim", "som")
---指定类型 ,如果传入的数据不符合指定类型会报错--这里 "CAT"是报错的,因为这里需要Int类型的值
val gc2 = new GenericClass[String, Int]("HUU", "CAT")
gc.show() --- zss--->99.99 调用泛型类里面的方法
}
}
2 泛型方法
-------伴生类-----------
class GenericClass01{
-----定义一个泛型方法,参数和返回值都是泛型
def show[T](x:T):T = {
x
}
}
-------伴生对象------------
object GenericClass01 {
def main(args: Array[String]): Unit = {
val gf = new GenericClass01
----不指定类型 ,自动推断
println(gf.show("hello"))
println(gf.show(23))
println(gf.show(99.99))
----指定函数传入的类型
println(gf.show[Int](12))
gf.show[String](12) ----这里是报错的,因为指定了字符类型,而这里传入的是整数类型,所以报错
}
}
3 上届
有时需要明确指定泛型类 T 是某个类或者特质(trait) 的子类型,比如 Comparable 接口的子类 ,这样才能对泛型类对象进行比较操作,Comparable 是泛型的上届!
1) Java泛型里表示某个类型是A类型的子类型 ,使用 extends 关键字 ,这种形式叫 opper bounds(上限或上界) ,语法如下 :
<T extends A> 或者用调配符表示 <? extends A>
2) Scala 泛型里表示某个类型是A类型的子类型, 也称上界或上限 ,使用 <: 关键字 ,语法如下 :
[ T <: A] 或者用通配符 [ _ <: A]
----定义的两个class 类---------
----定义一个继承了Ordered 的类传入上界泛型中去,指定了类的泛型
class User(var name:String,var age:Int) extends Ordered[User]{
override def compare(that: User): Int = {
this.age - that.age
}
}
----上界,传入的参数必须是定义的Ordered 的子类 ,否则在运行的时候会报错
class Upper01[T<: Ordered[T]](v1: T ,v2: T) {
def max():T = {
if(v1 > v2) v1 else v2
---条件为true ,就返回 v1 ,为 false 就返回 v2,这是一种排序规则,哪个大就返回谁------
----如果 32>23 为true,就返回 32 ;如果 12>23 为false ,就返回 23 ,总之返回最大值-----
}
}
----------定义的object类 ,用于测试--------------
object UpperTest01{
def main(args: Array[String]): Unit = {
val ut = new Upper01(new User("A",32),new User("A",23))
println(ut.max().age) -----32 调用max方法,比较 v1
}
}
4 下界
要求: 传入的类型或处理的类型是某种类型的父类
1) 在 java 泛型里表示某个类型是 A 类型的父类型 ,使用 super 关键字 ,语法是 :
<T super A> 或者用通配符的形式 <? super A>
2) 在 Scala 中的下界或下限,表示 T 是 A 的父类型 ,使用 >: 关键字 ,语法是 : [T >: A] 或者通配符 [ _ >: A]
---------下界必须是 A 的父类---------
class A extends B
class B {
}
class C
---------泛型中的T的类型必须是A的父类 或者A类型 ,下界-----------
------伴生类-------
class LowDemo[T >: A]{
def test(x:T)={
println(x)
}
}
-------伴生对象--------
object LowDemo{
def main(args: Array[String]): Unit = {
-----泛型中执行传入A的父类以及本身
val ld = new LowDemo[A]
println(ld)
}
}
5 上下文界定 (:):在隐式参数中,需要一个隐式参数来将泛型中的类型隐式转换成对应的类型
上下文界定 (:) 和视图界定(<%) 这两个都跟隐式转换有关 ,;
上下文界定 (:) 需要插入一个隐式参数
视图界定(<%) 需要传入一个隐式转换方法或隐式转换函数
注意 : 使用柯里化方法也可以实现 视图界定 或 上下文界定
6 视图界定(<%)
<% 的意思是 "view bounds"(视界) ,它比 <: 适用的范围更广 ,除了所有的子类型 ,还允许隐式转换类型 ,目的是为了省略隐式值的书写
-----定义一个继承了Ordered 的类传入上界泛型中去,指定了类的泛型
class User(var name:String,var age:Int) extends Ordered[User]{
override def compare(that: User): Int = {
this.age - that.age
}
}
----视图界定,存在一个隐式转换 直接将泛型T转成Ordered的子类-----
-----定义一个object类-----------
object ViewBounds01 {
def max01[T <% Ordered[T]](x:T ,y:T):T = {
if(x > y) x else y ------ 一种排序规则 ,哪个大就返回哪个
}
def main(args: Array[String]): Unit = {
val res: User = max01(new User("",23),new User(" ",34))
println(res.age) -----34
}
}
7 逆变和协变
7.1 泛型集合都是协变的 ,对于List 而言 ,如果B是A的子类 ,那么List[B] 也是List[A] 的子类 ,即可以把List[B] 的实例赋值给List[A]变量 .
7.2 Scala 的协变(+) covariant ,逆变(-) contravariant ,不可变 invariant .
7.3 对于一个带类型参数的类型 ,比如 List[T] ,如果对 A 及其子类型B ,满足 List[B] 也符合 List[A] 的子类型 ,那么就称为 covariant(协变) ,如果List[A] 是List[B] 的子类型 ,即与原来的父子关系正好相反 ,则称为 contravariant(逆变) .如果一个类型支持协变或逆变 ,则称这个类型为variant(可变的或变型),否则称为不可变 invariant .
7.4 在 java中 ,泛型类型都是 invariant ,比如 List <String> 并不是 List<Object> 的子类型 . 而scala支持 ,可以在定义类型时声明(用加号表示为协变 ,减号表示逆变)
7.5 如 :trait List[+T] ,在类型定义时声明为协变这样会把 List[String] 作为 List[Any] 的子类型
C[+T]:如果A是B的子类,那么C[A]是C[B]的子类,称为协变
C[-T]:如果A是B的子类,那么C[B]是C[A]的子类,称为逆变
C[T]:无论A和B是什么关系,C[A]和C[B]没有从属关系。称为不变.
八 比较器
Scala 提供两个特质(trait) Ordered 与 Ordering 用于比较 .其中,Ordered 混入(mix) Java的 Comparable 接口 ,而 Ordering 则混入Comparable 接口 .众所周知 ,在Java中 ,实现 Comparable 接口的类, 其对象具有了可比较性 ;实现了Comparator 接口的类 ,则提供一个外部比较器 ,用于比较两个对象 .
Ordered 的用法和java中的 Comparable 接口一致 ,定义了单一的比较方式
Ordering 的用法和java中的Comparator 接口一致 ,可以自定义多种比较 ,方式更灵活
1 Ordered
Ordered提供了隐式转换 ,这样自定义的类就不用实现Ordered 接口单一排序方式
-------定义一个class类------------
class User2(val name:String ,val age:Int){
override def toString: String = {
s"$name : $age"
}
}
-------定义一个object类----------
object Test1 {
implicit def myOrders(user:User)={
new Ordered[User] {
override def compare(that: User) = {
if( user.name.compareTo(that.name) == 0){
-(user.age - that.age) ---如果名字相同,就根据年龄进行降序排序
}else{
user.name.compareTo(that.name) ----如果名字不同,就按照名字进行升序排序
}
}
}
}
def main(args: Array[String]): Unit = {
val ls = List(new User("zs",21),new User("zs",34),new User("abc",23))
ls.sorted.foreach(println) ---abc:23 zs:34 zs:21
}
}
2 Ordering
--------定义一个class类----------
class User2 (val name:String , val age:Int) {
-----重写toString
override def toString: String = {s"$name : $age"}
}
-------定义一个object类------------
-----类似于上面的隐式函数
val ls = List(new User2("zs",21),new User2("zs",34),new User2("abc",23))
-----隐式转换
implicit object Usero extends Ordering[User2]{
override def compare(x: User2, y: User2): Int = {
x.age - y.age ----按照对象的年龄,进行升序排序
}
}
ls.sorted.foreach(println) ---zs:21 abc:23 zs:34