Scala高级语法入门 (六)Scala中的异常&隐式转换&泛型
🙆♂️🙆♂️ 写在前面
🏠 个人主页:csdn春和
📚 推荐专栏:更多专栏尽在主页!
JavaWeb专栏(从入门到实战超详细!!!)
SSM专栏 (更新中…)
📖 本期文章:Scala高级语法入门 (六)Scala中的异常&隐式转换&泛型
本篇文章作为Scala系列的完结篇,感谢各位码友一直以来的支持与厚爱💜
📌本文目录
- 一、Scala异常
-
- 1.1、Java中的异常
- 1.2、Scala中的
- 二、隐式转换
-
- 2.1、什么是隐式转换
- 2.2、隐式函数
- 2.2、隐式参数
- 2.3、隐式类
- 2.4、隐式机制
- 三、泛型
-
- 3.1、简介
- 3.2、泛型转换
-
- 3.2.1、泛型不可变
- 3.2.2、泛型协变
- 3.2.3、泛型逆变
- 3.2.4、泛型的上限
- 3.2.5、泛型的下限
- 3.2.6、上下文限定
一、Scala异常
Scala异常语法处理上和Java类似,但是又不尽相同。
1.1、Java中的异常
try { int a = 10; int b = 0; int c = a / b;} catch (ArithmeticException e){ // catch时,需要将范围小的写到前面 e.printStackTrace();} catch (Exception e){ e.printStackTrace();} finally { System.out.println("finally");}
java中异常的捕获是从小大大
(1)Java 语言按照 try—catch—finally 的方式来处理异常
(2)不管有没有异常捕获,都会执行 finally,因此通常可以在 finally 代码块中释放资源
(3)可以有多个 catch,分别捕获对应的异常,这时需要把范围小的异常类写在前面, 把范围大的异常类写在后面,否则编译错误。
但是异常并不是我们看上去那么简单
我们来看看下面的代码执行情况是什么?你知道吗?
public class JavaTestException { public static void main(String[] args) { int j = test(); System.out.println(j); } public static int test(){ int i = 0; try { return i++; }finally { return ++i; } }}
❓ 执行结果为2 这是为什么?
一旦源码看不懂 我们就去看字节码
javap -v JavaTestException
查看字节码文件
1、return关键字不会马上返回结果
2、所有的return返回的都是同一个值:临时变量
所以上述代码中return不会马上返回 它要等finally执行完毕后返回,但是finally里面也有return 就相当于对临时变量进行了操作 最终return返回的都是同一个值。
如果finally里面没有return 那么 临时变量不会被修改 返回 0
1.2、Scala中的
Scala中的异常不区分所谓的编译时异常和运行时异常,也无需显示抛出方法异常,所以Scala中没有throws关键字。
object Scala_exception1 { def main(args: Array[String]): Unit = { try { var n= 10 / 0 } catch { case ex: ArithmeticException=>{ // 发生算术异常 println("发生算术异常") } case ex: Exception=>{ // 对异常处理 println("发生了异常1") } } finally { println("finally") } }}
Scala中也要求异常范围大的写在后面
1、我们将可疑代码封装在 try 块中。在 try 块之后使用了一个 catch 处理程序来捕获异常。
如果发生任何异常,catch 处理程序将处理它,程序将不会异常终止。
2、Scala 的异常的工作机制和 Java 一样,但是 Scala 没有“checked(编译期)”异常,
即 Scala 没有编译异常这个概念,异常都是在运行的时候捕获处理。
3、异常捕捉的机制与其他语言中一样,如果有异常发生,catch 子句是按次序捕捉的。
因此,在 catch 子句中,越具体的异常越要靠前,越普遍的异常越靠后,如果把越普遍的异常写在前,
把具体的异常写在后,在 Scala 中也不会报错,但这样是非常不好的编程风格。
4、finally 子句用于执行不管是正常处理还是有异常发生时都需要执行的步骤,一般用于对象的清理工作,这点和 Java 一样。
java 提供了 throws 关键字来声明异常。可以使用方法定义声明异常。它向调用者函数提供了此方法可能引发此异常的信息。它有助于调用函数处理并将该代码包含在 try-catch 块中,以避免程序异常终止。
在 Scala 中,可以使用 throws 注解
来声明异常
def main(args: Array[String]): Unit = { f11()}@throws(classOf[NumberFormatException])def f11()={ "abc".toInt}
二、隐式转换
2.1、什么是隐式转换
在之前的类型学习中,我们已经学习了自动类型转换,精度小的类型可以自动转换为精度大的类型,这个转换过程无需开发人员参与,由编译器自动完成,这个转换操作我们称之为隐式转换。
在其他的场合,隐式转换也起到了非常重要的作用。如Scala在程序编译错误时,可以通过隐式转换中类型转换机制尝试进行二次编译,将本身错误无法编译通过的代码通过类型转换后编译通过
。慢慢地,这也形成了一种扩展功能的转换机制。
所谓的隐式转换其实就是类型的转换
2.2、隐式函数
在函数前面加上implicit关键字就是隐式函数了
看看下面的代码:
object Scala_Transform1 { def main(args: Array[String]): Unit = { // 获取第三方的提供的数据 val age:Int = thirdPart() println(age) } // 第三方 提供的数据 年龄 def thirdPart(): Int = { 20 }}
打印输出20没问题,那么问题来了。第三方发现年龄有半岁,使用int表示不再合适,所以改为了Double类型
// 第三方 提供的数据 年龄def thirdPart(): Double = { 20.30}
之前我们提到过OCP开发原则,在不改变源码的情况下如何使得编译器不报错
这里我们就提到了Scala中的隐式转换机制,他会在全局寻找可以使得编译通过的方法,使得二次编译通过
所以这里我们编写一个方法 使得double类型数据转换为int类型数据
// 增加一个将double类型转为int类型的方法 def transform(num:Double): Int ={ num.toInt}
但是这个方法和我们的隐式转换机制怎么联系起来呢?加上
implicit
关键字
// 增加一个将double类型转为int类型的方法implicit def transform(num:Double): Int ={ num.toInt }
隐式转换可以在不需改任何代码的情况下,扩展某个类的功能。
2.2、隐式参数
普通方法或者函数中的参数可以通过 implicit 关键字声明为隐式参数,调用该方法时, 就可以传入该参数,编译器会在相应的作用域寻找符合条件的隐式值。
(1)同一个作用域中,相同类型的隐式值只能有一个
(2)编译器按照隐式参数的类型去寻找对应类型的隐式值,与隐式值的名称无关。
(3)隐式参数优先于默认参数
object Scala_Transform2 { def main(args: Array[String]): Unit = { // TODO 隐式转换 隐式参数 // 隐式参数 def reg(implicit passwd:String = "000000"): Unit ={ println(s"密码为:${passwd}") } reg() reg("123123") // 隐式变量 implicit val passwd = "123456" // 通过隐式参数可以修改默认值 // 隐式参数是不用传递的,这个过程是由编译器完成 reg // 输出的默认密码就是123456 // 如果传了参数 那么就不去查找隐式变量了 reg() // 输出 000000 }}
在同一个作用域中相同的转换规则会报错
2.3、隐式类
在 Scala2.10 后提供了隐式类,可以使用 implicit 声明类,隐式类的非常强大,同样可以扩展类的功能
,在集合中隐式类会发挥重要的作用
object Scala_Transform3 { def main(args: Array[String]): Unit = { // TODO 隐式转换 隐式参数 val user = new User() user.insertUser() } class User{ def insertUser(): Unit ={ println("添加用户!") } } }
加入我们想要扩展一个修改用户的功能该怎么做?
我们可以再写一个类扩展功能
class UserExtend(){ def updateUser(): Unit = { println("修改用于!") }}
然后在再写一个隐式函数做转换
implicit def transforUser(user: User): UserExtend = { new UserExtend}
但是这样做显得十分的麻烦
我们可以使用隐式类
implicit class UserExtend(user: User) { def updateUser(): Unit = { println("修改用于!") }}
这样更容易扩展类的功能
注意事项
1、其所带的构造参数有且只能有一个
2、隐式类必须被定义在“类”或“伴生对象”或“包对象”里,即隐式类不能是顶级的。
2.4、隐式机制
所谓的隐式机制,就是一旦出现编译错误时,编译器会从哪些地方查找对应的隐式转换规则
1、当前代码作用域
2、当前代码上级作用域
3、当前类所在的包对象
4、当前类(对象)的父类(父类)或特质(父特质)
其实最直接的方式就是直接导入。
三、泛型
3.1、简介
Scala的泛型和Java中的泛型表达的含义都是一样的,对处理的数据类型进行约束,但是Scala提供了更加强大的功能
1、泛型和类型的区别?
类型是约束外部的数据
泛型是约束内部的数据
2、泛型在某些场合中其实就是类型参数,用于向类中传递采纳数
3、泛型只在编译时有效,将这个操作称之为
“泛型擦除”
4、泛型的主要目的是为了约束内部数据
scala中的泛型用[] 表示
3.2、泛型转换
3.2.1、泛型不可变
scala中的泛型也是不可变得
new 的是类型 而不是泛型 泛型只是约束力内部的数据
def main(args: Array[String]): Unit = { val test1 : Test[User] = new Test[User] // OK val test2 : Test[User] = new Test[Parent] // Error val test3 : Test[User] = new Test[SubUser] // Error } class Test[T] { } class Parent { } class User extends Parent{ } class SubUser extends User { }
3.2.2、泛型协变
马丁想着泛型和类型不是同一个层面的东西,所以无法联合使用,不方便,如果能将类型和泛型当成一个整体来使用的话不就方便了?
如果将类型和泛型联合使用,那么类型相同,泛型存在父子关系,那么整体就存在父子关系,这种操作其实就是一种变化,称之为
协变 +T
def main(args: Array[String]): Unit = { val test1 : Test[User] = new Test[User] // OK val test2 : Test[User] = new Test[Parent] // Error val test3 : Test[User] = new Test[SubUser] // OK } class Test[+T] { } class Parent { } class User extends Parent{ } class SubUser extends User { }
3.2.3、泛型逆变
如果将类型和泛型联合使用,那么类型相同,泛型存在父子关系,那么整体就存在子父关系,这种操作其实就是一种变化,称之为
逆 变 -T
def main(args: Array[String]): Unit = { val test1 : Test[User] = new Test[User] // OK val test2 : Test[User] = new Test[Parent] // OK val test3 : Test[User] = new Test[SubUser] // Error } class Test[-T] { } class Parent { } class User extends Parent{ } class SubUser extends User { }
3.2.4、泛型的上限
object Scala_geniric4 { // 泛型的上限 def main(args: Array[String]): Unit = { val parent: Parent = new Parent() val user: User = new User() val subuser: SubUser = new SubUser()// test[Parent](parent) // Error test[User](user) // OK test[SubUser](subuser) // OK } // 上限采用颜文字 def test[A <: User](a: A): Unit = { println(a) } class Parent { } class User extends Parent { } class SubUser extends User { }}
3.2.5、泛型的下限
def main(args: Array[String]): Unit = { val parent : Parent = new Parent() val user : User = new User() val subuser : SubUser = new SubUser() test[Parent](parent) // OK test[User](user) // OK test[SubUser](subuser) // Error } def test[A>:User]( a : A ): Unit = { println(a) } class Parent { } class User extends Parent{ } class SubUser extends User { }}
3.2.6、上下文限定
1)语法
def f[A : B](a: A) = println(a)
//等同于def f[A](a:A)(implicit arg:B[A])=println(a)
2)说明
上下文限定是将泛型和隐式转换的结合产物,以下两者功能相同,使用上下文限定[A : Ordering]之后,方法内无法使用隐式参数名调用隐式参数,需要通过 implicitly[Ordering[A]] 获取隐式变量,如果此时无法查找到对应类型的隐式变量,会发生出错误。
object ScalaGeneric { def main(args: Array[String]): Unit = { def f[A : Test](a: A) = println(a) implicit val test : Test[User] = new Test[User] f( new User() ) } class Test[T] { } class Parent { } class User extends Parent{ } class SubUser extends User { }}