Scala入门(下)----【类、对象、继承、特质(trait)、模式匹配、样式类、Option类型、偏函数、协变、逆变、非变、上下界】

1. 类、对象、继承、特质

1.1 类

  • 类的定义
/**
* 在Scala中,类并不用声明为public类型的。
* Scala源文件中可以包含多个类,所有这些类都具有共有可见性。
*/
class Person {
  //用val修饰的变量是可读属性,有getter但没有setter(相当与Java中用final修饰的变量)
  val id="9527"

  //用var修饰的变量都既有getter,又有setter
  var age:Int=18

  //类私有字段,只能在类的内部使用或者伴生对象中访问
  private var name : String = "唐伯虎"

  //类私有字段,访问权限更加严格的,该字段在当前类中被访问
  //在伴生对象里面也不可以访问
  private[this] var pet = "小强"
  val abc = animal + "itcast"//在本类中是可以调用
}

//伴生对象(这个名字和类名相同,叫伴生对象)
object Person{
  def main(args: Array[String]): Unit = {
    val p=new Person

    //如果是下面的修改,发现下面有红线,说明val类型的不支持重新赋值,但是可以获取到值
    //p.id = "123"
    println(p.id)
    //打印age
    println(p.age)
    //打印name,伴生对象中可以在访问private变量
    println(p.name)
    //由于pet字段用private[this]修饰,伴生对象中访问不到pet变量
    //p.pet(访问不到)

  }
}

伴生对象 : 类名和object名一致

  • 构造器
    • Scala 中的每个类都有主构造器,主构造器的参数直接放置类名后面,与类交织在一起。
    • 注意:主构造器会执行类定义中的所有语句 , 通过主构造器构建的实例不会执行辅助构造器中的语句
    • tips : 每个辅助构造器都必须以一个对先前已定义的其他辅助构造器或主构造器的调用开始
class Student(val name: String, var age: Int) {
  println("执行主构造器")
  private var gender: String = "male"

  def this(name: String, age: Int, gender: String) {
    this(name, age)
    println("执行辅助构造器")
    this.gender = gender
  }

  def this(name: String) {
    this(name, 20, "female")//可以以其他辅助构造器的调用开始
    println("another")
  }

}

object Student {
  def main(args: Array[String]): Unit = {
    val s1 = new Student("zhangsan", 20) //执行主构造器
    //val s2 = new Student("zhangsan",20,"female")
    //执行主构造器
    //执行辅助构造器
  }
}

1.2 Scala面向对象编程之对象

  • Scala中的object

    object 相当于 class 的单个实例,通常在里面放一些静态的 field 或者 method;

    在Scala中没有静态方法和静态字段,但是可以使用object这个语法结构来达到同样的目的

    • object作用 :
      • 存放工具方法和常量
      • 高效共享单个不可变的实例
      • 单例模式 : 一个类只有一个对象实例 , 可以通过类名.方式调用
import scala.collection.mutable.ArrayBuffer


class Session {}

object SessionFactory {
  //该部分相当于java中的静态块
  val session = new Session

  //在object中的方法相当于java中的静态方法
  def getSession(): Session = {
    session
  }
}

object SingletonDemo {
  def main(args: Array[String]) {
    //单例对象,不需要new,用【单例对象名称.方法】调用对象中的方法
    val session1 = SessionFactory.getSession()
    println(session1)
    //单例对象,不需要new,用【单例对象名称.变量】调用对象中成员变量
    val session2 = SessionFactory.session
    println(session2)
  }
}
  • Scala中的伴生对象
    • 如果有一个class文件,还有一个与class同名的object文件,那么就称这个object是class的伴生对象class是object的伴生类
    • 伴生类和伴生对象必须存放在一个.scala文件
    • 伴生类和伴生对象的最大特点是,可以相互访问
//伴生类
class Dog {
  val id = 1
  private var name = "itcast"

  def printName(): Unit = {
    //在Dog类中可以访问伴生对象Dog的私有属性
    println(Dog.CONSTANT + name)
  }
}

//伴生对象
object Dog {
  //伴生对象中的私有属性
  private val CONSTANT = "汪汪汪 : "

