3 Option类型
scala中,Option类型来表示可选值。这种类型的数据有两种形式:
- Some(x):表示实际的值
- None:表示没有值
使用Option类型,可以用来有效避免空引用(null)异常。也就是说,将来我们返回某些数据时,可以返回一个Option类型来替代。
示例:
/** * 定义除法操作 * @param a 参数1 * @param b 参数2 * @return Option包装Double类型 */ def dvi(a:Double, b:Double):Option[Double] = { if(b != 0) { Some(a / b) } else { None } } def main(args: Array[String]): Unit = { val result1 = dvi(1.0, 5) result1 match { case Some(x) => println(x) case None => println("除零异常") } }
getOrElse方法
使用getOrElse方法,当Option对应的实例是None时,可以指定一个默认值,从而避免空指针异常
示例:
val result1 = dvi(1.0, 1) println(result1.getOrElse("除零错误"))
- scala鼓励使用Option类型来封装数据,可以有效减少,在代码中判断某个值是否为null
- 可以使用getOrElse方法来针对None返回一个默认值
4 偏函数
被包在花括号内没有match的一组case语句是一个偏函数,它是PartialFunction[A, B]的一个实例,A代表输入参数类型,B代表返回结果类型。可以理解为:偏函数是一个参数和一个返回值的函数。
示例:
// func1是一个输入参数为Int类型,返回值为String类型的偏函数 val func1: PartialFunction[Int, String] = { case 1 => "一" case 2 => "二" case 3 => "三" case _ => "其他" } println(func1(2))
示例:获取List中能够整除2的数字
val list = List(1,2,3,4,5,6,7) val list2 = list.filter{ case x if x % 2 == 0 => true case _ => false } println(list2)
5 正则表达式
在scala中,可以很方便地使用正则表达式来匹配数据。
scala中提供了Regex类来定义正则表达式,要构造一个RegEx对象,直接使用String类的r方法即可。
建议使用三个双引号来表示正则表达式,不然就得对正则中的反斜杠来进行转义。
val regEx = """正则表达式""".r
示例:检测是否匹配正则表达式
val emailRE = """.+@(.+)\..+""".r val emailList = List("38123845@qq.com", "a1da88123f@gmail.com", "zhansan@163.com", "123afadff.com") // 检查邮箱是否匹配正则 val size = emailRE.findAllMatchIn(emailList(0)).size // 如果匹配size为1,否则size为0 println(size)
示例:找出列表中的所有不合法的邮箱
// 找出列表中不合法的邮箱格式 println("不合法的邮箱为:") emailList.filter{ eml => emailRE.findAllIn(eml).size < 1 }.foreach { println(_) } println("------")
示例:使用正则表达式进行模式匹配,获取正则中匹配的分组
// 找到所有邮箱运营公司 println("邮箱的运营公司为") emailList.foreach { case email @ emailRE(company) => println(s"$email => ${company}") case _ => println("未知") }
6 异常处理
来看看下面一段代码。
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抛出了异常,而且没有打印出来"你好"。说明程序出现错误后就终止了。
那怎么解决该问题呢?
6.1 捕获异常
在scala中,可以使用异常处理来解决这个问题。以下为scala中try…catch异常处理的语法格式:
try { // 代码 } catch { case ex:异常类型1 => // 代码 case ex:异常类型2 => // 代码 } finally { // 代码 }
- try中的代码是我们编写的业务处理代码
- 在catch中表示当出现某个异常时,需要执行的代码
- 在finally中,是不管是否出现异常都会执行的代码
示例:
try { val i = 10 / 0 println("你好!") } catch { case ex: Exception => println(ex.getMessage) } finally { println("我始终都会执行!") }
6.2 抛出异常
我们也可以在一个方法中,抛出异常。语法格式和Java类似,使用throw new Exception…
示例:
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不需要再main方法上声明要抛出的异常,它已经解决了再Java中被认为是设计失败的检查型异常。下面是Java代码
public static void main(String[] args) throws Exception { throw new Exception("这是一个异常"); }
scala异常处理语法要比Java简洁、易用。
- scala中也是使用try…catch…finally处理异常
- 所有异常处理都是在catch语句中,每一个异常处理写成
case ex1:异常类型1 => 异常处理代码 case ex2:异常类型1 => 异常处理代码 case ex3:异常类型1 => 异常处理代码
- 抛出异常使用throw
- scala中方法抛出异常不需要像Java一样编写异常声明
7 提取器(Extractor)
我们之前已经使用过scala中非常强大的模式匹配功能了,通过模式匹配,我们可以快速匹配样例类中的成员变量。例如:
// 定义样例类 case class SubmitTask(id: String, name: String) case class HeartBeat(time: Long) case object CheckTimeOutTask val msg1 = SubmitTask("001", "task-001") val msg2 = HeartBeat(1000) val msg3 = CheckTimeOutTask val list = List(msg1, msg2, msg3) list(2) match { // 可以使用模式匹配快速匹配到到SubmitTask样例类中的id和name case SubmitTask(id, name) => println(s"id=$id, name=$name") case HeartBeat(time) => println(s"time=$time") case CheckTimeOutTask => println("检查超时") }
那是不是所有的类都可以进行这样的模式匹配呢?答案是不可以
的。要支持模式匹配,必须要实现一个提取器。
7.1 定义提取器
之前我们学习过了,实现一个类的伴生对象中的apply方法,可以用类名来快速构建一个对象。伴生对象中,还有一个unapply方法。与apply相反,unapply是将该类的对象,拆解为一个个的元素。
要实现一个类的提取器,只需要在该类的伴生对象中实现一个unapply方法即可。
示例:实现一个类的解构器,并使用match表达式进行模式匹配,提取类中的字段。
class Student { var name:String = _ // 姓名 var age:Int = _ // 年龄 // 实现一个辅助构造器 def this(name:String, age:Int) = { this() this.name = name this.age = age } } object Student { def apply(name:String, age:Int): Student = new Student(name, age) // 实现一个解构器 def unapply(arg: Student): Option[(String, Int)] = Some((arg.name, arg.age)) } object extractor_DEMO { def main(args: Array[String]): Unit = { val zhangsan = Student("张三", 20) zhangsan match { case Student(name, age) => println(s"姓名:$name 年龄:$age") case _ => println("未匹配") } } }
样例类自动实现了apply、unapply方法(可以使用scalap反编译一个样例类的字节码)
8 泛型
scala和Java一样,类和特质、方法都可以支持泛型。我们在学习集合的时候,一般都会涉及到泛型。
scala> val list1:List[String] = List("1", "2", "3") list1: List[String] = List(1, 2, 3) scala> val list1:List[String] = List("1", "2", "3") list1: List[String] = List(1, 2, 3)
在scala中,使用方括号来定义类型参数。
8.1 定义一个泛型方法
需求:用一个方法来获取任意类型数组的中间的元素
- 不考虑泛型直接实现(基于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 getMiddle[A](arr:Array[A]) = arr(arr.length / 2) def main(args: Array[String]): Unit = { val arr1 = Array(1,2,3,4,5) val arr2 = Array("a", "b", "c", "d", "f") println(getMiddle[Int](arr1)) println(getMiddle[String](arr2)) // 简写方式 println(getMiddle(arr1)) println(getMiddle(arr2)) }
8.2 定义一个泛型类
我们接下来要实现一个Pair类(一对数据)来讲解scala泛型相关的知识点。
Pair类包含两个值,而且两个值的类型不固定。
// 类名后面的方括号,就表示这个类可以使用两个类型、分别是T和S // 这个名字可以任意取 class Pair[T, S](val first: T, val second: S) case class Person(var name:String, val age:Int) object Pair { def main(args: Array[String]): Unit = { val p1 = new Pair[String, Int]("张三", 10) val p2 = new Pair[String, String]("张三", "1988-02-19") val p3 = new Pair[Person, Person](Person("张三", 20), Person("李四", 30)) } }
- 要定义一个泛型类,直接在类名后面加上方括号,指定要使用的类型参数。上述的T、S都是类型参数,就代表一个类型
- 指定了类对应的类型参数后,就可以使用这些类型参数来定义变量了
8.3 上下界
现在,有一个需求,在Pair类中,我们只想用来保存Person类型的对象,因为我们要添加一个方法,让好友之间能够聊天。例如:
def chat(msg:String) = println(s"${first.name}对${second.name}说: $msg")
但因为,Pair类中根本不知道first有name这个字段,上述代码会报编译错误。
而且,添加了这个方法,就表示Pair类,现在只能支持Person类或者Person的子类的泛型。所以,我们需要给Pair的泛型参数,添加一个上界。
使用<: 类型名
表示给类型添加一个上界,表示泛型参数必须要从上界继承。
// 类名后面的方括号,就表示这个类可以使用两个类型、分别是T和S // 这个名字可以任意取 class Pair[T <: Person, S <:Person](val first: T, val second: S) { def chat(msg:String) = println(s"${first.name}对${second.name}说: $msg") } class Person(var name:String, val age:Int) object Pair { def main(args: Array[String]): Unit = { val p3 = new Pair(new Person("张三", 20), new Person("李四", 30)) p3.chat("你好啊!") } }
接着再提一个需求,Person类有几个子类,分别是Policeman、Superman。
要控制Person只能和Person、Policeman聊天,但是不能和Superman聊天。此时,还需要给泛型添加一个下界。
// 类名后面的方括号,就表示这个类可以使用两个类型、分别是T和S // 这个名字可以任意取 class Pair[T <: Person, S >: Policeman <:Person](val first: T, val second: S) { def chat(msg:String) = println(s"${first.name}对${second.name}说: $msg") } class Person(var name:String, val age:Int) class Policeman(name:String, age:Int) extends Person(name, age) class Superman(name:String) extends Policeman(name, -1) object Pair { def main(args: Array[String]): Unit = { // 编译错误:第二个参数必须是Person的子类(包括本身)、Policeman的父类(包括本身) val p3 = new Pair(new Person("张三", 20), new Superman("李四")) p3.chat("你好啊!") } }
U >: T 表示U必须是类型T的父类或本身
S <: T 表示S必须是类型T的子类或本身
8.4 协变、逆变、非变
来一个类型转换的问题:
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]的子类型。这种情况,参数化类型的方向和类型的方向是相反的。
示例:
class Super class Sub extends Super //非变 class Temp1[A](title: String) //协变 class Temp2[+A](title: String) //逆变 class Temp3[-A](title: String) object Covariance_demo { def main(args: Array[String]): Unit = { val a = new Sub() // 没有问题,Sub是Super的子类 val b:Super = a // 非变 val t1:Temp1[Sub] = new Temp1[Sub]("测试") // 报错!默认不允许转换 // val t2:Temp1[Super] = t1 // 协变 val t3:Temp2[Sub] = new Temp2[Sub]("测试") val t4:Temp2[Super] = t3 // 非变 val t5:Temp3[Super] = new Temp3[Super]("测试") val t6:Temp3[Sub] = t5 } }