Scala
模式匹配
概述
-
Scala中的模式匹配,类似于Java中
switch-case
结构,但是比Java中的switch-case
的功能更加强大 -
语法结构
scala选项 match { case c1 => op1 case c2 => op2 ... case _ => op }
-
case _
类似于Java中的default,当其他的所有的case都不匹配的时候,才会使用case _
-
在Scala中,没有
break
关键字,每一个case执行完成之后自动结束,不会顺延执行下一个case -
案例
scalapackage com.fe.matchx import scala.io.StdIn object MatchDemo1 { def main(args: Array[String]): Unit = { // 获取符号 val symbol = StdIn.readChar() // 获取数字 val x = StdIn.readDouble() val y = StdIn.readDouble() // 四则运算 val r = symbol match { case '+' => x + y case '-' => x - y case '*' => x * y case '/' => x / y case _ => 0 } println(r) } }
-
如果没有
case _
,且所有的case都不匹配的时候,就会抛出MatchError
常见匹配
-
常量匹配:在Scala中,
match-case
匹配的时候,不限制元素的类型scalapackage com.fe.matchx object MatchDemo2 { def main(args: Array[String]): Unit = { // Scala中,match不限制元素的类型 def typeOf(x: Any): Unit = x match { case 3 => println("整数") case 5.5 => println("小数") case true => println("布尔值") case "abc" => println("字符串") case _ => println("其他类型") } val t = (3, true, "abc", 5.5, 'a') t.productIterator.foreach(x => typeOf(x)) } }
-
类型匹配
scalapackage com.fe.matchx object MatchDemo3 { def main(args: Array[String]): Unit = { // 判断元组中每一个元素的类型 val t = (4, 5.5, 6, true, 'a', "abc", 3.5f, 2L) def typeOf(x: Any): String = x match { case _: Int => "Int" case _: Double => "Double" case _: String => "String" case _: Long => "Long" case _: Boolean => "Boolean" case _: Char => "Char" case _ => "other type" } t.productIterator.foreach(x => println(typeOf(x))) } }
-
集合匹配
scalapackage com.fe.matchx object MatchDemo4 { def main(args: Array[String]): Unit = { // 对列表中的元素类型进行匹配 def listType(list: List[_]) = list match { case _: List[Int] => "Int" case _: List[Double] => "Double" case _ => "other type" } val list = List(2, 3, 4, 5) println(listType(list)) // 对列表中的元素进行限定 def listMatch(list: List[_]) = list match { case List(3) => "List(3)" // 固定匹配,就匹配这个List(3) case List(2, 4, 6) => "List(2,4,6)" // 固定匹配,就匹配这个List(2, 4, 6) case List(_, _) => "长度为2的List" // 匹配长度为2的列表 case List("abc", _, _) => "列表的长度为3,第一个元素是abc" // 匹配列表的长度为3,并且第一个元素是"abc" case List("hello", _*) => "第一个元素是hello的列表" // 匹配第一个元素是"hello"的列表 } val list1 = List(3) println(listMatch(list1)) val list2 = List(2, 4, 6) println(listMatch(list2)) val list3 = List('a', 'b') val list4 = List(3.5, 4.8) println(listMatch(list3)) println(listMatch(list4)) val list5 = List("abc", "hello", "hi") val list6 = List("abc", "hadoop", "Scala") println(listMatch(list5)) println(listMatch(list6)) val list7 = List("hello", "hi", "system") val list8 = List("hello") val list9 = List("hello", "david", "amy", "bob") println(listMatch(list7)) println(listMatch(list8)) println(listMatch(list9)) } }
模式守卫
-
如果在进行匹配的时候,还需要指定其他条件,那么可以使用模式守卫
scalapackage com.fe.matchx object MatchDemo5 { def main(args: Array[String]): Unit = { // 判断数字是几位数 def test(n: Int) = n match { case _: Int if n < 0 => "负数" case _: Int if n < 10 => "一位数" case _ => "两位数" } println(test(5)) // 列表 def listMatch(list: List[Int]) = list match { case x if x.length == 3 && x.contains(5) => "case 1" // 列表长度为3,且包含数字5的列表 case y if y.contains(7) => "case 2" // 列表中包含数字7 } val list1 = List(3, 1, 5) val list2 = List(5, 2, 4) println(listMatch(list1)) println(listMatch(list2)) val list3 = List(7, 1, 3, 4) println(listMatch(list3)) } }
函数参数匹配
-
案例
scalapackage com.fe.matchx object MatchDemo6 { def main(args: Array[String]): Unit = { // toInt ===> AnyVal val values: List[Any] = List(true, 1, 3.5, "25.53", -4, "35", 'a', false, -6.5) // 将列表中的所有的元素转化为整数 // 如果是布尔值,那么true转化为1,false转化为0 // 如果是字符串,那么就转化为对应的值 val r = values.map(x => x match { case a: Double => a.toInt case b: String => b.toDouble.toInt case c: Char => c.toInt case d: Boolean => if (d) 1 else 0 case e: Int => e }) println(r) // 对参数进行匹配,并且在这个过程中,是直接去进行了match操作,而没有进行其他操作 // ({}),如果代码只有一行,()或者{}可以省略其一;如果有多行,那么只能考虑省略() // val r2 = values.map { case a: Double => a.toInt case b: String => b.toDouble.toInt case c: Char => c.toInt case d: Boolean => if (d) 1 else 0 case e: Int => e } println(r2) } }
对象匹配
-
Scala中不只是可以对数据进行匹配,还能对对象进行匹配,到那时要求对应对应的类中必须提供了解析函数
unapply
scalapackage com.fe.matchx object MatchDemo7 { def main(args: Array[String]): Unit = { val p1 = Person("Bob", 21, "male") val p2 = Person("Amy", 22, "female") val p3 = Person("David", 22, "male") val p4 = Person("Grace", 23, "female") val p5 = Person("Henry", 21, "male") val persons = List(p1, p2, p3, p4, p5) // 对集合中数据进行操作 def testList(p: Person) = p match { case Person(_, _, "female") => "女性用户" // 目标客户:女性 case Person(_, 21, _) => "21岁客户群体" // 目标客户:21岁群体 case _ => } persons.foreach(x => println(testList(x))) } } class Person { var username: String = _ var age: Int = _ var gender: String = _ } object Person { def apply(username: String, age: Int, gender: String): Person = { val p = new Person p.username = username p.age = age p.gender = gender p } // 解析函数,用于解析对象 // 在Scala中,为了防止空指针异常,会考虑将结果封装成Option // 在封装Option的时候,如果值为空,对应的封装成None // 如果值不为空,封装成Some def unapply(p: Person): Option[(String, Int, String)] = { if (p == null) None else Some(p.username, p.age, p.gender) } }
样例类
-
样例类的声明和普通类差别不大,区别在于,在
class
之前添加了case
scalacase class 类名 {}
-
样例类的普通类的区别
- 如果一个类被声明为样例类,那么Scala在编译的时候会自动的给这个样例类生成一个伴生对象;普通类需要手动声明伴生对象
- 样例类对应的伴生对象中,会自动覆盖
apply
、unapply
、toString
、equals
和hashCode
- 也正因为样例类中会自动覆盖apply和unapply函数,所以可以参与模式匹配
- 样例类的主构造器中声明的参数默认是val修饰
-
案例
scalapackage com.fe.matchx object CaseClassDemo { def main(args: Array[String]): Unit = { val s1 = Student("Bob", 10, "male", 3) val s2 = Student("Tom", 10, "male", 3) val s3 = Student("Sam", 10, "male", 3) val s4 = Student("Cam", 10, "female", 3) val s5 = Student("Amy", 10, "female", 3) val s6 = Student("Dan", 10, "male", 3) val students = List(s1, s2, s3, s4, s5, s6) for (s <- students) { val r = s match { case Student(_, _, "male", _) => "男生" case _ => "女生" } println(r) } } } // 自动给Student生成伴生对象`object Student` case class Student(name: String, age: Int, gender: String, grade: Int) { // 声明在类中的属性,默认不是被apply接收和unapply解析 // var address:String = _ } /* object Student{ def apply(name: String, age: Int, gender: String, grade: Int):Student = new Student(name, age, gender, grade) def unapply(s:Student) = Some(u.name, u.age, u.gender, u.grade) } */
偏函数
-
对于Scala的集合函数而言,分为全函数和偏函数
- 全函数:对集合中所有的元素进行操作,例如
map
,reduce
等 - 偏函数:对集合中的部分元素进行操作
- 全函数:对集合中所有的元素进行操作,例如
-
案例
scalapackage com.fe.matchx object MatchDemo8 { def main(args: Array[String]): Unit = { val list: List[Any] = List(2, 'a', 6, 3.5, true, 8, 5.21, 9, false, "abc") // 需求:将列表中的整数挑选出来,计算整数的平方形式 // 方式一:过滤 -> 转化为整数 -> 计算平方 val r = list.filter(_.isInstanceOf[Int]) .map(_.asInstanceOf[Int]) .map(x => x * x) println(r) // 方式二:偏函数 val r2 = list.collect { case a: Int => a * a } println(r2) } }
异常机制
-
Scala中的异常机制,类似于Java中的异常机制,但是又不完全一样。Scala中所有的异常都是在运行的时候才会检查,没有所谓的编译时异常
-
Scala捕获异常的方式
scalatry{ 代码块 } catch { case e1:异常名 => 处理 case e2:异常名 => 处理 ... } finally{ 代码块 }
-
案例
scalapackage com.fe.exception import scala.io.StdIn object ExceptionDemo { def main(args: Array[String]): Unit = { try { val x = StdIn.readInt() val y = StdIn.readInt() println(x / y) } catch { case e: ArithmeticException => println("捕获到一个算术异常") } } }
-
在Scala中,如果一个函数没有别的返回值,而是只抛出了一个异常,那么此时这个函数的返回值类型是
Nothing
scaladef testException():Nothing ={ throw new IllegalArgumentException }
-
Scala中提供了一个注解
throws
,用于标记这个函数有异常抛出。这个注解仅仅起提示作用,不要求使用者必须处理这个异常
隐式转换(implicit
)
概述
- 当编译器对当前代码第一次编译失败的时候,会在当前的环境中查找能够让代码编译通过的方式,用于将当前的类型进行转换,进行二次编译,这个过程就称之为隐式转换
- 隐式转换包含隐式函数、隐式参数和隐式类
- Scala运行的时候,自动加载
Predef
类,Predef
类中定义了大量的隐式转换
隐式函数
-
隐式函数,能够在不改变某一个类的前提下,扩展这个类的功能
-
案例
scalapackage com.fe.implicitx object ImplicitDemo { def main(args: Array[String]): Unit = { // 正常情况下,x是Int类的对象,所以无法调用SumInt中的函数 val x: Int = 10 // 在没法改变Int类的前提下,又想扩展Int的功能 // 此时,就意味着需要将Int -> SumInt对象之后才能调用 // 此时可以定义一个隐式函数来完成这个转换过程 // 在底层,偷偷的将Int包装成了SumInt implicit def transform(n:Int):SumInt = new SumInt(n) println(x.sum) println(x.^(5)) } } class SumInt(val n: Int) { def sum: Int = { println("我来啦~~~") var r = 0 for (i <- 1 to n) r += i r } }
隐式参数
-
隐式参数,指的是在作用域中定义的可以被自动匹配的参数
-
注意:
- 同一个作用域中,相同类型的隐式参数只能有1个
- 编译器是根据参数类型匹配,而不是根据参数名称匹配
-
案例
scalapackage com.fe.implicitx object ImplicitDemo2 { def main(args: Array[String]): Unit = { implicit val s: String = "男" // 性别默认为"男",除非用户指定为"女" // 第三个参数声明为隐式参数,所以如果调用的时候不给定,自动的在当前作用域内自动寻找同类型的隐式变量 def register(username: String)(age: Int)(implicit gender: String): Unit = println(s"新注册用户$username,性别$gender,年龄$age") // 隐式参数的优先级高于默认参数 def login(username: String)(implicit gender: String = "女"): Unit = println(s"用户$username,性别$gender 登陆") register("Bob")(15) register("David")(16) login("Bob") login("Any")("女") } }
隐式类
-
如果需要对某一个类产生的对象进行功能扩展,而又不想要改变原来的类,那么除了可以使用隐式函数,还可以使用隐式类
-
注意:隐式类不是顶级类,即隐式类不能直接定义到包中,而必须定义到类、伴生对象或者包对象中
-
饮食类必须提供构造器,并且构造器中只能有1个参数!
scalapackage com.fe.implicitx object ImplicitDemo3 { def main(args: Array[String]): Unit = { val x: Int = 5 println(x.fac) } implicit class FacInt(n: Int) { def fac: Int = { var r = 1 for (i <- 1 to n) r *= i r } } }