  def main(args: Array[String]) {
    val p = new Dog
    //访问私有的字段name
    p.name = "123"
    p.printName()
  }
}
//执行结果 汪汪汪 : 123
  • Scala中的apply方法
    • object 中非常重要的一个特殊方法,就是apply方法
    • apply方法通常是在伴生对象中实现的,其目的是通过伴生类的构造函数功能来实现伴生对象的构造函数功能
    • 通常我们会在类的伴生对象中定义apply方法,当遇到类名(参数1,...参数n)时apply方法会被调用
    • 在创建伴生对象或伴生类的对象时,通常不会使用new class/class() 的方式,而是直接使用class(),隐式的调用伴生对象的apply 方法,这样会让对象创建的更加简洁
/**
  * Array 类的伴生对象中,就实现了可接收变长参数的 apply 方法,
  * 并通过创建一个 Array 类的实例化对象,实现了伴生对象的构造函数功能
  */
// 指定 T 泛型的数据类型,并使用变长参数 xs 接收传参,返回 Array[T] 数组
// 通过 new 关键字创建 xs.length 长的 Array 数组
// 其实就是调用Array伴生类的 constructor进行 Array对象的初始化
//  def apply[T: ClassTag](xs: T*): Array[T] = {
//    val array = new Array[T](xs.length)
//    var i = 0
//    for (x <- xs.iterator) { array(i) = x; i += 1 }
//    array
//  }

object ApplyDemo {
  def main(args: Array[String]) {
    //调用了Array伴生对象的apply方法
    //def apply(x: Int, xs: Int*): Array[Int]
    //arr1中只有一个元素5
    val arr1 = Array(5)

    //new了一个长度为5的array,数组里面包含5个null
    var arr2 = new Array(5)
    println(arr1.toBuffer)
  }
}
  • Scala中的main方法
    • 同Java一样,如果要运行一个程序,必须要编写一个包含main 方法的类
    • 在Scala 中,也必须要有一个main 方法,作为入口
    • Scala 中的 main 方法定义为 def main(args:Array[String]),而且必须定义在 object 中
    • 除了自己实现main 方法之外,还可以继承 App Trait,然后,将需要写在 main 方法中运行的代码,直接作为 object 的 constructor 代码即可,而且还可以使用 args 接收传入的参数
//1.在object中定义main方法
object Main_Demo1 {
  def main(args: Array[String]) {
    if (args.length > 0) {//初始为0
      println("Hello, " + args(0))
    } else {
      println("Hello World!")
    }
  }
}

//2.使用继承App Trait ,将需要写在 main 方法中运行的代码
// 直接作为 object 的 constructor 代码即可,
// 而且还可以使用 args 接收传入的参数。

object Main_Demo2 extends App {
  if (args.length > 0) {
    println("Hello, " + args(0))
  } else {
    println("Hello World!")
  }
}

1.3 Scala面向对象编程之继承

  • Scala中继承(extends)的概念

    • Scala中,让子类继承父类,与 Java 一样,也是使用 extends 关键字
    • 继承就代表,子类可继承父类的 field 和 method ,然后子类还可以在自己的内部实现父类没有的,子类特有的 field 和method,使用继承可以有效复用代码
    • 子类可以覆盖父类的field 和 method,但是如果父类用 final 修饰,或者 field 和 method 用 final 修饰,则该类是无法被继承的,或者 field 和 method 是无法被覆盖的
    • private修饰的 field 和 method 不可以被子类继承,只能在类的内部使用
    • field必须要被定义成val 的形式才能被继承,并且还要使用 override 关键字。因为 var 修饰的 field 是可变的,在子类中可直接引用被赋值,不需要被继承;即 val 修饰的才允许被继承,var 修饰的只允许被引用。继承就是改变、覆盖的意思 .
    • Java 中的访问控制权限,同样适用于Scala
    类内部 本包 子类 外部包
    public
    protected ×
    default × ×
    private × × ×
class Person {
  val name = "super"

  def getName = this.name
}

class Student extends Person {
  //继承加上关键字
  override
  val name = "sub"
  //子类可以定义自己的field和method
  val score = "A"

  def getScore = this.score
}
  • Scala中override 和 super 关键字\
    • Scala中,如果子类要覆盖父类中的一个非抽象方法必须要使用 override 关键字;子类可以覆盖父类的val修饰的field,只要在子类中使用 override 关键字即可
    • override关键字可以帮助开发者尽早的发现代码中的错误,比如, override 修饰的父类方法的方法名拼写错误
    • 此外,在子类覆盖父类方法后,如果在子类中要调用父类中被覆盖的方法,则必须要使用
      super关键字,显示的指出要调用的父类方法
