Scala学习(一)

变量声明

  1. 关键字valvar
    使用val定义的变量值是不可变的,可以理解为java里用final修饰的变量; 使用var定义的变量是可变得
    如: val i = 1
  2. 变量类型
    Scala编译器会自动推断变量的类型, 也可以指定变量类型
    如: val str: String = "hello"

常用类型

与Java一样,有7种数值类型Byte、Char、Short、Int、Long、Float、Double 和 1个Boolean类型。

条件表达式

  1. if else
    定义变量时加上if else判断条件 val x = if (条件) 值1 else 值2
  2. 缺失else
    if (x > 2) 1, 相当于if (x > 2) 1 else (), scala中的Unit类,相当于Java中的void,用于表示无返回结果的结果类型,Unit只有一个实例值,写成()
  3. if else if
    val x = if (条件1) 值1 else if (条件2) 值2 else 值3

块表达式

使用{}定义代码块,块中最后一个表达式的值就是块的值
如:

val result = {
	val a = 1
	val b = a + 1
	b //最后一个表达式的值
}

循环

  1. for循环 for (i <- 表达式/数组/集合)
    1.1 使用 表达式/数组/集合
    for (i <- 1 to 3) println(i) //1, 2, 3
    for (i <- 1 until 3) println(i) //1, 2
    
    val arr = Array(1, 2, 3)
    for (i <- arr) println(i) //1, 2, 3
    
    for(i <- 1 to 3; j <- 1 to 3 if i != j)
      println(i * j)
    
    1.2 yield
    val r = for (i <- 1 to 3) yield i * 2
    println(r) //2, 4, 6
    
    
  2. while循环
    var a = 10
    while( a < 20 ){
    	println( "a =  " + a )
    	a += 1
    }
    

方法和函数

  1. 定义方法和函数
    定义方法: def 方法名(参数列表): 返回值类型 = 方法体

    def m1(x: Int, y: Int): Int = {
      x * y
    }
    

    方法的返回值类型可以不写,但是对于递归函数(recursive method) 必须指定返回类型

    定义函数 val 函数名 = (参数列表) => 函数体
    val f1 = (x: Int, y: Int) => x * y

  2. 方法和函数的区别
    函数可以像任何其他数据类型一样被传递和操作

    def m2(f: (Int, Int) => Int) = f(1, 2)
    val r = m2(f1) //把f1作为参数输入m2
    println(r) //2
    

    函数是一个对象,继承自FuctionN,具有apply、curried、toString、tupled这些方法。

    可以用下划线将方法转换为函数 val f2 = m1 _

数组、映射、元组、集合

数组

  1. 定义数组
    1.1 定长数组:val arr = new Array[T](数组长度)
    初始化一个长度为5的数组 val arr1 = new Array[Int](5), 元素默认值为0
    或者 val arr = Array(1, 2, 3, 4, 5), 相当于调用了数组的apply方法,直接为数组赋了值

    1.2 变长数组:val arr = ArrayBuffer[T]() ,注意导包import scala.collection.mutable.ArrayBuffer

  2. 数组遍历
    使用for循环进行遍历

    for(i <- (0 until arr.length))
        println(arr(i))
        
    //使用reverse进行反转
    for(i <- (0 until arr.length).reverse)
      println(arr(i))
    
    //使用 if 按条件过滤 并使用 yield 生成一个新的数组
    val r = for (i <- arr if e % 2 == 0) yield i * 10
    
    //使用filter过滤,接收一个返回值为boolean的函数
    //map会将数组中的每一个元素取出来,对其应用传进去的函数
    val r1 = arr.filter(_ % 2 == 0).map(_ * 10)
    
  3. 数组常用算法

    //求和
    arr.sum
    //最大值
    arr.max
    //排序
    arr.sorted
    //...
    

映射

  1. 构建映射
    注意:immutable包下的Map,内容不可变;mutable包下的Map,内容可变
    val map=Map(键 -> 值,键 -> 值…)
    利用元组构建:val map=Map((键,值),(键,值),(键,值)…)
  2. 操作
    2.1 获取映射中的值
    map(键)
    map.getOrElse(键, 默认值) 如果键有对应的值就返回,如果没有就返回默认值

元组

