一、偏函数
偏函数的基本概念
1)偏函数并非对所有的输入值都有意义
2)偏函数是PartialFunction[A,B]特质的一个实例(A是参数类型,B是返回值类型)(partial:偏爱的,部分的),需要重写isDefinedAt(x:A):Boolean相当于过滤,只要返回值为true的元素、和apply(v1:A):B:将前面返回值为true的元素转换成B类型,并做相关处理。
3)简写的偏函数:被花括号括起来的一组case语句就是一个偏函数
4)理论上任何高级函数都可以接受偏函数,但只有collcet会把偏函数作为偏函数对待,其他都是当做普通函数而已。
5)用偏函数的好处是容易对参数进行提取
2.实现一个需求
1、需求
有一个集合List(1,2,3,4,"abc"),只要其中的数字,非数字过滤掉不要,并将所有数字+1。返回值应为List(2, 3, 4, 5)
2、思路一:使用filter+map
val list:List[Any]=List(1,2,3,4,"abc")
val list2=list.filter(_.isInstanceOf[Int]).map(_.asInstanceOf[Int]+1)
println(list2)//List(2, 3, 4, 5)
3、思路二:偏函数
1)普通写法
//实现PartialFunction函数,并重写其中的链各个方法
val f1=new PartialFunction[Any,Int] {
override def isDefinedAt(x: Any): Boolean = x.isInstanceOf[Int]
//接收传入的参数。只要返回值为true的元素,即:只要整数
override def apply(v1: Any): Int = v1.asInstanceOf[Int]+1
//将上面过滤出来的元素转换成int,并做处理
}
val list=List(1,2,3,4,"abc")
val list2=list.collect(f1)
println(list2) //List(2, 3, 4, 5)
说明:
1、创建偏函数需要重写两个方法:isDefinedAt、apply,如果指定new PartialFunction[A,B]中的泛型,则自动生成的这两个需求重写的函数(ctrl+i)中的参数和返回值自动会写好。
2、当使用偏函数时,会遍历集合中的所有元素,编译器执行流程时先执行isDefinedAt。如果为true就会执行apply,构建一个Int对象。如果为false就会忽略这个元素。
3、map函数不支持偏函数,因为map无法过滤集合中的元素。
4、collect函数支持偏函数,可以将collect(f1)看成是filter(内调用isDefinedAt)+map(内调用apply)
2)简写
//花括号内直接写case语句,就是一个偏函数
val list = List(1, 2, 3, 4, "abc")
val list2=list.collect({case x:Int=>x+1})
println(list2)
二、模式匹配
1、介绍
scala中的模式匹配类似于java中的swich语法,但是更加强大。模式匹配语法中,采用match关键字声明。每个分支采用case关键字进行声明。当需要匹配时,会从第一个case分支开始,如果匹配成功,那么执行对应的逻辑代码。如果匹配不成功,则继续执行下一个分支进行判断。当所有的case都不匹配,那么会执行case _分支,类似于java中的default语句。
2、知识点
1)如果所有case都不匹配,那么会执行case _分支。如果此时没有写case _分支,则会抛出MatchError异常。
2)每个case中不用写break语句,也会自动中断该case。
3)可以在match中使用其他类型(任意类型),而不仅仅是字符。
4)=>后面的代码块到下一个case,是作为一个整体执行的,可以使用{}括起来,也可以不括。
5)模式匹配的值:模式匹配是表达式,所以也有自己的值。
2、基本使用
val a1=10; val a2=20;
var res=0;
val c='+';
c match {
case '+' => res=a1+a2
case '-' => res=a1-a2
case '*' => res=a1*a2
case '/' => res=a1/a2
case _ => println("你的运算符有问题")
}
println(res) //30
因为模式匹配表达式有值,所以上面的代码可以简写为
val a1=10; val a2=20;
val c='+';
val res=c match {
case '+' => a1+a2
case '-' => a1-a2
case '*' => a1*a2
case '/' => a1/a2
case _ => println("你的运算符有问题")
}
println(res) //30
3、类型匹配
val people:Any=Map("name"->"pangpang",2->"num")
val res=people match{
case x1:Int =>println(x1);"匹配到的是Int"
case x2:String =>println(x2); "匹配到的是String"
case x3:Array[Int] =>println(x3); "匹配到的是Array[Int]"
case x4:Map[_,_] => println(x4); "匹配到的是Map[Int,Int]"
case _:BigInt => "匹配到的是BigInt" //可以不起名
case _=> "啥都没匹配到"
}
println(res)
说明:
1)在进行类型匹配时,编译器会预先检测是否有可能得匹配,如果没有则报错。
2)类型匹配成功之后会把people的值赋值给case后面跟着的变量,而不需要做类型的转换。
3)对数组来说提供数组中元素的类型是必要的,否则没办法确定数组的类型。
4)对于集合类型比如Map,提供内部元素的类型是无用的,因为内部元素的类型无法区分Map的类型,例如对于Map来说Map[Int,String]和Map[Int,Int]没有区别,所以应该写通用类型:Map[_,_]。
4、匹配数组
这里的匹配数据不是指匹配类型,而是匹配数组的内容
val arr:Array[Int]=Array(1,20,11)
val res=arr match{
case Array(0) => "Array(0)" //匹配只有1个元素,并且元素是0的数组
case Array(0,1) => "Array(0,1)" //匹配只有2个元素,并且元素是(0,1)的数组
case Array(x,y,z) => x+" "+y+" "+z //匹配长度为3的数组,如果匹配成功,会依次将arr中的元素赋值给x,y,z
case _=> "啥也没有匹配到"
}
println(res)
5、匹配元组
val my_tuple=(10,20,30)
my_tuple match {
//case(a,b)=>println(a+" "+b) 报错,匹配元组的时候,元组内元素的个数是确定的
case(_,_,30)=>println("(_,_,30)") //3个元素,最后一个为30
case(a,b,c)=>println(a+" "+b+" "+c) //含有3个元素的元组
case _=>println("啥都不匹配")
}
三、样例类
1、知识点
1)样例类:用case修饰的类
2)样例类回自动生成一系列方法,比如:apply、unapply、toString、copy、hashCode、equals等
3)样例类是为模式匹配而优化的类
4)在样例类对应的伴生对象中自动提供apply方法让你不用new关键字就能构造出相应的对象
5)样例类的伴生对象中自动提供unapply方法让模式匹配可以工作
6)除上述外,样例类和其他类型完全一样,可以给样例类天假方法和字段
2、对象匹配(提取器)
1)提取器:是一个带有unapply方法的对象
2)unapply方法:是apply方法的反向操作,unapply接收一个对象,然后从对象中提取值,提取的值通常是用来构造该对象的值。
3)对象匹配:使用提取器进行匹配。case中对象的unapply方法,返回Some集合则为匹配成功,返回None集合则为匹配失败。一般情况下我们都把要提取的数据封装到Some中返回(Some和None都是Option类的子类)。
3、演示
object TestPian {
def main(args: Array[String]): Unit = {
val num:Double=9.0
num match {
case Square(z) => println(z)
case _=> println("啥也没有匹配到")
}
println(math.sqrt(36))
}
}
object Square{//平方
def apply(a:Double):Double=a*a
def unapply(arg:Double):Option[Double]=Some(math.sqrt(arg)) //sqrt:求平方根
}
说明:
1)在case匹配的过程中,如果匹配到这种:伴生对象(参数)的形式,就会调用这个伴生对象的unapply方法,来提取对象
2)调用unapply的时候,会把前面的匹配表达式(本例中的num)传递给unapply
3)如果unapply返回的是Some则表示匹配成功,并将unapply的返回值赋值给伴生对象(参数)。如果返回值是None则表示匹配失败,继续下一个case的匹配...
四、隐式转换
1、介绍
当把范围大的数据类型转化给范围小的数据类型时,必须要进行强制类型转换,否则会报错。隐式转换可以让这种情况自动进行类型转换。
隐式转换是以implicit关键字声明的带有单个参数的函数,这种函数会根据需要自动被调用。
隐式函数会根据需要自动被调用,隐式函数将值从一种类型转换为另一种类型。
2、隐式转换需要注意的事项
1)隐式函数的函数名可以是任意的,隐式转换与函数名无关,只与函数的参数表和返回值类型有关(有点像java中的函数重载)
2)隐式函数可以有多个(即隐式函数列表),但是需要保证在当前环境下,只有一个隐式函数能被识别。
3)隐式函数的命名应为:Source2Target,例如Double2Int
3、快速入门
def main(args: Array[String]): Unit = {
val a:Int=3.14
val b:Int=1000L
println(a+" "+b)
}
implicit def f1(x:Double):Int={
x.toInt
}
implicit def f2(x:Long):Int={
x.toInt
}
四、scala和java的关系、函数式编程
1、什么是scala
scala是一门多范式的语言,这里说的范式,是指编写程序的方式。因为它融合了面向对象的编程和函数式编程。
scala是运行在java虚拟机(JVM)上的,并将面向对象和函数式编程的最佳特性集合在一起的静态类型编程语言(运行之前一定知道是什么类型)。
2、scala和java的关系
scala源代码(.scala)先被编译成java字节码(.class)文件,然后运行于JVM之上,并可以调用现有的java类库。
3、什么是函数式编程
函数式编程主要的思想是把运算过程尽量写成对一系列嵌套的函数的调用,函数式编程可以接受吧函数当做输入(参数)和输出(返回值),函数式编程最重要的就是函数。
五、面向对象编程
面向对象编程的三大特征:封装、继承、多态
1、封装
1)封装性:插线板,使用者只需要知道网上插电器可以工作即可,至于内部线路如何连接,不需要了解。
2)什么是封装:在类中,对于不想被类外直接访问的成员,进行私有化,同时对外提供一个共有的方法,来访问私有的成员。
3)private:使用private访问权限实现成员的私有化,private修饰的成员就私有成员,只有在类内部能直接访问,类外不能直接访问。
4)get和set方法
public 属性类型 getXx(){
return 属性;
}
public void setXx(参数类型 参数){
this.xx = 参数;
}
2、继承
1)继承的概念:在原有类的基础上,产生一个新的类,在新的类中可以访问原有类中的非私有成员,并且可以添加一些自己独有的成员,这个过程叫做继承。不能被子类继承的成员:
私有成员:私有成员不能被子类继承
构造方法:父类中的构造方法不能被子类继承,但是会在子类的构造方法中调用(子类的构造方法中默认第一条语句是调用父类的构造方法)。
2)继承的特点
类单继承,一个子类只有一个父类。
一个父类可以有多个子类。
子类只能继承父类中的非私有成员
父类的引用可以指向子类的实例,但是,父类的引用无法访问子类中的独有成员。
3、多态
1)什么是多态
一个事物有多重表现形态,对象的多重形态,方法的多样性。就是子类对象给父类对象赋值。此时只有在调用子类的非静态方法时,调用的是子类的,其余:非静态成员、静态成员、静态方法都是父类的。
2)代码
package day10;
class Father{
public void f()
{
System.out.println("父类f");
}
}
class Son extends Father{
public void f()
{
System.out.println("子类f");
}
}
public class Test {
public static void main(String[] args) {
//1向上转型,子类对象给父类引用赋值
Father bb=new Son();//bb看起来是Father类型的,但本质上是Sun类型的。
bb.f();//输出:子类f
//此时f只有在调用非静态方法时调用的是子类的,此外:非静态成员,静态成员,静态方法都是调用的自己的
}
}