class Person1 {
  private val name = "leo"
  val age = 50

  def getName = this.name
}

class Student extends Person1 {
  private val score = "A"
  //子类可以覆盖父类的 val field,使用override关键字
  override
  val age = 30

  def getScore = this.score

  //覆盖父类非抽象方法,必须要使用 override 关键字
  //同时调用父类的方法,使用super关键字
  override def getName = "your name is " + super.getName
}
  • Scala中isInstanceOf 和 asInstanceOf

    如果实例化了子类的对象,但是将其赋予了父类类型的变量,在后续的过程中,又需要将父类类型的变量转换为子类类型的变量,应该如何做?

    • 首先,需要使用isInstanceOf[] 判断对象是否为指定类的对象,如果是的话,则可以使用asInstanceOf[] 将对象转换为指定类型
    • 注意p.isInstanceOf[XX] 判断 p 是否为 XX 对象的实例;p.asInstanceOf[XX] 把 p 转换成 XX 对象的实例
    • 注意:如果没有用 isInstanceOf 先判断对象是否为指定类的实例,就直接用 asInstanceOf 转换,则可能会抛出异常
    • 注意:如果对象是 null,则 isInstanceOf一定返回 falseasInstanceOf一定返回 null
    • Scala与Java类型检查和转换的比较
Scala Java
obj.isInstanceOf[C] obj instanceof C
obj.asInstanceOf[C] ©obj
classOf[C] C.class
class Person3 {}

class Student3 extends Person3

object Student3 {
  def main(args: Array[String]) {
    val p: Person3 = new Student3
    var s: Student3 = null
    //如果对象是 null,则 isInstanceOf 一定返回 false
    println(s.isInstanceOf[Student3])//false
    // 判断 p 是否为 Student3 对象的实例
    if (p.isInstanceOf[Student3]) {
      //把 p 转换成 Student3 对象的实例
      s = p.asInstanceOf[Student3]
    }
    println(s.isInstanceOf[Student3])//true
  }
}
  • Scala中getClass和classOf
    • isInstanceOf只能判断出对象是否为指定类以及其子类的对象 , 而不能精确的判断出对象就是指定类的对象
    • 如果要求精确地判断出对象就是指定类的对象,那么就只能使用 getClass 和 classOf 了
    • p.getClass可以精确地获取对象的类,classOf[XX] 可以精确的获取类,然后使用==操作符即可判断
class Person4 {}

class Student4 extends Person4

object Student4 {
  def main(args: Array[String]) {
    val p: Person4 = new Student4
    //判断p是否为Person4类的实例
    println(p.isInstanceOf[Person4]) //true
    //判断p的类型是否为Person4类
    println(p.getClass == classOf[Person4]) //false
    //判断p的类型是否为Student4类
    println(p.getClass == classOf[Student4]) //true
  }
}
  • Scala中使用模式匹配进行类型判断
    • 在实际的开发中,比如spark 源码中,大量的地方使用了模式匹配的语法进行类型的判断,这种方式更加地简洁明了,而且代码的可维护性和可扩展性也非常高
    • 使用模式匹配,功能性上来说,与 isInstanceOf 的作用一样,主要判断是否为该类或其子类的对象即可,不是精准判断
    • 等同于 Java 中的 switch case 语法
class Person5 {}

class Student5 extends Person5

object Student5 {
  def main(args: Array[String]) {
    val p: Person5 = new Student5
    p match {
      // 匹配是否为Person类或其子类对象
      case per: Person5 => println("This is a Person5's Object!")
      // 匹配所有剩余情况
      case _ => println("Unknown type!")
    }
  }
}
//输出 : This is a Person5's Object!

tips : 当多个都匹配时 , 执行最先匹配的!

  • Scala 中protected
    • 跟 Java 一样,Scala 中同样可使用 protected 关键字来修饰 field 和 method。在子类中,可直接访问父类的 field 和 method,而不需要使用 super 关键字
    • 还可以使用protected[this] 关键字, 访问权限的保护范围:只允许在当前子类中访问父类的 field 和 method,不允许通过其他子类对象访问父类的 field 和 method