元组是不同类型的值的聚集;元组表示通过将不同的值用小括号括起来

  1. 创建元组
    val tuple=(元素,元素…)
  2. 获取元组中的值
    使用下划线加脚标,如 t._1 t._2 t._3
    注意:元组中元素的脚标是从1开始的
  3. 操作
    3.1 将对偶的集合转换成映射
    val a = Array((1,"a"), (2,"b"))
    a.toMap
    
    3.2 拉链操作
    使用 zip 命令可以将多个值绑定在一起
    val n = Array("a", "b", "c")
    val b = Array(1, 2, 3)
    a.zip(b) //Array(("a",1),("b",2),("c",3))
    
    如果两个数组的元素个数不一致,则以较小的那个数组为准,或者使用 zipAll 用默认的元素填充
    val n = Array("a", "b", "c")
    val b = Array(1, 2)
    a.zipAll(b, "d", 3) //Array(("a",1),("b",2),("c",3))
    

集合

Scala的集合有三大类:序列Seq、Set、映射Map,所有的集合都扩展自Iterable特质,集合分为可变(mutable)和不可变(immutable)两种类型

  1. List
    1.1 不可变类型 import scala.collection.immutable._
    在Scala中列表要么为空(Nil表示空列表) 要么是一个head元素加上一个tail列表 9 :: List(5, 2)
    :: 操作符是将给定的头和尾创建一个新的列表
    注意::: 操作符是右结合的,如9 :: 5 :: 2 :: Nil相当于 9 :: (5 :: (2 :: Nil))
    list常用的操作符:

    操作符 说明
    : (elem: A): List[A] 在列表的头部添加一个元素
    : (x: A): List[A] 在列表的头部添加一个元素
    + (elem: A): List[A] 在列表的尾部添加一个元素
    +[B](that: GenTraversableOnce[B]): List[B] 从列表的尾部添加另外一个列表
    :: (prefix: List[A]): List[A] 在列表的头部添加另外一个列表

    如:

    val left = List(1,2,3)
    val right = List(4,5,6)
    //以下操作等价
    left ++ right      // List(1,2,3,4,5,6)
    right.:::(left)     // List(1,2,3,4,5,6)
    //以下操作等价
    0 +: left    //List(0,1,2,3)
    left.+:(0)   //List(0,1,2,3)
    //以下操作等价
    left :+ 4    //List(1,2,3,4)
    left.:+(4)   //List(1,2,3,4)
    //以下操作等价
    0 :: left      //List(0,1,2,3)
    left.::(0)     //List(0,1,2,3)
    

    1.2 可变类型 import scala.collection.mutable._

    import scala.collection.mutable.ListBuffer
    
      //构建一个可变列表,初始有3个元素1,2,3
      val lst0 = ListBuffer[Int](1,2,3)
      //创建一个空的可变列表
      val lst1 = new ListBuffer[Int]
      //向lst1中追加元素,注意:没有生成新的集合
      lst1 += 4
      lst1.append(5)
    
      //将lst1中的元素最近到lst0中, 注意:没有生成新的集合
      lst0 ++= lst1
    
      //将lst0和lst1合并成一个新的ListBuffer 注意:生成了一个集合
      val lst2= lst0 ++ lst1
    
      //将元素追加到lst0的后面生成一个新的集合
      val lst3 = lst0 :+ 5
    
      //删除元素,注意:没有生成新的集合
      val lst4 = ListBuffer[Int](1,2,3,4,5)
      lst4 -= 5
    
      //删除一个集合列表,生成了一个新的集合
      val lst5=lst4--List(1,2)
    
      //把可变list 转换成不可变的list 直接加上toList
      val lst6=lst5.toList
    
      //把可变list 转变数组用toArray
      val lst7=lst5.toArray
    
  2. Set
    Set代表一个没有重复元素的集合;而且 Set 中的元素是乱序的。

    2.1 不可变类型 import scala.collection.immutable._

    //创建一个set集合
    val set = Set(1,2,3,4,5)
    //一些操作
    set.size //元素个数
    set.min //最小值
    set.max //最大值
    
    //向集合中添加元素,会生成新的集合,原有set不变
    set + 6
    
    val set1 = Set(5,6,7)
    //两个集合的交集
    set & set1
    //两个集合的并集
    set ++ set1
    //从一个集合中除去另一个集合中存在的元素
    scala> set -- set1
    //返回第一个不同于第二个set的元素集合
    set &~ set1
    set.diff(set1)
    //计算符合条件的元素个数
    set.count(_ >3)
    //取子set(2,5为元素位置, 从0开始,包含头不包含尾)
    set.slice(2,5) //Set(3,4,5)
    //迭代所有的子set,取指定的个数组合
    set1.subsets(2).foreach(x=>println(x)) //Set(5,6) Set(5,7) Set(6,7)
    

    2.2 可变类型 import scala.collection.mutable._

    //定义一个可变的Set
    val set=new HashSet[Int]()
    //添加元素
    set += 1
    set.add(2)
    //向集合中添加元素集合
    set ++= Set(3,4)
    //删除一个元素
    set -= 4
    set.remove(3)
    
  3. Map
    3.1 不可变类型 import scala.collection.immutable._
    构建映射
    val map=Map(键 -> 值,键 -> 值…)
    或者利用元组构建:val map=Map((键,值),(键,值),(键,值)…)
    操作

    • 获取映射中的值
      map(键)
      map.getOrElse(键, 默认值) 如果键有对应的值就返回,如果没有就返回默认值
    • 显示所有的key map.keys 或者 map.keySet

    3.2 可变类型 import scala.collection.mutable._

    val map = mutable.HashMap("a"->1,"b" -> 2)
    //添加键值对
    map += ("c" -> 3)
    map += ("d" -> 4, "e" -> 5)
    //更新键值对
    map.("a") = 6
    //更新多个键值对
    map += ("a" -> 7, "b" -> 8)
    //删除
    map -= ("a")
    map.remove("b")
    //通过key遍历
    for(x<- map.keys) println(x+" -> "+map(x))
    //通过模式匹配遍历
    for((x,y) <- map) println(x+" -> "+y)
    //通过foreach遍历
    map.foreach{case (x,y) => println(x+" -> "+y)}
    

