第七课 尚硅谷Scala语言学习-模式匹配、异常、泛型隐式转换
第一节 模式匹配
1.1 基本语法
Scala 中的模式匹配类似于 Java 中的 switch 语法 ,但是 scala 从语法中补充了更多的功能,所以更加强大。模式匹配语法中,采用 match 关键字声明,每个分支采用 case 关键字进行声明,当需要匹配时,会从第一个 case 分支开始,如果匹配成功,那么执行对应的逻辑代码,如果匹配不成功,继续执行下一个分支进行判断。如果所有 case 都不匹配,那么会执行 case _
分支,类似于 Java 中 default 语句。
如果所有 case 都不匹配,那么会执行 case _
分支,类似于 Java 中 default 语句,若此时没有 case _ 分支,那么会抛出 MatchError 。 每个 case 中,不需要使用 break 语句,自动中断 case 。match case 语句可以匹配任何类型,而不只是字面量。 => 后面的代码块,直到下一个 case 语句之前的代码是作为一个整体执行,可以使用{}括起来,也可以不括 。 模式守卫。如果想要表达匹配某个范围的数据,就需要在模式匹配中增加条件守卫。
package chapter08
object Test01_PatternMatchBase {
def main( args: Array[ String ] ) : Unit = {
val x: Int = 5
val y: String = x match {
case 1 => "one"
case 2 => "two"
case 3 => "three"
case _ => "other"
}
println( y)
val a = 25
val b = 13
def matchDualOp( op: Char ) : Int = op match {
case '+' => a + b
case '-' => a - b
case '*' => a * b
case '/' => a / b
case '%' => a % b
case _ => - 1
}
println( matchDualOp( '+' ) )
println( matchDualOp( '/' ) )
println( matchDualOp( '\\' ) )
println( "=========================" )
def abs( num: Int ) : Int = {
num match {
case i if i >= 0 => i
case i if i < 0 => - i
}
}
println( abs( 67 ) )
println( abs( 0 ) )
println( abs( - 24 ) )
}
}
1.2 模式匹配类型
匹配常量 。Scala 中,模式匹配可以匹配所有的字面量,包括字符串,字符,数字,布尔值等等。匹配类型 。需要进行类型判断时,可以使用前面所学的isInstanceOf[T]和asInstanceOf[T]
,也可使用模式匹配实现同样的功能。匹配数组 。scala 模式匹配可以对集合进行精确的匹配,例如匹配只有两个元素的、且第一个元素为 0 的数组。匹配列表 匹配元组
package chapter08
object Test02_MatchTypes {
def main( args: Array[ String] ) : Unit = {
// 1 . 匹配常量
def describeConst( x: Any) : String = x match {
case 1 = > "Int one"
case "hello" = > "String hello"
case true = > "Boolean true"
case '+' = > "Char +"
case _ = > ""
}
println( describeConst( "hello" ))
println( describeConst( '+' ))
println( describeConst( 0.3 ))
println( "==================================" )
// 2 . 匹配类型
def describeType( x: Any) : String = x match {
case i: Int = > "Int " + i
case s: String = > "String " + s
case list: List[ String] = > "List " + list
case array: Array[ Int] = > "Array[Int] " + array.mkString( "," )
case a = > "Something else: " + a
}
println( describeType( 35 ))
println( describeType( "hello" ))
println( describeType( List( "hi" , "hello" )) )
println( describeType( List( 2 , 23 )) ) // 因为LIST 泛型擦除也能匹配成功
println( describeType( Array( "hi" , "hello" )) ) // array 不存在泛型擦除
println( describeType( Array( 2 , 23 )) )
// 3 . 匹配数组
for ( arr < - List(
Array( 0 ) ,
Array( 1 , 0 ) ,
Array( 0 , 1 , 0 ) ,
Array( 1 , 1 , 0 ) ,
Array( 2 , 3 , 7 , 15 ) ,
Array( "hello" , 1 , 30 ) ,
)) {
val result = arr match {
case Array( 0 ) = > "0"
case Array( 1 , 0 ) = > "Array(1, 0)"
case Array( x, y) = > "Array: " + x + ", " + y // 匹配两元素数组
case Array( 0 , _*) = > "以0开头的数组"
case Array( x, 1 , z) = > "中间为1的三元素数组"
case _ = > "something else"
}
println( result)
}
println( "=========================" )
// 4 . 匹配列表
// 方式一
for ( list < - List(
List( 0 ) ,
List( 1 , 0 ) ,
List( 0 , 0 , 0 ) ,
List( 1 , 1 , 0 ) ,
List( 88 ) ,
List( "hello" )
)) {
val result = list match {
case List( 0 ) = > "0"
case List( x, y) = > "List(x, y): " + x + ", " + y
case List( 0 , _*) = > "List(0, ...)"
case List( a) = > "List(a): " + a
case _ = > "something else"
}
println( result)
}
// 方式二
val list1 = List( 1 , 2 , 5 , 7 , 24 )
val list = List( 24 )
list match {
case first :: second :: rest = > println( s"first: $first , second: $second , rest: $rest " )
case _ = > println( "something else" )
}
println( "===========================" )
// 5 . 匹配元组
for ( tuple < - List(
( 0 , 1 ) ,
( 0 , 0 ) ,
( 0 , 1 , 0 ) ,
( 0 , 1 , 1 ) ,
( 1 , 23 , 56 ) ,
( "hello" , true, 0.5 )
)) {
val result = tuple match {
case ( a, b) = > "" + a + ", " + b
case ( 0 , _) = > "(0, _)"
case ( a, 1 , _) = > "(a, 1, _) " + a
case ( x, y, z) = > "(x, y, z) " + x + " " + y + " " + z
case _ = > "something else"
}
println( result)
}
}
}
匹配元组拓展
package chapter08
object Test03_MatchTupleExtend {
def main( args: Array[ String ] ) : Unit = {
val ( x, y) = ( 10 , "hello" )
println( s "x: $ x , y: $ y " )
val List( first, second, _* ) = List( 23 , 15 , 9 , 78 )
println( s "first: $ first , second: $ second " )
val fir :: sec :: rest = List( 23 , 15 , 9 , 78 )
println( s "first: $ fir , second: $ sec , rest: $ rest " )
println( "=====================" )
val list: List[ ( String , Int ) ] = List( ( "a" , 12 ) , ( "b" , 35 ) , ( "c" , 27 ) , ( "a" , 13 ) )
for ( elem <- list) {
println( elem. _1 + " " + elem. _2)
}
for ( ( word, count) <- list ) {
println( word + ": " + count)
}
println( "-----------------------" )
for ( ( word, _) <- list)
println( word)
println( "-----------------------" )
for ( ( "a" , count) <- list) {
println( count)
}
}
}
1.3 匹配对象及样例类
Student("alice", 18)
该语句在执行时,实际调用的是Student伴生对象中的apply 方法,因此不用 new 关键字就能构造出相应的对象。当将 Student(“alice”, 18)写在 case 后时 case Student("alice", 18) => "Alice
, 18",会默认调用 unapply 方法(对象提取器),user 作为 unapply 方法的参数,unapply 方法将 user 对象的 name 和 age 属性提取出来,与 Student(“alice”, 18)中的属性值进行匹配 case 中对象的 unapply 方法(提取器)返回 Some,且所有属性均一致,才算匹配成功,属性不一致,或返回 None,则匹配失败。 若只提取对象的一个属性,则提取器为 unapply(obj:Obj):Option[T]
若提取对象的多个属性,则提取器为 unapply(obj:Obj):Option[(T1,T2,T3…)]
若提取对象的可变个属性,则提取器为 unapplySeq(obj:Obj):Option[Seq[T]]
package chapter08
object Test04_MatchObject {
def main( args: Array[ String ] ) : Unit = {
val student = new Student( "alice" , 19 )
val result = student match {
case Student( "alice" , 18 ) => "Alice, 18"
case _ => "Else"
}
println( result)
}
}
class Student( val name: String , val age: Int )
object Student {
def apply( name: String , age: Int ) : Student = new Student( name, age)
def unapply( student: Student) : Option[ ( String , Int ) ] = {
if ( student == null ) {
None
} else {
Some( ( student. name, student. age) )
}
}
}
样例类语法 , 默认就实现了伴生对象、apply和unapply方法。
样例类仍然是类,和普通类相比,只是其自动生成了伴生对象,并且伴生对象中自动提供了一些常用的方法,如 apply、unapply、toString、equals、hashCode 和 copy。 样例类是为模式匹配而优化的类,因为其默认提供了 unapply 方法,因此,样例类可以直接使用模式匹配,而无需自己实现 unapply 方法。 构造器中的每一个参数都成为 val,除非它被显式地声明为 var(不建议这样做)
case class Person ( name: String , age: Int )
样例类案例
package chapter08
object Test05_MatchCaseClass {
def main( args: Array[ String ] ) : Unit = {
val student = Student1( "alice" , 18 )
val result = student match {
case Student1( "alice" , 18 ) => "Alice, 18"
case _ => "Else"
}
println( result)
}
}
case class Student1( name: String , age: Int )
1.4 偏函数中的模式匹配
偏函数也是函数的一种,通过偏函数我们可以方便的对输入参数做更精确的检查 。例如该偏函数的输入类型为 List[Int],而我们需要的是第一个元素是 0 的集合,这就是通过模式匹配实现的 。 偏函数定义
val second: PartialFunction[ List[ Int ] , Option[ Int ] ] = {
case x :: y :: _ => Some( y)
}
偏函数原理。上述代码会被 scala 编译器翻译成以下代码,与普通函数相比,只是多了一个用于参数检查的函数——isDefinedAt,其返回值类型为 Boolean。
val second = newPartialFunction[ List[ Int ] , Option[ Int ] ] {
override def isDefinedAt( list: List[ Int ] ) : Boolean = list match
{
case x :: y :: _ => true
case _ => false
}
override def apply( list: List[ Int ] ) : Option[ Int ] = list match
{
case x :: y :: _=> Some( y)
}
}
偏函数使用。偏函数不能像 second(List(1,2,3))这样直接使用,因为这样会直接调用 apply 方法,而应该调用 applyOrElse 方法,如下
second. applyOrElse( List( 1 , 2 , 3 ) , ( _: List[ Int ] ) => None)
applyOrElse 方法的逻辑为 if (ifDefinedAt(list)) apply(list) else default。如果输入参数满足条件,即 isDefinedAt 返回 true,则执行 apply 方法,否则执行 defalut 方法,default 方法为参数不满足要求的处理逻辑。 案例实操
package chapter08
object Test06_PartialFunction {
def main( args: Array[ String ] ) : Unit = {
val list: List[ ( String , Int ) ] = List( ( "a" , 12 ) , ( "b" , 35 ) , ( "c" , 27 ) , ( "a" , 13 ) )
val newList = list. map( tuple => ( tuple. _1, tuple. _2 * 2 ) )
val newList2 = list. map(
tuple => {
tuple match {
case ( word, count) => ( word, count * 2 )
}
}
)
val newList3 = list. map {
case ( word, count) => ( word, count * 2 )
}
println( newList)
println( newList2)
println( newList3)
val positiveAbs: PartialFunction[ Int , Int ] = {
case x if x > 0 => x
}
val negativeAbs: PartialFunction[ Int , Int ] = {
case x if x < 0 => - x
}
val zeroAbs: PartialFunction[ Int , Int ] = {
case 0 => 0
}
def abs( x: Int ) : Int = ( positiveAbs orElse negativeAbs orElse zeroAbs) ( x)
println( abs( - 67 ) )
println( abs( 35 ) )
println( abs( 0 ) )
}
}
第二节 异常处理
2.1 Java 异常处理
Java 语言按照 try—catch—finally 的方式来处理异常 不管有没有异常捕获,都会执行 finally,因此通常可以在 finally 代码块中释放资源。 可以有多个 catch,分别捕获对应的异常,这时需要把范围小的异常类写在前面,把范围大的异常类写在后面,否则编译错误。
public static class ExceptionDemo {
public static void main ( String [ ] args) {
try {
int a = 10 ;
int b = 0 ;
int c = a / b;
} catch ( ArithmeticException e) {
e. printStackTrace ( ) ;
} catch ( Exception e) {
e. printStackTrace ( ) ;
} finally {
System . out. println ( "finally" ) ;
}
}
}
2.2 Scala 异常处理
我们将可疑代码封装在 try 块中。在 try 块之后使用了一个 catch 处理程序来捕获异常。如果发生任何异常,catch 处理程序将处理它,程序将不会异常终止。 Scala 的异常的工作机制和 Java 一样,但是 Scala 没有“checked(编译期)”异常,即 Scala 没有编译异常这个概念,异常都是在运行的时候捕获处理 。 异常捕捉的机制与其他语言中一样,如果有异常发生,catch 子句是按次序捕捉的。因此,在 catch 子句中,越具体的异常越要靠前,越普遍的异常越靠后,如果把越普遍的异常写在前,把具体的异常写在后,在 Scala 中也不会报错,但这样是非常不好的编程风格。 finally 子句用于执行不管是正常处理还是有异常发生时都需要执行的步骤,一般用于对象的清理工作,这点和 Java 一样。 用 throw 关键字,抛出一个异常对象。所有异常都是 Throwable 的子类型。throw 表达式是有类型的,就是 Nothing ,因为 Nothing 是所有类型的子类型,所以 throw 表达式可以用在需要类型的地方. java 提供了 throws 关键字来声明异常。可以使用方法定义声明异常。它向调用者函数提供了此方法可能引发此异常的信息。它有助于调用函数处理并将该代码包含在 try-catch块中,以避免程序异常终止。在 Scala 中,可以使用 throws 注解来声明异常
package chapter09plus
object Test01_Exception {
def main( args: Array[ String ] ) : Unit = {
try {
val n = 10 / 0
} catch {
case e: ArithmeticException => {
println( "发生算术异常" )
}
case e: Exception => {
println( "发生一般异常" )
}
} finally {
println( "处理结束" )
}
}
}
第三节 隐式转换
3.1 常用隐式转换
隐式转换:当编译器第一次编译失败的时候,会在当前的环境中查找能让代码编译通过的方法,用于将类型进行转换,实现二次编译 隐式函数 :隐式转换可以在不需改任何代码的情况下,扩展某个类的功能。隐式参数 : 普通方法或者函数中的参数可以通过 implicit
关键字声明为隐式参数,调用该方法时,就可以传入该参数,编译器会在相应的作用域寻找符合条件的隐式值。
同一个作用域中,相同类型的隐式值只能有一个 编译器按照隐式参数的类型去寻找对应类型的隐式值,与隐式值的名称无关。 隐式参数优先于默认参数 隐式类 : 在 Scala2.10 后提供了隐式类,可以使用implicit
声明类,隐式类的非常强大,同样可以扩展类的功能,在集合中隐式类会发挥重要的作用。
其所带的构造参数有且只能有一个 隐式类必须被定义在“类”或“伴生对象”或“包对象”里,即隐式类不能是顶级的 。 案例实操
package chapter09plus
object Test02_Implicit {
def main( args: Array[ String ] ) : Unit = {
val new12 = new MyRichInt( 12 )
println( new12. myMax( 15 ) )
implicit def convert( num: Int ) : MyRichInt = new MyRichInt( num)
println( 12. myMax( 15 ) )
println( "============================" )
implicit class MyRichInt2( val self : Int ) {
def myMax2( n: Int ) : Int = if ( n < self ) self else n
def myMin2( n: Int ) : Int = if ( n < self ) n else self
}
println( 12. myMin2( 15 ) )
println( "============================" )
implicit val str: String = "alice"
implicit val num: Int = 18
def sayHello( ) ( implicit name: String ) : Unit = {
println( "hello, " + name)
}
def sayHi( implicit name: String = "atguigu" ) : Unit = {
println( "hi, " + name)
}
sayHello
sayHi
def hiAge( ) : Unit = {
println( "hi, " + implicitly[ Int ] )
}
hiAge( )
}
}
class MyRichInt( val self : Int ) {
def myMax( n: Int ) : Int = if ( n < self ) self else n
def myMin( n: Int ) : Int = if ( n < self ) n else self
}
3.2 隐式解析机制
首先会在当前代码作用域下查找隐式实体(隐式方法、隐式类、隐式对象)。(一般是这种情况) 如果第一条规则查找隐式实体失败,会继续在隐式参数的类型的作用域里查找。类型的作用域是指与该类型相关联的全部伴生对象 以及该类型所在包的包对象 。
第四节 泛型和IDEA 快捷键
4.1 泛型介绍
协变和逆变语法
协变:Son 是 Father 的子类,则 MyList[Son] 也作为 MyList[Father]的“子类”。 逆变:Son 是 Father 的子类,则 MyList[Son]作为 MyList[Father]的“父类”。 不变:Son 是 Father 的子类,则 MyList[Father]与 MyList[Son]“无父子关系”。
class MyList[ + T] {
}
class MyList[ - T] {
}
class MyList[ T]
泛型上下限 : 泛型的上下限的作用是对传入的泛型进行限定。
Class PersonList[ T < : Person] {
}
Class PersonList[ T > : Person] {
}
上下文限定。上下文限定是将泛型和隐式转换的结合产物,以下两者功能相同,使用上下文限定[A : Ordering]
之后,方法内无法使用隐式参数名调用隐式参数,需要通过implicitly[Ordering[A]]
获取隐式变量,如果此时无法查找到对应类型的隐式变量,会发生出错误。
def f[ A : B] ( a: A) = println( a)
案例实操
package chapter09plus
object Test03_Generics {
def main( args: Array[ String ] ) : Unit = {
val child: Parent = new Child
val childList: MyCollection[ SubChild] = new MyCollection[ Child]
def test[ A < : Child] ( a: A) : Unit = {
println( a. getClass. getName)
}
test[ SubChild] ( new SubChild)
}
}
class Parent { }
class Child extends Parent { }
class SubChild extends Child { }
class MyCollection[ - E] { }
4.2 IDEA 快捷键
快速生成程序入口:main 输入 main->回车 自动补全变量:.var 输入 1.var->回车 快速打印:.sout 输入 1.sout->回车 快速生成 for 循环:遍历对象.for 输入 1 to 3.for 查看当前文件的结构:Ctrl + F12 格式化当前代码:Ctrl + Shift + L 自动为当前代码补全变量声明:Ctrl + Shift + V