class Person6 {
  protected var name: String = "tom"
  protected[this] var hobby: String = "game"

  protected def sayBye = println(s"${hobby}再见...")
}

class Student6 extends Person6 {
  //父类使用protected 关键字来修饰 field可以直接访问
  def sayHello = println("Hello " + name)

  //父类使用protected 关键字来修饰method可以直接访问
  def sayByeBye = sayBye

  //父类用protected[this] 关键字来修饰 field可以在当前子类中访问
  def makeFriends(s: Student6) = {
    println("My hobby is " + hobby + ", your hobby is UnKnown")
  }
}

object Student6 {
  def main(args: Array[String]) {
    val s: Student6 = new Student6
    s.sayHello
    s.makeFriends(s)
    s.sayByeBye
    //其他子类对象中无法使用 , 报错
    //s.hobby
    //而s.name , s.sayBye是可以在其他子类对象中使用的
  }
}
//new Student6  相当于生成了一个其他子类对象
  • Scala中调用父类的constructor
    • Scala中,每个类都可以有一个主constructor和任意多个辅助constructor,而且每个辅助constructor的第一行都必须调用其他辅助constructor或者主constructor代码;因此子类的辅助constructor是一定不可能直接调用父类的constructor的
    • 只能在子类的主constructor中调用父类的constructor
    • 如果父类的构造函数已经定义过的 field,比如name和age,子类再使用时,就不要用 val 或 var 来修饰了,否则会被认为,子类要覆盖父类的field,且要求一定要使用 override 关键字
class Person7(val name: String, val age: Int) {
  var score: Double = 0.0
  var address: String = "beijing"

  def this(name: String, score: Double) = {
    //每个辅助constructor的第一行都必须调用其他辅助constructor或者主constructor代码
    //主constructor代码
    this(name, 30)
    this.score = score
  }

  //其他辅助constructor
  def this(name: String, address: String) = {
    this(name, 100.0)
    this.address = address
  }
}

class Student7(name: String, score: Double) extends Person7(name, score)
  • Scala中抽象类
    • 如果在父类中,有某些方法无法立即实现,而需要依赖不同的子类来覆盖,重写实现不同的方法。此时,可以将父类中的这些方法编写成只含有方法签名,不含方法体的形式,这种形式就叫做抽象方法
    • 一个类中,如果含有一个抽象方法或抽象field,就必须使用abstract将类声明为抽抽象类,该类是不可以被实例化的
    • 在子类中覆盖抽象类的抽象方法时,可以不加override关键字
abstract class Person9(val name: String) {
  //必须指出返回类型,不然默认返回为Unit
  def sayHello: String

  def sayBye: String
}

class Student9(name: String) extends Person9(name) {
  //必须指出返回类型,不然默认
    //可以使用override , 也可以不使用
  def sayHello: String = "Hello," + name

  def sayBye: String = "Bye," + name
}

object Student9 {
  def main(args: Array[String]) {
    val s = new Student9("tom")
    println(s.sayHello) //=>Hello,tom
    println(s.sayBye)   //=>Bye,tom
  }
}
  • Scala中抽象field
    • 如果在父类中,定义了field,但是没有给出初始值,则此field为抽象field
abstract class Person10 (val name:String){
	//抽象fields
    val age:Int
}
class Student10(name: String) extends Person10(name) {
   val age: Int = 50
}

1.4 Scala中面向对象编程之trait

  • 将trait作为接口使用
    • Scala中的trait是一种特殊的概念
    • 首先先将trait作为接口使用,此时的trait就与Java中的接口 (interface)非常类似
    • 在trait中可以定义抽象方法,就像抽象类中的抽象方法一样,只要不给出方法的方法体即可
    • 类可以使用extends关键字继承trait,注意,这里不是 implement,而是extends ,在Scala中没有 implement 的概念,无论继承类还是trait,统一都是 extends
    • 类继承后,必须实现其中的抽象方法,实现时,不需要使用 override 关键字
    • Scala不支持对类进行多继承,但是支持多重继承 trait,使用 with关键字即可
trait HelloTrait {
  def sayHello(): Unit
}

trait MakeFriendsTrait {
  def makeFriends(c: Children): Unit
}