类、对象、继承、特质

  1. 类的定义
    在Scala中,类并不用声明为public类型的。Scala源文件中可以包含多个类,所有这些类都具有共有可见性。
    class Student{
      //用val修饰的变量是可读属性,有getter但没有setter(相当与Java中用final修饰的变量)
      val id="001"
      //用var修饰的变量都既有getter,又有setter
      var age:Int=18
      //private 类私有字段,只能在类的内部使用或者伴生对象中访问
      private var name : String = "张三"
    
      //private[this] 访问权限更加严格的类私有字段,该字段只能在当前类中被访问,在伴生对象里面也不可以访问
      private[this] var score = 98
    }
    
    //object 伴生对象(名字和类名相同)
    object Student{
      def main(args: Array[String]): Unit = {
        val s = new Student
        //打印
        println(s.id)
        println(s.age)
        //打印name,伴生对象中可以在访问private变量
        println(s.name)
        //由于score 字段用private[this]修饰,伴生对象中访问不到pet变量
        //s.score  //访问不到
      }
    }
    
  2. 构造器
    Scala中的每个类都有主构造器,主构造器的参数直接放置类名后面,与类交织在一起。主构造器会执行类定义中的所有语句。
    class Student(val name:String,var age:Int) {
      //主构造器会执行类定义的所有语句
      println("执行主构造器")
      private  var gender="male"
      //辅助构造器
      def this(name:String,age:Int,gender:String){
        //每个辅助构造器执行必须以主构造器或者其他辅助构造器的调用开始
        this(name,age)
        println("执行辅助构造器")
        this.gender=gender
      }
    }
    
    object Student {
      def main(args: Array[String]): Unit = {
        val s1=new Student("zhangsan",20)
        val s2=new Student("zhangsan",20,"female")
      }
    }
    

对象

  1. object
    object 相当于 class 的单个实例,通常在里面放一些静态的 field 或者 method;
    在Scala中没有静态方法和静态字段,但是可以使用object这个语法结构来达到同样的目的。
    object作用:

    1. 存放工具方法和常量
    2. 高效共享单个不可变的实例
    3. 单例模式

    例如:

    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)
      }
    }
    
  2. 类的伴生对象
    class文件,有一个与其同名的object文件,那么就称这个object是class的伴生对象,class是object的伴生类;
    伴生类和伴生对象必须存放在一个 .scala 文件中;伴生类和伴生对象的最大特点是,可以相互访问;

    class Student{
      val id = 1
      private var name = "lisi"
      def printName(): Unit ={
        //在Student类中可以访问伴生对象Student的私有属性
        println(Student.CONSTANT + name )
      }
    }
    //伴生对象
    object Student{
      //伴生对象中的私有属性
      private val CONSTANT = "姓名 : "
      def main(args: Array[String]) {
        val s = new Student
        //访问私有的字段name
        s.name = "张三"
        s.printName()
      }
    }
    
  3. 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
    }
    

    在创建Array时可以new Array来创建,也可以直接通过apply方法来创建

    //new一个长度为 5 的array,数组里面包含5个null
    var arr1 = new Array(5)
    
    //调用Array伴生对象的apply方法
    //def apply(x: Int, xs: Int*): Array[Int]
    //arr2中只有一个元素5
    val arr2 = Array(5)
    
  4. main方法
    和Java一样Scala也需要一个 main 方法作为程序的入口
    Scala 中的 main 方法必须定义在 object 中,格式为 def main(args: Array[String])

    除了自己实现 main 方法之外,还可以继承 App Trait,然后,将需要写在 main 方法中运行的代码,直接作为 object 的 constructor 代码即可,而且还可以使用 args 接收传入的参数,如:

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

