异常处理
- 类似Java,使用
try..catch
捕获处理异常 - 不同的是catch中使用模式匹配的思想
try {
// 代码
}
catch {
case ex:异常类型1 => // 代码
case ex:异常类型2 => // 代码
}
finally { // 不管是否出现异常都会执行
// 代码
}
- 同样,可以使用
throw
抛出异常
提取器
- 是一个带有
unapply
方法的对象 - 从传递给它的对象中提取出构造该对象的参数
- 类似于apply()方法,需要在伴生对象中使用
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) // 使用伴生对象的apply方法直接实例化
zhangsan match { // 自动实现实例类的解构方法
case Student(name, age) => println(s"姓名:$name 年龄:$age")
case _ => println("未匹配")
}
}
}
泛型
- 泛型用于指定方法或类可以接受任意类型参数,参数在实际使用时才被确定
- 使用泛型可以使得类或方法具有更强的通用性,泛型无处不在
- 使用方括号
[ ]
来定义类型参数(注意区分类型与类型参数)
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))
}
- 泛型类
// 类名后面的方括号,就表示这个类可以使用两个类型、分别是T和S
// 这个名字可以任意取,可以叫W或者C
class Pair[T, S](val first: T, val second: S)
case class People(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[People, People](People("张三", 20), People("李四", 30))
}
}
- 随着泛型还有一些其他概念:
上下界
- 在指定泛型类型时,有时需要界定泛型类型的范围,而不是接收任意类型
- 上下边界特性允许泛型类型是某个类的子类,或者是某个类的父类
//要控制Person只能和Person、Policeman聊天,但是不能和Superman聊天。此时,还需要给泛型添加一个下界。
//上下界
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[Person,Superman](new Person("张三", 20), new Superman("李四"))
p3.chat("你好!")
}
}
协变
class Pair[+T]
,这种情况是协变。类型B是A的子类型,那么Pair[B]可以认为是Pair[A]的子类型- 这种情况,类型参数的方向和类型的方向是一致的
class Pair[+T](a:T)
object Pair {
def main(args: Array[String]): Unit = {
val p1 = new Pair("hello")
// String是AnyRef的子类
val p2:Pair[AnyRef] = p1
println(p2)
}
}
逆变
class Pair[-T]
,这种情况是逆变。类型B是A的子类型,Pair[A]反过来可以认为是Pair[B]的子类型- 这种情况,类型参数的方向和类型的方向是相反的
非变
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()
// 与Java同样,向上转型,不属于泛型
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 // 类似Java普通类向上转型
// 逆变
val t5:Temp3[Super] = new Temp3[Super]("测试")
val t6:Temp3[Sub] = t5 // 类似Java普通类向下转型
}
}
隐式转换和隐式参数
- 可以允许你手动指定,将某种类型的对象转换成其他类型的对象或者是给一个类增加方法
- 隐式转换:使用
implicit
修饰的方法实现把一个原始类转换成目标类,进而可以调用目标类中的方法 - 所有的隐式转换和隐式参数必须定义在一个object中
//todo:一个类隐式转换成具有相同方法的多个类
class C
class A(c:C) {
def readBook(): Unit ={
println("A说:好书好书...")
}
}
class B(c:C){
def readBook(): Unit ={
println("B说:看不懂...")
}
def writeBook(): Unit ={
println("B说:不会写...")
}
}
object AB{
// 将原始类C转换为目标类A B
// 此时的C就有了A/B的能力
implicit def C2A(c:C) = new A(c)
implicit def C2B(c:C) = new B(c)
}
object B{
def main(args: Array[String]) {
//导包
//1. import AB._ 会将AB类下的所有隐式转换导进来
//2. import AB.C2A 只导入C类到A类的的隐式转换方法
//3. import AB.C2B 只导入C类到B类的的隐式转换方法
import AB._
val c=new C
//由于A类与B类中都有readBook(),只能导入其中一个,否则会冲突
//c.readBook()
//C类可以执行B类中的writeBook() 即C2B
c.writeBook()
}
}
- 隐式参数:在函数或者方法中,定义一个用implicit修饰的参数,Scala会尝试找到一个指定类型的用implicit修饰的参数,即隐式值,传入方法
- Scala会在两个范围内查找:一种是当前作用域内可见的val或var定义的隐式变量(导包);一种是隐式参数类型的伴生对象内的隐式值
//todo:隐式参数案例:员工领取薪水
object Company{
// 在object中定义用implicit修饰的参数(隐式参数)
// 注意:同一类型的隐式值只允许出现一次,否则会报错
implicit val xxx="zhangsan"
implicit val yyy=10000.00
//implicit val zzz="lisi"
}
class Boss {
// 定义一个用implicit修饰的参数,类型为String
// 注意参数匹配的类型 它需要的是String类型的隐式值
def callName(implicit name:String):String={
name+" is coming !"
}
// 定义一个用implicit修饰的参数,类型为Double
// 注意参数匹配的类型 它需要的是Double类型的隐式值
def getMoney(implicit money:Double):String={
" 当月薪水:"+money
}
}
object Boss extends App{
//使用import导入定义好的隐式值,注意:必须先加载否则会报错
import Company.xxx // String类型的隐式值
import Company.yyy // Int类型的隐式值
val boss = new Boss
// Scala会尝试找到一个指定类型的用implicit修饰的参数,即隐式值,传入方法
println(boss.callName+boss.getMoney)
}
结语
以上四篇基本介绍了在大数据开发过程中经常使用到的基本Scala语法,这并不是全面的语法总结。Scala是一门多范式编程语言,有很强的灵活性,掌握思想比较关键,后面会通过案例和源码的方式呈现基本原理。