//多重继承 trait
class Children(val name: String) extends HelloTrait with MakeFriendsTrait with Cloneable with Serializable {
  def sayHello() = println("Hello, " + this.name)//this表示当前对象

  def makeFriends(c: Children) = println("Hello, my name is " + this.name + ", your name is " + c.name)//c表示所需的内部属性
}

object Children {
  def main(args: Array[String]) {
    val c1 = new Children("tom")
    val c2 = new Children("jim")
    c1.sayHello()       //=>Hello, tom
    c1.makeFriends(c2)  //=>Hello, my name is tom, your name is jim
  }
}
  • 在trait中定义具体的方法
    • Scala中的trait不仅可以定义抽象方法,还可以定义具体的方法,此时 trait 更像是包含了通用方法的工具,可以认为trait还包含了类的功能
/**
  * 比如 trait 中可以包含很多子类都通用的方法,例如打印日志或其他工具方法等等。
  * spark就使用trait定义了通用的日志打印方法;
  */
trait Logger {
  //trait中可以定义具体的方法
  def log(message: String): Unit = println(message)
}

class PersonForLog(val name: String) extends Logger {
  def makeFriends(other: PersonForLog) = {
    println("Hello, " + other.name + "! My name is " + this.name + ", I miss you!!")
    this.log("makeFriends method is invoked with parameter PersonForLog[name = " + other.name + "]")
  }
}

object PersonForLog {
  def main(args: Array[String]) {
    val p1 = new PersonForLog("jack")
    val p2 = new PersonForLog("rose")
    p1.makeFriends(p2)
    //Hello, rose! My name is jack, I miss you!!
    //makeFriens method is invoked with parameter PersonForLog[name = rose]
  }
}
  • 在trait中定义具体field
    • Scala 中的 trait 可以定义具体的field,此时继承trait 的子类就自动获得了 trait 中定义的
      field
    • 但是这种获取 field 的方式与继承class 的是不同的。 如果是继承class 获取的field ,实际上还是定义在父类中的;而继承 trait获取的 field,就直接被添加到子类中了
trait PersonForField {
  val age: Int = 50
}

//继承 trait 获取的field直接被添加到子类中
class StudentForField(val name: String) extends PersonForField {
  def sayHello = println("Hi, I'm " + this.name + ", my  age  is " + age)
}

object StudentForField {
  def main(args: Array[String]) {
    val s = new StudentForField("tom")
    s.sayHello  //=>Hi ,I'm tom , my  age  is 50
  }
}
  • 在trait中定义抽象field
    • Scala中的trait也能定义抽象field, 而trait中的具体方法也能基于抽象field编写
    • 继承trait的类,则必须覆盖抽象field,提供具体的值
trait SayHelloTrait {
  val msg: String

  def sayHello(name: String) = println(msg + ", " + name)
}

class PersonForAbstractField(val name: String) extends SayHelloTrait {
  //必须覆盖抽象 field
  val msg = "Hello"

  def makeFriends(other: PersonForAbstractField) = {
    this.sayHello(other.name)
    println("I'm " + this.name + ", I want to make friends with you!!")
  }
}

object PersonForAbstractField {
  def main(args: Array[String]) {
    val p1 = new PersonForAbstractField("Tom")
    val p2 = new PersonForAbstractField("Rose")
    p1.makeFriends(p2)
    //Hello , Rose
    //I'm Tom, I want to make friends with you!!
  }
}
  • 在实例对象指定混入某个trait
    • 可在创建类的对象时,为该对象指定混入某个trait,且只有混入了trait的对象才具有trait中的方法,而其他该类的对象则没有
    • 在创建对象时,使用with关键字指定混入某个trait
trait LoggedTrait {
  // 该方法为实现的具体方法
  def log(msg: String) = {}
}

trait MyLogger extends LoggedTrait {
  // 覆盖 log() 方法
  override def log(msg: String) = println("log: " + msg)
}

class PersonForMixTraitMethod(val name: String) extends LoggedTrait {
  def sayHello = {
    println("Hi, I'm " + this.name)
    log("sayHello method is invoked!")
  }
}