继承(extends)

  1. Scala中的继承(extends)
    同 Java 一样,Scala中子类继承父类,也是使用 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

    类内部 本包 子类 外部包
    ublic
    rotected
    efault ×
    rivate × ×

    如:

    class Person {
      val name="super"
      def getName=this.name
    }
    class Student extends Person{
      //继承加上关键字
      override
      val name="stu"
      //子类可以定义自己的field和method
      val score="90"
      def getScore=this.score
    }
    
  2. override 和 super 关键字
    Scala中,如果子类要覆盖父类中的一个非抽象方法,必须要使用 override 关键字;子类可以覆盖父类的 val 修饰的field,只要在子类中使用 override 关键字即可。
    此外,在子类覆盖父类方法后,如果在子类中要调用父类中被覆盖的方法,则必须要使用 super 关键字,显示的指出要调用的父类方法。
    如:

    class Person {
      private val name = "lisi"
      val age=20
      def getName = this.name
    }
    class Student extends Person{
      private val score = 98
      //子类可以覆盖父类的 val field,使用override关键字
      override
      val age=30
      def getScore = this.score
      //覆盖父类非抽象方法,必须要使用 override 关键字
      //同时调用父类的方法,使用super关键字
      override def getName = "your name is " + super.getName
    }
    
  3. isInstanceOf 和 asInstanceOf
    isInstanceOf 用于判断对象是否为指定类的对象
    asInstanceOf 用于将对象转换为指定类型
    注意:如果没有用 isInstanceOf 先判断对象是否为指定类的实例,就直接用 asInstanceOf 转换,则可能会抛出异常
    此外,如果对象是 null,则 isInstanceOf 一定返回 false, asInstanceOf 一定返回 null
    Scala与Java类型检查和转换

    Scala Java
    obj.isInstanceOf[C] obj instanceof C
    obj.asInstanceOf[C] (C)obj
    classOf[C] C.class
  4. getClass 和 classOf
    isInstanceOf 只能判断出对象是否为指定类以及其子类的对象,而不能精确的判断出,对象就是指定类的对象;
    如果要求精确地判断出对象就是指定类的对象,那么就可以使用 getClass 和 classOf 了;
    p.getClass == classOf[Person] //返回true 或者 false

  5. Scala中使用模式匹配进行类型判断
    使用模式匹配的语法进行类型的判断,更加地简洁明了,代码的可维护性和可扩展性也非常高;从功能性上来说,模式匹配与 isInstanceOf 的作用一样,主要判断是否为该类或其子类的对象即可,不是精准判断。
    其语法与 Java 中的 switch case 相同:

    val p:Person=new Student
        p match {
          // 匹配是否为Person类或其子类对象
          case per:Person => println("This is a Person's Object!")
          // 匹配所有剩余情况
          case _  =>println("Unknown type!")
        }
    
  6. Scala 中的 protected
    跟 Java 一样,Scala 中同样可使用 protected 关键字来修饰 field 和 method。在子类中,可直接访问父类的 field 和 method,而不需要使用 super 关键字;
    还可以使用 protected[this] 关键字, 限制访问权限的保护范围:只允许在当前子类中访问父类的 field 和 method,不允许通过其他子类对象访问父类的 field 和 method。

    class Person{
      protected var name:String="zhangsan"
      protected[this] var hobby:String ="game"
      protected def sayBye=println("再见...")
    }
    class Student extends Person{
      //父类使用protected 关键字来修饰 field可以直接访问
      def  sayHello =println("Hello "+name)
      //父类使用protected 关键字来修饰method可以直接访问
      def  sayByeBye=sayBye
      def makeFriends(s:Student)={
        println("My hobby is "+hobby+", your hobby is UnKnown")
      }
    }
    object Student{
      def main(args: Array[String]) {
        val s:Student=new Student
        s.sayHello
        s.makeFriends(s)
        s.sayByeBye
      }
    }
    
  7. 调用父类的constructor
    Scala中每个类都可以有一个主constructor和任意多个辅助constructor,而且每个辅助constructor的第一行都必须调用其他辅助constructor或者主constructor代码;因此子类的辅助constructor是一定不可能直接调用父类的constructor的,只能在子类的主constructor中调用父类的constructor。
    如果父类的构造函数已经定义过的 field,比如name和age,子类再使用时,就不要用 val 或 var 来修饰了,否则会被认为,子类要覆盖父类的field,且要求一定要使用 override 关键字。
    如:

    class Person(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 Student(name:String,score:Double) extends Person(name,score)
    
  8. 匿名内部类
    匿名内部类,就是定义一个没有名称的子类,并直接创建其对象,然后将对象的引用赋予一个变量,即匿名内部类的实例化对象。然后将该对象传递给其他函数使用。
    例:

    class Person(val name:String) {
      def sayHello="Hello ,I'm "+name
    }
    class GreetDemo{
      //接受Person参数,并规定Person类只含有一个返回String的sayHello方法
      def greeting(p:Person{
          def sayHello:String})={
              println(p.sayHello)
      }
    }
    object GreetDemo{
      def main(args: Array[String]) {
        //创建Person的匿名子类对象
        val p=new Person("tom")
        val g=new GreetDemo
        g.greeting(p)
      }
    }
    
  9. 抽象类、抽象方法、抽象field
    一个类中,如果含有一个抽象方法或抽象field,就必须使用abstract将类声明为抽象类,该类是不可以被实例化的;
    如果在父类中,有某些方法无法立即实现,而需要依赖不同的子类来覆盖,重写实现不同的方法。此时,可以将父类中的这些方法编写成只含有方法签名,不含方法体的形式,这种形式就叫做抽象方法;
    如果在父类中,定义了field,但是没有给出初始值,则此field为抽象field
    在子类中覆盖抽象类的抽象方法时,可以不加override关键字;

    abstract class Person(val name:String) {
      //必须指出返回类型,不然默认返回为Unit
      def sayHello:String
      def sayBye:String
    }
    class Student(name:String) extends Person(name){
      //必须指出返回类型,不然默认
      def sayHello: String = "Hello,"+name
      def sayBye: String ="Bye,"+name
    }
    object Student{
      def main(args: Array[String]) {
        val s = new Student("tom")
        println(s.sayHello)
        println(s.sayBye)
      }
    }
    

trait

  1. 将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)
      def makeFriends(c: Children) = println("Hello, my name is " + this.name + ", your name is " + c.name)
    }
    object Children{
      def main(args: Array[String]) {
        val c1=new Children("zhangsan")
        val c2=new Children("lisi")
        c1.sayHello()
        c1.makeFriends(c2)
      }
    }
    
  2. trait中定义具体的方法
    Scala中的trait不仅可以定义抽象方法,还可以定义具体的方法,此时 trait 更像是包含了通用方法的工具,可以认为trait还包含了类的功能。
    比如 trait 中可以包含很多子类都通用的方法,例如打印日志或其他工具方法等等。

    trait Logger {
      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]
      }
    }
    
    
  3. trait中定义具体field 和 抽象field
    Scala 中的 trait 可以定义具体的 field,此时继承 trait 的子类就自动获得了 trait 中定义的 field;但是这种获取 field 的方式与继承 class 的是不同的。 如果是继承 class 获取的 field ,实际上还是定义在父类中的;而继承 trait获取的 field,就直接被添加到子类中了。

    Scala中的trait也能定义抽象field, 而trait中的具体方法也能基于抽象field编写;继承trait的类,则必须覆盖抽象field,提供具体的值;
    如:

    trait TestTrait {
      //具体的field
      val  msg1:String="Hello"
      //抽象的field
      val msg2:String
    }
    
    class TestClass() extends TestTrait  {
      //继承 trait 获取的field直接被添加到子类中
      def printMsg= println(msg1)
      
      //必须覆盖抽象 field
      val msg2 = "Hello"
    }
    
  4. 在实例对象指定混入某个trait
    在创建对象时,使用 with 关键字指定混入某个 trait,且只有混入了trait的对象才具有trait中的方法,而其他该类的对象则没有;
    例:

    trait TestTrait {
      def log(msg: String) = println("log: " + msg)
    }
    
    class TestClass(val name: String) extends TestTrait  {
      def printLog= {
        println(this.name)
        log("printLog method is invoked!")
      }
    }
    object TestClass{
      def main(args: Array[String]) {
        val tc1= new TestClass("zhangsan").printLog //结果为:Hi, I'm zhangsan
        
        // 使用 with 关键字,指定混入TestTrait  trait
        val tc2= new TestClass("lisi") with TestTrait 
        tc2.printLog
    	/* 结果为:     Hi, I'm lisi
    				  log: printLog method is invoked! */
      }
    }
    
  5. trait 调用链
    Scala中支持让类继承多个trait后,可依次调用多个trait中的同一个方法,只要让多个trait中的同一个方法,在最后都依次执行 super 关键字即可;
    类中调用多个trait中都有的这个方法时,首先会从最右边的trait的方法开始执行,依次往左执行,形成一个调用链条;
    这种特性非常强大,其实就是设计模式中责任链模式的一种具体实现;

    trait Trait1 {
      def handle(msg: String) = {println("1")}
    }
    trait Trait2 extends Trait1{
      override def handle(msg: String) = {
                  println("2: " + msg)
                  super.handle(msg)
    }
    }
    trait Trait3 extends Trait1 {
      override def handle(msg: String) = {
              println("3: " + msg)
              super.handle(msg)
      }
    }
    class TestClass(val msg: String) extends Trait3 with Trait2 {
      def printMsg= {
            println("msg: " + this.msg)
            this.handle(this.msg)
      }
    }
    object TestClass{
      def main(args: Array[String]) {
         val tc=new TestClass("hello")
          tc.printMsg
          //执行结果:
    	  //msg:hello
          //3:hello
          //2:hello
          //1
      }
    }
    
  6. 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!
      }
    }
    
  7. 混合使用 trait 的具体方法和抽象方法
    在 trait 中,可以混合使用具体方法和抽象方法;可以让具体方法依赖于抽象方法,而抽象方法则可放到继承 trait的子类中去实现;这种 trait 特性,其实就是设计模式中的模板设计模式的体现;
    如:

    trait ValidTrait {
      def getName: String      //抽象方法         
     def valid: Boolean = {"Jack".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
      }
    }
    
  8. 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 { //此时 Person_Three 已经成为 MyUtil  的子类
        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
      }
    }
    

