Scala高级用法 2

简介: Scala高级用法

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("除零错误"))
  1. scala鼓励使用Option类型来封装数据,可以有效减少,在代码中判断某个值是否为null
  2. 可以使用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简洁、易用。

  1. scala中也是使用try…catch…finally处理异常
  2. 所有异常处理都是在catch语句中,每一个异常处理写成
case ex1:异常类型1 => 异常处理代码
case ex2:异常类型1 => 异常处理代码
case ex3:异常类型1 => 异常处理代码
  1. 抛出异常使用throw
  2. 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 定义一个泛型方法


需求:用一个方法来获取任意类型数组的中间的元素

  1. 不考虑泛型直接实现(基于Array[Int]实现)
  2. 加入泛型支持

不考虑泛型的实现

  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))
  }
}
  1. 要定义一个泛型类,直接在类名后面加上方括号,指定要使用的类型参数。上述的T、S都是类型参数,就代表一个类型
  2. 指定了类对应的类型参数后,就可以使用这些类型参数来定义变量了

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
  }
}


目录
相关文章
|
7月前
|
消息中间件 分布式计算 Java
Scala函数式编程【从基础到高级】
Scala函数式编程【从基础到高级】
|
7月前
|
存储 Scala 索引
scala中常见数据结构的用法
scala中常见数据结构的用法
48 1
|
7月前
|
SQL 分布式计算 Serverless
scala-spark中的groupby、like等的用法
scala-spark中的groupby、like等的用法
243 0
|
前端开发 Java 程序员
Scala高级用法 3
Scala高级用法
48 0
|
分布式计算 Java Scala
Scala高级用法 1
Scala高级用法
59 0
|
Java Scala
Scala的高级用法
Scala的高级用法
|
Java Scala
scala 匿名函数的用法实操
1. => 什么意思 => 匿名函数(Anonymous Functions),表示创建一个函数实例。 比如:(x: Int) => x + 1 和如下JAVA方法表示的含义一样:
126 0
scala之list用法史上最全
Scala 列表类似于数组,它们所有元素的类型都相同,但是它们也有所不同:列表是不可变的,值一旦被定义了就不能改变,其次列表 具有递归的结构(也就是链接表结构)而数组不是 下面是list的常用方法,当然了这不是所有的.但都是最常用的.具体看下面的demo.具体可以看代码里面的注释
|
2月前
|
分布式计算 大数据 Java
大数据-87 Spark 集群 案例学习 Spark Scala 案例 手写计算圆周率、计算共同好友
大数据-87 Spark 集群 案例学习 Spark Scala 案例 手写计算圆周率、计算共同好友
58 5
|
2月前
|
分布式计算 关系型数据库 MySQL
大数据-88 Spark 集群 案例学习 Spark Scala 案例 SuperWordCount 计算结果数据写入MySQL
大数据-88 Spark 集群 案例学习 Spark Scala 案例 SuperWordCount 计算结果数据写入MySQL
51 3