object PersonForMixTraitMethod {
  def main(args: Array[String]) {
    val tom = new PersonForMixTraitMethod("Tom").sayHello //结果为:Hi, I'm Tom
    // 使用 with 关键字,指定混入MyLogger trait
    val rose = new PersonForMixTraitMethod("Rose") with MyLogger
    rose.sayHello
    // 结果为:     Hi, I'm Rose
    // 结果为:     log: sayHello method is invoked!
  }
}
  • trait 调用链
    • Scala中支持让类继承多个trait后,可依次调用多个trait中的同一个方法,只要让多个trait中的同一个方法,在最后都依次执行super关键字即可
    • 类中调用多个trait中都有的这个方法时,首先会从最右边的trait的方法开始执行,然后依次往左执行,形成一个调用链条
    • 这种特性非常强大,其实就是设计模式中责任链模式的一种具体实现
trait HandlerTrait {
  def handle(data: String) = {
    println("last one")
  }
}

trait DataValidHandlerTrait extends HandlerTrait {
  override def handle(data: String) = {
    println("check data: " + data)
    super.handle(data)
  }
}

trait SignatureValidHandlerTrait extends HandlerTrait {
  override def handle(data: String) = {
    println("check signature: " + data)
    super.handle(data)
  }
}

class PersonForRespLine(val name: String) extends SignatureValidHandlerTrait with DataValidHandlerTrait {
  def sayHello = {
    println("Hello, " + this.name)
    this.handle(this.name)
  }
}

object PersonForRespLine {
  def main(args: Array[String]) {
    val p = new PersonForRespLine("tom")
    p.sayHello
    //执行结果:
    //    Hello, tom
    //    check data: tom
    //    check signature: tom
    //    last one
  }
}

tips : 所谓责任链

以当前为例(sayHello)-->[this.handle(this.name)]-->DataValidHandlerTrait-->[super.handle(data)]-->SignatureValidHandlerTrait-->[super.handle(data)]-->(handle)

首先执行sayHello中的方法 , 根据this.handle传递到最右边的trait , 执行完之后再通过super.handle(data)传递到次右的trait , 依次传递到最后的trait , 如果其中的"传递介质

"(即上面中用[]包裹的)不存在 , 就无法向下传递了

  • 混合使用trait 的具体方法和抽象方法
    • 在 trait 中,可以混合使用具体方法和抽象方法
    • 可以让具体方法的方法体依赖于抽象方法,而抽象方法则可放到继承 trait的子类中去实现
    • 这种 trait 特性,其实就是设计模式中的模板设计模式的体现
trait ValidTrait {
  //抽象方法
  def getName: String

  //具体方法,具体方法的返回值依赖于抽象方法

  def valid: Boolean = {
    "Tom".equals(this.getName)
  }
}

class PersonForValid(val name: String) extends ValidTrait {
  def getName: String = this.name
}

object PersonForValid {
  def main(args: Array[String]): Unit = {
    val person = new PersonForValid("Rose")
    println(person.valid)//false
  }
}
  • trait的构造机制
    • 在Scala中,trait也是有构造代码的,即在trait中,不包含在任何方法中的代码
    • **继承了trait的子类,其构造机制如下 : **
    • 父类的构造函数先执行, class 类必须放在最左边;多个trait从左向右依次执行;构造trait时,先构造父 trait,如果多个trait继承同一个父trait,则父trait只会构造一次;所有trait构造完毕之后,子类的构造函数最后执行
class Person_One {
  println("Person's constructor!")
}

trait Logger_One {
  println("Logger's constructor!")
}

trait MyLogger_One extends Logger_One {
  println("MyLogger's constructor!")
}

trait TimeLogger_One extends Logger_One {
  println("TimeLogger's contructor!")
}

class Student_One extends Person_One with MyLogger_One with TimeLogger_One {
  println("Student's constructor!")
}

object exe_one {
  def main(args: Array[String]): Unit = {
    val student = new Student_One
    //执行结果为:
    //      Person's constructor!
    //      Logger's constructor!
    //      MyLogger's constructor!
    //      TimeLogger's contructor!
    //      Student's constructor!
  }
}
  • trait 继承 class
    • 在Scala中trait 也可以继承 class,此时这个 class 就会成为所有继承该 trait 的子类的超级父类
class MyUtil {
  def printMsg(msg: String) = println(msg)
}

trait Logger_Two extends MyUtil {
  def log(msg: String) = this.printMsg("log: " + msg)
}