模式匹配和样例类

  1. 模式匹配
    //匹配字符串
    val str = "hello"
    str match {
      case "hello"    => println("world")
      case "hi" => println("scala")
      case _ => println("wow")
    }
    
    //匹配类型
    val arr = Array("hello", 1, 2.0)
    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 _中。
      case z: String => println("String " + z)
      case _ => throw new Exception("not match exception")
    }
    
    //匹配数组
    val arr = Array(1, 3, 5)
    arr match {
      case Array(1, x, y) => println(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")
    }
    
  2. 样例类
    case class 类型,是多例的,后面要跟构造参数。
    case object 类型,是单例的。
    case class SubmitTask(id: String, name: String)
    case class HeartBeat(time: Long)
    case object CheckTimeOutTask
    
    object CaseDemo 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")
        }
        case HeartBeat(time) => {
          println(time)
        }
        case CheckTimeOutTask => {
          println("check")
        }
      }
    }
    
  3. Option类型
    Option 类型用样例类来表示可能存在或者可能不存在的值(Option 的子类有Some和None)。Some包装了某个值,None表示没有值
     val map = Map("a" -> 1, "b" -> 2)
     val v = map.get("b") match {
       case Some(i) => i
       case None => 0
     }
     println(v)
     //更好的方式
     val v1 = map.getOrElse("c", 0)
     println(v1)
    }
    
  4. 偏函数
    被包在花括号内没有match的一组case语句是一个偏函数,它是PartialFunction[A, B]的一个实例,A代表输入参数类型,B代表返回结果类型,常用作输入模式匹配,偏函数最大的特点就是它只接受和处理其参数定义域的一个子集。
    //偏函数
    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
    }
    

协变、逆变、非变

协变和逆变主要是用来解决参数化类型的泛化问题。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]的子类型。也就是被参数化类型的泛化方向与参数类型的方向是相反的,所以称为逆变。

可以理解为:

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

如:

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)
    println(t2.toString)
  }
}

Scala中的上下界

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

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

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

你可能感兴趣的:(语言学习)