文章目录
异常处理
来看看下面一段代码。
def main(args: Array[String]): Unit = {
val i = 10 / 0
println("你好!")
}
Exception in thread "main" java.lang.ArithmeticException: / by zero
at ForDemo$.main(ForDemo.scala:3)
at ForDemo.main(ForDemo.scala)
执行程序,可以看到scala抛出了异常,而且没有打印出来"你好"。说明程序出现错误后就终止了。
那怎么解决该问题呢?
在scala中,可以使用异常处理来解决这个问题
捕获异常
语法格式
try {
// 代码
}
catch {
case ex:异常类型1 => // 代码
case ex:异常类型2 => // 代码
}
finally {
// 代码
}
- try中的代码是我们编写的业务处理代码
- 在catch中表示当出现某个异常时,需要执行的代码
- 在finally中,是不管是否出现异常都会执行的代码
示例
示例说明
- 使用try…catch来捕获除零异常
参考代码
try {
val i = 10 / 0
println("你好!")
} catch {
case ex: Exception => println(ex.getMessage)
}
抛出异常
我们也可以在一个方法中,抛出异常。语法格式和Java类似,使用throw new Exception...
示例 | 抛出异常
示例说明
- 在main方法中抛出一个异常
参考代码
def main(args: Array[String]): Unit = {
throw new Exception("这是一个异常")
}
Exception in thread "main" java.lang.Exception: 这是一个异常
at ForDemo$.main(ForDemo.scala:3)
at ForDemo.main(ForDemo.scala)
- scala不需要在方法上声明要抛出的异常,它已经解决了再Java中被认为是设计失败的检查型异常。
下面是Java代码
public static void main(String[] args) throws Exception {
throw new Exception("这是一个异常");
}
提取器(Extractor)
我们之前已经使用过scala中非常强大的模式匹配功能了,通过模式匹配,我们可以快速匹配样例类中的成员变量。例如:
// 1. 创建两个样例类
case class Person(name:String, age:Int)
case class Order(id:String)
def main(args: Array[String]): Unit = {
// 2. 创建样例类对象,并赋值为Any类型
val zhangsan:Any = Person("张三", 20)
val order1:Any = Order("001")
// 3. 使用match...case表达式来进行模式匹配
// 获取样例类中成员变量
order1 match {
case Person(name, age) => println(s"姓名:${name} 年龄:${age}")
case Order(id1) => println(s"ID为:${id1}")
case _ => println("未匹配")
}
}
那是不是所有的类都可以进行这样的模式匹配呢?答案是:
不可以
的。要支持模式匹配,必须要实现一个提取器。
[!NOTE]
样例类自动实现了apply、unapply方法
定义提取器
之前我们学习过了,实现一个类的伴生对象中的apply方法,可以用类名来快速构建一个对象。伴生对象中,还有一个unapply方法。与apply相反,unapply是将该类的对象,拆解为一个个的元素。
要实现一个类的提取器,只需要在该类的伴生对象中实现一个unapply方法即可。
语法格式
def unapply(stu:Student):Option[(类型1, 类型2, 类型3...)] = {
if(stu != null) {
Some((变量1, 变量2, 变量3...))
}
else {
None
}
}
示例
示例说明
- 创建一个Student类,包含姓名年龄两个字段
- 实现一个类的解构器,并使用match表达式进行模式匹配,提取类中的字段。
参考代码
class Student(var name:String, var age:Int)
object Student {
def apply(name:String, age:Int) = {
new Student(name, age)
}
def unapply(student:Student) = {
val tuple = (student.name, student.age)
Some(tuple)
}
}
def main(args: Array[String]): Unit = {
val zhangsan = Student("张三", 20)
zhangsan match {
case Student(name, age) => println(s"${name} => ${age}")
}
}
泛型
scala和Java一样,类和特质、方法都可以支持泛型。我们在学习集合的时候,一般都会涉及到泛型。
scala> val list1:List[String] = List("1", "2", "3")
list1: List[String] = List(1, 2, 3)
那如何自己定义泛型呢?
定义一个泛型方法
在scala中,使用方括号来定义类型参数。
语法格式
def 方法名[泛型名称](..) = {
//...
}
示例
示例说明
- 用一个方法来获取任意类型数组的中间的元素
- 不考虑泛型直接实现(基于Array[Int]实现)
- 加入泛型支持
参考代码
不考虑泛型的实现
def getMiddle(arr:Array[Int]) = arr(arr.length / 2)
def main(args: Array[String]): Unit = {
val arr1 = Array(1,2,3,4,5)
println(getMiddle(arr1))
}
加入泛型支持
def getMiddleElement[T](array:Array[T]) =
array(array.length / 2)
def main(args: Array[String]): Unit = {
println(getMiddleElement(Array(1, 2, 3, 4, 5)))
println(getMiddleElement(Array("a", "b", "c", "d", "e")))
}
泛型类
scala的类也可以定义泛型。接下来,我们来学习如何定义scala的泛型类
定义
语法格式
class 类[T](val 变量名: T)
- 定义一个泛型类,直接在类名后面加上方括号,指定要使用的泛型参数
- 指定类对应的泛型参数后,就使用这些类型参数来定义变量了
示例
示例说明
- 实现一个Pair泛型类
- Pair类包含两个字段,而且两个字段的类型不固定
- 创建不同类型泛型类对象,并打印
参考代码
case class Pair[T](var a:T, var b:T)
def main(args: Array[String]): Unit = {
val pairList = List(
Pair("Hadoop", "Storm"),
Pair("Hadoop", 2008),
Pair(1.0, 2.0),
Pair("Hadoop", Some(1.9))
)
println(pairList)
}
上下界
需求:
我们在定义方法/类的泛型时,限定必须从哪个类继承、或者必须是哪个类的父类。此时,就需要使用到上下界。
上界定义
使用<: 类型名
表示给类型添加一个上界,表示泛型参数必须要从该类(或本身)继承
语法格式
[T <: 类型]
示例
示例说明
- 定义一个Person类
- 定义一个Student类,继承Person类
- 定义一个demo泛型方法,该方法接收一个Array参数,
- 限定demo方法的Array元素类型只能是Person或者Person的子类
- 测试调用demo,传入不同元素类型的Array
参考代码
class Person
class Student extends Person
def demo[T <: Person](a:Array[T]) = println(a)
def main(args: Array[String]): Unit = {
demo(Array(new Person))
demo(Array(new Student))
// 编译出错,必须是Person的子类
// demo(Array("hadoop"))
}
下界
上界是要求必须是某个类的子类,或者必须从某个类继承,而下界是必须是某个类的父类(或本身)
语法格式
[T >: 类型]
[!NOTE]
如果类既有上界、又有下界。下界写在前面,上界写在后面
示例
示例说明
- 定义一个Person类
- 定义一个Policeman类,继承Person类
- 定义一个Superman类,继承Policeman类
- 定义一个demo泛型方法,该方法接收一个Array参数,
- 限定demo方法的Array元素类型只能是Person、Policeman
- 测试调用demo,传入不同元素类型的Array
参考代码
class Person
class Policeman extends Person
class Superman extends Policeman
def demo[T >: Policeman](array:Array[T]) = println(array)
def main(args: Array[String]): Unit = {
demo(Array(new Person))
demo(Array(new Policeman))
// 编译出错:Superman是Policeman的子类
// demo(Array(new Superman))
}
注意:在有多个泛型,比如A,B,…时,泛型的下界会失效
协变、逆变、非变
spark的源代码中大量使用到了协变、逆变、非变,学习该知识点对我们将来阅读spark源代码很有帮助。
来看一个类型转换的问题:
class Pair[T]
object Pair {
def main(args: Array[String]): Unit = {
val p1 = Pair("hello")
// 编译报错,无法将p1转换为p2
val p2:Pair[AnyRef] = p1
println(p2)
}
}
如何让带有泛型的类支持类型转换呢?
非变
语法格式
class Pair[T]{}
- 默认泛型类是非变的
- 类型B是A的子类型,Pair[A]和Pair[B]没有任何从属关系
- Java是一样的
协变
语法格式
class Pair[+T]
- 类型B是A的子类型,Pair[B]可以认为是Pair[A]的子类型
- 参数化类型的方向和类型的方向是一致的。
逆变
语法格式
class Pair[-T]
- 类型B是A的子类型,Pair[A]反过来可以认为是Pair[B]的子类型
- 参数化类型的方向和类型的方向是相反的
示例
示例说明
- 定义一个Super类、以及一个Sub类继承自Super类
- 使用协变、逆变、非变分别定义三个泛型类
- 分别创建泛型类来演示协变、逆变、非变
参考代码
class Super
class Sub extends Super
class Temp1[T]
class Temp2[+T]
class Temp3[-T]
def main(args: Array[String]): Unit = {
val a:Temp1[Sub] = new Temp1[Sub]
// 编译报错
// 非变
//val b:Temp1[Super] = a
// 协变
val c: Temp2[Sub] = new Temp2[Sub]
val d: Temp2[Super] = c
// 逆变
val e: Temp3[Super] = new Temp3[Super]
val f: Temp3[Sub] = e
}