class Person_Three(val name: String) extends Logger_Two {
  def sayHello {
    this.log("Hi, I'm " + this.name)
    this.printMsg("Hello, I'm " + this.name)
  }
}

object Person_Three {
  def main(args: Array[String]) {
    val p = new Person_Three("Tom")
    p.sayHello
    //执行结果:
    //      log: Hi, I'm Tom
    //      Hello, I'm Tom
  }
}

2. 模式匹配和样例类

Scala有一个十分强大的模式匹配机制,可以应用到很多场合:如switch语句、类型检查等。并且Scala还提供了样例类,对模式匹配进行了优化,可以快速进行匹配

2.1 匹配字符串

import scala.util.Random

object CaseDemo01 extends App {
  val arr = Array("hadoop", "zookeeper", "spark", "storm")
  val name = arr(Random.nextInt(arr.length))
  name match {
    case "hadoop"    => println("大数据分布式存储和计算框架...")
    case "zookeeper" => println("大数据分布式协调服务框架...")
    case "spark" => println("大数据分布式内存计算框架...")
    case _ => println("我不认识你...")
  }
}

2.2 匹配类型

import scala.util.Random


object CaseDemo02 extends App {
  val arr = Array("hello", 1, 2.0, CaseDemo01)
  val v = arr(Random.nextInt(4))
  println(v)
  v match {
    case x: Int => println("Int " + x)
    case y: Double if (y >= 0) => println("Double " + y)
    case z: String => println("String " + z)
    case _ => throw new Exception("not match exception")
  }

}

**注意 : **case y: Double if(y >= 0) => …

模式匹配的时候还可以添加守卫条件。如不符合守卫条件,将掉入case _中。

2.3 匹配数组、元组、集合

object CaseDemo03 extends App {

  val arr = Array(1, 3, 5)
  arr match {
    case Array(1, x, y) => println(x + " " + y)//3 5
    //case Array(1, x, y) => println(s"$x $y")
    case Array(0) => println("only 0")
    case Array(0, _*) => println("0 ...")
    case _ => println("something else")
  }

  val lst = List(3, -1)
  lst match {
    case 0 :: Nil => println("only 0")
    case x :: y :: Nil => println(s"x: $x y: $y")
    case 0 :: tail => println("0 ...")
    case _ => println("something else")
  }

  val tup = (1, 3, 7)
  tup match {
    case (1, x, y) => println(s"1, $x , $y")//=>
    case (_, z, 5) => println(z)
    case _ => println("else")
  }
}

注意:在Scala中列表要么为空(Nil表示空列表)要么是一个head元素加上一个tail列表。

9 :: List(5, 2) :: 操作符是将给定的头和尾创建一个新的列表

注意::: 操作符是右结合的,如9 :: 5 :: 2 :: Nil相当于 9 :: (5 :: (2 :: Nil))

notice :当出现多个匹配时 , 匹配第一个符合条件的 ; 输出的方式也可以使用$x , 但是需要在前面加s==>(s"1, $x , $y")

2.4 样例类

在Scala中样例类是一种特殊的类 , 可用于模式匹配

定义形式 :

  • case class 类型 , 是多例的 , 后面要跟构造参数 . =>case class Student(name:String)
  • case object 类型 , 是单例的 . ==>case object Person
import scala.util.Random

//样例类
case class SubmitTask(id: String, name: String)
case class HeartBeat(time: Long)
case object CheckTimeOutTask

object CaseDemo04 extends App {
  val arr = Array(CheckTimeOutTask, HeartBeat(12333), SubmitTask("0001", "task-0001"))

  arr(Random.nextInt(arr.length)) match {
    case SubmitTask(id, name) => {
      println(s"$id, $name")//=>0001,task-0001
    }
    case HeartBeat(time) => {
      println(time) //=>12333
    }
    case CheckTimeOutTask => {
      println("check") //=>check
    }
  }
}

2.5 Option类型

​ 在Scala中Option类型用样例类来表示可能存在或者可能不存在的值(Option的子类有Some和None)。Some包装了某个值,None表示没有值

object OptionDemo {
  def main(args: Array[String]) {
    val map = Map("a" -> 1, "b" -> 2)
    val v = map.get("b") match {
      case Some(i) => i //=>2
      case None => 0
    }
    println(v)
    //更好的方式 , 存在返回存在的值 , 不存在返回默认的
    val v1 = map.getOrElse("c", 0)
    println(v1)//=>0
  }
}

2.6 偏函数

​ 被包在花括号内没有match的一组case语句是一个偏函数,它是PartialFunction[A, B]的一个实例,A代表输入参数类型,B代表返回结果类型,常用作输入模式匹配,偏函数最大的特点就是它只接受和处理其参数定义域的一个子集。

object PartialFuncDemo {

  val func1: PartialFunction[String, Int] = {
    case "one" => 1
    case "two" => 2
    case _ => -1
  }

  def func2(num: String): Int = num match {
    case "one" => 1
    case "two" => 2
    case _ => -1
  }

  def main(args: Array[String]) {
    println(func1("one")) //=>1
    println(func2("one")) //=>1
  }
}

3. Scala中的协变、逆变、非变

3.1 协变、逆变、非变介绍

​ 协变和逆变主要是用来解决参数化类型的泛化问题。Scala的协变与逆变是非常有特色的,完全解决了Java中泛型的一大缺憾;举例来说,Java中,如果有 A是 B的子类,但 Card[A] 却不是 Card[B] 的子类;而 Scala 中,只要灵活使用协变与逆变,就可以解决此类 Java 泛型问题;

由于参数化类型的参数(参数类型)是可变的,当两个参数化类型的参数是继承关系(可泛化),那被参数化的类型是否也可以泛化呢?Java中这种情况下是不可泛化的,然而Scala提供了三个选择,即协变(“+”)逆变(“-”)非变

下面说一下三种情况的含义,首先假设有参数化特征Queue,那它可以有如下三种定义。

(1) trait Queue[T] {}

​ 这是非变情况。这种情况下,当类型B是类型A的子类型,则Queue[B]与Queue[A]没有任何从属关系,这种情况是和Java一样的。

(2) trait Queue[+T] {}
​ 这是协变情况。这种情况下,当类型B是类型A的子类型,则Queue[B]也可以认为是Queue[A]的子类型,即Queue[B]可以泛化为Queue[A]。也就是被参数化类型的泛化方向与参数类型的方向是一致的,所以称为协变。

(3) trait Queue[-T] {}

​ 这是逆变情况。这种情况下,当类型B是类型A的子类型,则Queue[A]反过来可以认为是Queue[B]的子类型。也就是被参数化类型的泛化方向与参数类型的方向是相反的,所以称为逆变。

3.2 协变、逆变、非变总结

  • C[+T]:如果A是B的子类,那么C[A]是C[B]的子类
  • C[-T]:如果A是B的子类,那么C[B]是C[A]的子类
  • C[T]: 无论A和B是什么关系,C[A]和C[B]没有从属关系

3.3 案例

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]) {
    //支持协变 Temp1[Sub]还是Temp1[Super]的子类
    val t1: Temp1[Super] = new Temp1[Sub]("hello scala!!!")
    //支持逆变 Temp1[Super]是Temp1[Sub]的子类
    val t2: Temp2[Sub] = new Temp2[Super]("hello scala!!!")
    //支持非变 Temp3[Super]与Temp3[Sub]没有从属关系,如下代码会报错
    //val t3: Temp3[Sub] = new Temp3[Super]("hello scala!!!")
    //val t4: Temp3[Super] = new Temp3[Sub]("hello scala!!!")
    println(t1.toString)  //=>com.itrk.cases_demo.Temp1@35851384
    println(t2.toString)  //=>com.itrk.cases_demo.Temp2@3c5a99da
  }
}

4. Scala中的上下界

​ 在指定泛型类型时,有时需要界定泛型类型的范围,而不是接收任意类型。比如,要求某个泛型类型,必须是某个类的子类,这样在程序中就可以放心的调用父类的方法,程序才能正常的使用与运行。此时,就可以使用上下边界Bounds的特性;Scala的上下边界特性允许泛型类型是某个类的子类,或者是某个类的父类

有以下两种形式 :

  • U >: T <==> ? super T

这是类型下界的定义,也就是U必须是类型T的父类或本身(自己也可以认为是自己的父类)

  • S <: T <==> ? extends T

这是类型上界的定义,也就是S必须是类型T的子类或本身(自己也可以认为是自己的子类)

猜你喜欢

转载自blog.csdn.net/CoderBoom/article/details/84584277