6.1.2 Scala 数组元组(变长,操作,算法,多维,元组), 类与对象(无参构造,自定义GS,Bean,构造器,对象(单例,伴生,app对象)), 继承(构造执行顺序,方法重写,类型检查转换)

目录

第三部分 数组和元组

第1节 数组定义

第2节 变长数组

第3节 数组操作

第4节 常见算法

第5节 多维数组

第6节 元组及操作

第四部分 类与对象

第1节 类和无参构造器

第2节 自定义getter和setter方法

第3节 Bean属性

第4节 构造器

第5节 对象

5.1 单例对象

5.2 伴生类与伴生对象

5.3 应用程序对象

5.4 apply方法

第五部分 继承

第1节 继承的概念

第2节 构造器执行顺序

第3节 override方法重写

第4节 类型检查与转换


 

第三部分 数组和元组

第1节 数组定义

数组几乎是所有语言中最基础的数据结构。数组可索引、类型一致、长度不变。

* 在Scala中,数组分为定长数组和变长数组。

* 定长数组,Array,长度是不变的

* 变长数组,ArrayBuffer,长度是可变的,它等效于Java中的ArrayList

* 使用变长数组之前需要导入包scala.collection.mutable.ArrayBuffer

object ArrayDemo {
  def main(args: Array[String]): Unit = {

    println("===========定长数组================")
    //定义长度为10的整型数组,初始值为0
    val nums = new Array[Int](10)

    //定义长度为10的字符串数组,初始值为null
    val strs = new Array[String](10)

    //访问数组元素,索引从0开始,使用()而不是[]来访问元素
    println(strs(0))

    //省略了关键字new,那么Scala会进行自动类型推断
    val arrays: Array[Int] = Array(1, 2, 3, 4)
    val arrays2: Array[Nothing] = Array()
    println(arrays.length)

    //通过toArray快速定义数组,用于测试
    val numsList: Array[Int] = (1 to 10).toArray
  } 
}

 

第2节 变长数组

长度按需要变换的数组ArrayBuffer。Scala 中很多数组类型都有可变、不可变两个版本,推荐使用不可变的数组类型,使用可变数组类型时需要显示声明;

使用ArrayBuffer时,需要导包 import scala.collection.mutable.ArrayBuffer;

import scala.collection.mutable.ArrayBuffer

object VarArrayDemo {
  def main(args: Array[String]) {

    println("===========变长数组================")
    //定义一个空的Int的变长数组
    //注意:后面要有小括号
    val numsBuffer = ArrayBuffer[Int]()

    //通过+=在尾端添加一个或多个元素
    numsBuffer += 1
    numsBuffer += (2, 3, 4, 5)

    //通过++=在尾端添加集合
    numsBuffer ++= Array(6, 7, 8)
    numsBuffer.foreach(println(_))

    //还可通过-=  --=对变长数组进行删减
    numsBuffer -= 8
    numsBuffer --= Array(6, 7)

    println("============删减后的变长数组=============")
    numsBuffer.foreach(println(_))

    //使用append追加一个或多个元素
    numsBuffer.append(1)
    numsBuffer.append(2, 3, 4)    // 1 2 3 4 ...

    println("============追加后的变长数组=============")
    numsBuffer.foreach(println(_))

    //在某索引之前插入元素, 使用变长参数时, 表示重复在该位置插入参数
    numsBuffer.insert(2,10)     // 1 10 2 3 4 ...
    numsBuffer.insert(2,20,30)  // 1 30 20 10 2 3 4 ...

    println("============插入后的变长数组=============")
    numsBuffer.foreach(println(_))

    //移除元素
    //trimEnd移除最后的N个元素
    numsBuffer.trimEnd(3)
    //trimStart移除去最开始的一个或多个元素
    numsBuffer.trimStart(2)

    println("============移除后的变长数组=============")
    numsBuffer.foreach(println(_))

    //通过remove从某索引处移除一个或多个元素
    numsBuffer.remove(3)
    numsBuffer.remove(2,3)  // 从索引2开始,删掉3个元素

    println("============remove后的变长数组=============")
    numsBuffer.foreach(println(_))

  }
}

 

第3节 数组操作

import scala.collection.mutable.ArrayBuffer

object VarArrayDemo {
  def main(args: Array[String]) {

    println("==================数组转换=======================")

    //toArray,变长数组转换为定长数组
    val numArray: Array[Int] = numsBuffer.toArray

    //toBuffer,定长数组转换为变长数组
    val arrayBuffer: mutable.Buffer[Int] = arrays.toBuffer

    println("==================数组遍历=======================")

    println("===============使用until进行数组遍历==============")
    for(i <- 0 until(arrays.length)){
      println(arrays(i))
    }
    println("===============使用to进行数组遍历==============")
    for (i <- 0 to arrays.length-1){
      println(arrays(i))
    }
    println("===============使用增强for循环的方式进行数组遍历==============")
    for(elem <- arrays) println(elem)

    println("===============使用foreach进行数组遍历==============")
    arrays.foreach(println(_))
  }
}

 

第4节 常见算法

在Scala中对数组进行转换非常简单方便,这些转换动作不会修改原始数组,而是产生一个全新的数组。

package com.ch.part03

object OperatorDemo {
  def main(args: Array[String]): Unit = {

    println("=====================================================================")
    //将数组中的偶数加倍,奇数丢弃
    val nums = (1 to 10).toArray
    val result1 = for (elem <- nums if elem % 2 == 0) yield elem * 2
    val result2 = for (elem <- nums) yield if (elem % 2 == 0) elem * 2 else 0
    result1.foreach(println(_))
    result2.foreach(println(_))

    //使用Scala中的高阶函数来实现
    // _ 代表每一个元素, 用filter过滤, 对过滤出的每一个元素, 用map映射
    nums.filter(_ % 2 == 0).map(_ * 2).foreach(println(_))

    println("================== head last tail init ========================")
    //获取第一个元素
    println(nums.head)                  // 1
    //获取最后一个元素
    println(nums.last)                  // 10
    //获取除了第一个元素之外的其他元素
    println(nums.tail.toBuffer)         // ArrayBuffer(2, 3, 4, 5, 6, 7, 8, 9, 10)
    //获取除了最后一个元素之外的其他元素
    println(nums.init.toBuffer)         // ArrayBuffer(1, 2, 3, 4, 5, 6, 7, 8, 9)
    
    println("============= sum max min sorted 相乘product ================")
    //求和
    println(nums.sum)   // 55
    //求最大值
    println(nums.max)   // 10
    //求最小值
    println(nums.min)   // 1

    val nums2 = Array(2, 1, 4, 3)
    //数组元素排序,升序
    println(nums2.sorted.toBuffer)  // ArrayBuffer(1, 2, 3, 4)
    //元素相乘
    println(nums2.product)          // 24

    println("========== 数组操作: map reduce distinct indices ================")
    val nums3 = Array(1, 2, 3, 4, 3, 2, 1)
    println(nums3.map(_ * 2).toBuffer)        // ArrayBuffer(2, 4, 6, 8, 6, 4, 2)
    // 聚合操作, 逐个相加
    println(nums3.reduce(_ + _))              // 16
    //将数组中的数据进行去重
    println(nums3.distinct.toBuffer)          // ArrayBuffer(1, 2, 3, 4)
    println(nums3.length)     // 7
    println(nums3.size)       // 7
    //获取数组中每个元素的索引
    println(nums3.indices.toBuffer)     // ArrayBuffer(0, 1, 2, 3, 4, 5, 6)

    println("========== 数组操作: mkString count filter ====================")
    //使用mkString进行输出, 每个元素之间, 使用 & 分隔符
    println(nums3.mkString(" & "))                // 1 & 2 & 3 & 4 & 3 & 2 & 1
    // 使用 & 分隔,  使用 < > 作为前后缀
    println(nums3.mkString("<", " & ", ">"))      // <1 & 2 & 3 & 4 & 3 & 2 & 1>

    //count计数,注意:count后面必须有条件
    println(nums3.count(_ > 2))         // 3
    println(nums3.count(_ % 2 == 0))    // 3

    //filter过滤出符合条件的数据;
    println(nums3.filter(_ > 2).toBuffer)           // ArrayBuffer(3, 4, 3)
    //filterNot过滤出不符合条件的数据
    println(nums3.filterNot(_ % 2 == 0).toBuffer)   // ArrayBuffer(1, 3, 3, 1)

    println("============== 数组元素提取 take takeRight takeWhile ==============")
    //take提取前N个元素
    println(nums3.take(3).toBuffer)               // ArrayBuffer(1, 2, 3)
    //takeRight提取后N个元素
    println(nums3.takeRight(4).toBuffer)          // ArrayBuffer(4, 3, 2, 1)
    //takeWhile从左向右进行提取
    // 提取出符合条件的元素,如果条件不成立就终止提取
    println(nums3.takeWhile(_ < 4).toBuffer)      // ArrayBuffer(1, 2, 3)

    println("============= 数组元素删除 drop dropRight dropWhile =====================")
    //删除前N个元素
    println(nums3.drop(3).toBuffer)                         // ArrayBuffer(4, 3, 2, 1)
    //删除后N个元素
    println(nums3.dropRight(3).toBuffer)                    // ArrayBuffer(1, 2, 3, 4)
    //从左向右删除符合条件的元素,如果条件不成立,就终止删除操作
    println(nums3.dropWhile(_ < 4).toBuffer)                // ArrayBuffer(4, 3, 2, 1)

    println("=====================数组切分 split/ slice================================")
    // splitAt将数组分为两部分,前N个为一部分,剩下的为另一部分
    val tuple: (Array[Int], Array[Int]) = nums3.splitAt(3)
    // ._1 表示所有元素中的第一个部分,  ._2 为第二部分
    println(tuple._1.toBuffer + "  " + tuple._2.toBuffer)     // ArrayBuffer(1, 2, 3)  ArrayBuffer(4, 3, 2, 1)
    //对数组进行切片操作,取出从索引2到索引4的元素,不包括索引为5的元素
    println(nums3.slice(2, 5).toBuffer)                       // ArrayBuffer(3, 4, 3)

    println("==================== 数组填充zip zipAll zipWithIndex ======================")
    val array1 = Array("A", "B", "C")
    val array2 = Array(1, 2, 3, 4)
    //拉链操作,当两个数组的长度不一样时,截取相同的长度
    val z1: Array[(String, Int)] = array1.zip(array2)
    println(z1.toBuffer)        // ArrayBuffer((A,1), (B,2), (C,3))

    //拉链操作,当两个数组长度不一样时,array1用*填充,array2用-1填充
    val z2 = array1.zipAll(array2, "*", -1)
    //拉链操作,当两个数组长度不一样时,array2用*填充,array1用-1填充
    val z3 = array2.zipAll(array1, "*", -1)
    //用数组索引进行填充
    val z4 = array1.zipWithIndex
    println(z2.toBuffer)      // ArrayBuffer((A,1), (B,2), (C,3), (*,4))
    println(z3.toBuffer)      // ArrayBuffer((1,A), (2,B), (3,C), (4,-1))
    println(z4.toBuffer)      // ArrayBuffer((A,0), (B,1), (C,2))

    println("========================= 数组拆分unzip unzip3 ===========================")
    //通过unzip进行拆分数组的操作
    // 通过unzip把z4拆分成两个数组
    val (l1, l2) = z4.unzip
    println(l1.toBuffer)      // ArrayBuffer(A, B, C)
    println(l2.toBuffer)      // ArrayBuffer(0, 1, 2)

    // 通过unzip3 进行拆分操作
    val (l3, l4, l5) = Array((1, "one", '1'), (2, "two", '2'), (3, "three", '3')).unzip3
    println(l3.toBuffer)      // ArrayBuffer(1, 2, 3)
    println(l4.toBuffer)      // ArrayBuffer(one, two, three)
    println(l5.toBuffer)      // ArrayBuffer(1, 2, 3)

    println("===================== 数组的操作符::+  +:  ++ =================")
    // :+用于在数组的尾部追加元素; +:用于在数组的头部追加元素
    // ++用于连接两个集合(比如:数组、列表等)
    val num1 = (1 to 4).toArray
    val num2 = (5 to 8).toArray

    val num3 = 10 +: num1
    val num4 = num2 :+ 9
    val num5 = num1 ++ num2

    println(num3.toBuffer)    // ArrayBuffer(10, 1, 2, 3, 4)
    println(num4.toBuffer)    // ArrayBuffer(5, 6, 7, 8, 9)
    println(num5.toBuffer)    // ArrayBuffer(1, 2, 3, 4, 5, 6, 7, 8)

    println("============== 数组排序 sorted reverse sortWith ==================")
    val sortNums=Array(1,3,5,2,7,8,6,9)
    //升序
    println(sortNums.sorted.toBuffer)           // ArrayBuffer(1, 2, 3, 5, 6, 7, 8, 9)
    //降序
    println(sortNums.sorted.reverse.toBuffer)   // ArrayBuffer(9, 8, 7, 6, 5, 3, 2, 1)
    //降序
    println(sortNums.sortWith(_>_).toBuffer)    // ArrayBuffer(9, 8, 7, 6, 5, 3, 2, 1)
    //升序
    println(sortNums.sortWith(_<_).toBuffer)    // ArrayBuffer(1, 2, 3, 5, 6, 7, 8, 9)
  }
}

 

第5节 多维数组

通过Array的ofDim方法来定义一个多维的数组,多少行,多少列,都是自己说了算。

package com.ch.part03

object MultipleArrayDemo {
  def main(args: Array[String]): Unit = {
    
    //定义一个3行4列的二维数组
    val dim = Array.ofDim[Double](3, 4)
    dim(1)(1) = 2.5
    for (i <- 0 to 2; j <- 0 to 3){
      print(dim(i)(j)+"   ")         // 0.0   0.0   0.0   0.0   
      if(j==3) println()             // 0.0   2.5   0.0   0.0   
    }                                // 0.0   0.0   0.0   0.0   
  }
}

 

第6节 元组及操作

Tuple,元组。Map是键值对的集合。对偶是元组的最简单形态;
元组是不同类型的值的集合,元组中的元素可以是不同的数据类型,元组在Scala中的应用非常广泛。

package com.ch.part03

/**
 * Tuple元组,可以存放不同数据类型的元素
 * 元组的索引从1开始,不是从0开始
 * 元组在Scala中应用非常广泛,在Spark的源码中会见到很多元组。
 * 在Scala中,已经事先定义好了22个Tuple,从Tuple1~~Tuple22
 * 在Tuple22中,最多只能有22个元素
 */
object TupleDemo {
  def main(args: Array[String]): Unit = {

    //定义一个元组
    val tuple = (1, 2.5, "spark", 'a', true)
    val tuple2 = (1, 1.2, "scala", 'b')
    println(tuple == tuple2)    // false

    // 取tuple中的第三个元素
    println(tuple._3)           // spark

    //从元组中接收数据
    // 括号中的接收 tuple2中的元素, 括号外接收整个 tuple2
    val (t1, t2, t3, t4), t5 = tuple2
    val (b1,_,b2,_),b5=tuple2
    println(s"$t1  $t2  $t3  $t4")      // 1  1.2  scala  b
    println(s"$b1  $b2")                // 1  scala
    
    println("============ 遍历元组 方法1 ===============")
    for (t <- tuple.productIterator){
      println(t)
    }
    println("============ 遍历元组 方法2 ===============")
    tuple.productIterator.foreach(println(_))
  }
}

 

 

第四部分 类与对象

第1节 类和无参构造器

在Scala中,类并不用声明为public;
Scala源文件中可以包含多个类,所有这些类都具有公有可见性;
val修饰的变量(常量),值不能改变,只提供getter方法,没有setter方法;
var修饰的变量,值可以改变,对外提供getter、setter方法;
如果没有定义构造器,类会有一个默认的无参构造器;

package com.ch.part04

// 在Scala中,类都有一个无参构造器
class Person {

  // 声明字段必须进行初始化,Scala编译器会根据初始化值的数据类型自动推断字段的类型,字段类型可以省略
  var name = "ch"

  // _ 表示一个占位符,编译器会根据变量的数据类型赋予相应的初始值
  // 注意:使用占位符 _ 进行赋初始值时,数据类型必须指定
  var nickName: String = _
  var numInt: Int = _
  var numDouble: Double = _
  var boolean: Boolean = _

  // val修饰的变量不能使用占位符
  //  val test:Int=_

  val num = 30
  var age = 20

  // 注意: 如果赋值为null,就需要添加数据类型。如果不添加数据类型,那么就会认为是Null类型的。
  var address: String = null

  // 类中的私有字段,有私有的getter和setter方法
  // 可以在类的内部访问,也可以被其伴生对象访问
  private var hobby = "旅游"

  // 对象私有字段,访问权限更小,只能在当前类中访问
  private[this] val cardInfo = "10010"

  //自定义方法
  def hello(message: String): Unit = {

    //只能在当前类中访问cardInfo
    println(s"$message, $cardInfo")
  }

  //自定义方法实现两数求和
  def addNum(num1: Int, num2: Int): Int = {
    num1 + num2
  }
}

object ClassDemo {
  def main(args: Array[String]): Unit = {

    // 下面的创建方式, 实际是使用类的无参构造器来创建对象
    val person = new Person()

    // 创建对象时, 小括号也是可以省略的
    val person2 = new Person
    println(s"${person.nickName}  ${person.numInt}  ${person.numDouble}  ${person.boolean}")  // null  0  0.0  false

    // 给类的属性赋值
    person.age = 50

    // 注意:如果使用对象的属性加上 _= 给var修饰的属性进行赋值,其实就是调用age_=这个settter方法
    person.age_=(20)

    // 调用类的属性,其实就是调用它的getter方法
    println(person.age)   // 20

    // 下面的方法均报错, 即在类外无法调用私有属性
    // println(person.cardInfo)
    // println(person.hobby)

    // 调用类中的方法
    person.hello("hello")
    println(person.addNum(10, 20))

  }
}

 

第2节 自定义getter和setter方法

对于 Scala 类中的每一个属性,编译后会有一个私有的字段和相应的getter、setter方法生成。

//getter方法
println(person age)
//setter方法
person age_= (18)
//getter方法
println(person.age)

 

可以不使用自动生成的方式,自己定义getter和setter方法

package com.ch.part04

class Dog {
  
  private var _leg = 0

  //自定义getter方法 
  def leg: Int = _leg

  //自定义的setter方法
  def leg_=(newLeg: Int): Unit = {
    _leg = newLeg
  }
}

object GetterAndSetterDemo {
  def main(args: Array[String]): Unit = {
    
    val dog = new Dog
    //调用自定义的setter方法
    dog.leg_=(4)
    //调用自定义的getter方法
    println(dog.leg)
  }
}

 

自定义变量的getter和setter方法需要遵循以下原则:

  • 字段属性名以“_”作为前缀,如: _leg
  • getter方法定义为:def leg = _leg
  • setter方法定义为:def leg_=(newLeg: Int)

 

第3节 Bean属性

JavaBean规范把Java属性定义为一堆getter和setter方法。
类似于Java,当将Scala字段标注为 @BeanProperty时,getFoo和setFoo方法会自动生成。
使用@BeanProperty并不会影响Scala自己自动生成的getter和setter方法。
在使用时需要导入包 scala.beans.BeanProperty

package com.ch.part04

//需要导入下面的包
import scala.beans.BeanProperty

class Teacher {

  // 该注解表示生成 java风格的 getter 和 setter方法
  @BeanProperty 
  var name: String = _
}

object BeanDemo {
  def main(args: Array[String]): Unit = {
    
    val teacher=new Teacher
    
    // 下面是scala自带的
    teacher.name="jacky"
    teacher.name_=("tom")
    println(teacher.name)
    
    // 下面是BeanProperty生成的setName方法
    teacher.setName("lisi")
    //BeanProperty生成的getName方法
    println(teacher.getName)
  }
}

上述Teacher类中共生成了四个方法:

1. name: String
2. name_= (newValue: String): Unit
3. getName(): String
4. setName (newValue: String): Unit

 

第4节 构造器

如果没有定义构造器,Scala类中会有一个默认的无参构造器;
Scala当中类的构造器分为两种:主构造器和辅助构造器;
主构造器的定义与类的定义交织在一起,将主构造器的参数直接放在类名之后。
当主构造器的参数不用var或val修饰时,参数会生成类的私有val成员。
Scala中,所有的辅助构造器都必须调用另外一个构造器,另外一个构造器可以是辅助构造器,也可以是主构造器。

package com.ch.part04

//主构造器与类名交织在一起,类名后面的参数就是主构造器的参数
//主构造器直接在类中,其代码不包含在任何方法中
class Animal(name: String, age: Int) {

  //下面三行println代码都属于主构造器的代码
  println(name)
  println(age)
  println("=========================")

  var gender: String = ""

  // 辅助构造器的名字一般都叫 this
  def this(name: String, age: Int, gender: String) {

    //每个辅助构造器必须以主构造器或其他辅助构造器的调用作为第一句代码
    this(name, age)
    this.gender = gender
  }

  var color: String = ""

  def this(name: String, age: Int, gender: String, color: String) {

    //此处调用的是上面的辅助构造器
    this(name, age, gender)
    this.color = color
  }
}

object ConstructorDemo {
  def main(args: Array[String]): Unit = {

    val animal = new Animal("狗蛋", 4)
    val animal2 = new Animal("旺才", 3, "雄性")
    val animal3 = new Animal("小六", 5, "雄性", "黑色")
  }
}

 

第5节 对象

5.1 单例对象

Scala并没有提供Java那样的静态方法或静态字段;
可以采用object关键字实现单例对象,具备和Java静态方法同样的功能;
使用object语法结构【object是Scala中的一个关键字】达到静态方法和静态字段的目的;对象本质上可以拥有类的所有特性,除了不能提供构造器参数;
对于任何在Java中用单例对象的地方,在Scala中都可以用object实现:

  • 作为存放工具函数或常量的地方
  • 高效地共享单个不可变实例
class Session {
  def hello(first: Int): Int = {
    println(first)
    first
  }
}

object SessionFactory {
  val session = new Session

  def getSession(): Session = {
    session
  }

  def main(args: Array[String]): Unit = {

    for (x <- 1 to 10) {
      //通过直接调用,产生的对象都是单例的
      val session = SessionFactory.getSession()
      println(session)
    }
  }
}

 

Scala中的单例对象具有如下特点:
1、创建单例对象不需要使用new关键字
2、object中只有无参构造器
3、主构造代码块只能执行一次,因为它是单例的

package com.ch.part04

object Object {
  println("这是单例对象的代码!")

  def printInfo: Unit = {
    println("Hello Scala Object!")
  }
}

object ObjectDemo {
  def main(args: Array[String]): Unit = {

    // 下面的2行只会执行一行, 因为单例对象的构造只会执行一次
//    val object1=Object    // 这是单例对象的代码!
//    val object2=Object

    // 注掉上面的2句, 下面首次调用, 两句都打印
    Object.printInfo                        // 这是单例对象的代码!    Hello Scala Object!

    // 第二次调用, 只执行调用方法, 不执行构造
    Object.printInfo                        // 这是单例对象的代码!
  }
}

 

5.2 伴生类与伴生对象

单例对象与某个类具有相同的名称时,它被称为这个类的“伴生对象”;
类和它的伴生对象必须存在于同一个文件中,而且可以相互访问私有成员(字段和方法);

package com.ch.part04

//伴生类和伴生对象,它们的名字是一样的,并且必须存在于同一文件中
class ClassObject {

  private var name = "com"

  def printInfo: Unit = {

    //在伴生类中可以访问伴生对象的私有成员
    println(ClassObject.num)
    println("Hello Object!")
  }
}

object ClassObject {

  private val num = 10

  def main(args: Array[String]): Unit = {

    val classObject = new ClassObject

    //在伴生对象中可以访问伴生类的私有成员
    println(classObject.name)
    classObject.printInfo
  }
}

 

5.3 应用程序对象

每个Scala应用程序都必须从一个对象的main方法开始,这个方法的类型为 Array[String] => Unit;
备注:main方法写在class中是没有意义的,在IDEA中这样的 class 连run的图标都不能显示
除了main方法以外,也可以扩展App特质(trait)

object Hello extends App {
  if (args.length > 0)
    println(s"Hello World; args.length = ${args.length}")
  else
    println("Hello World")
}

 

5.4 apply方法

object 中有一个非常重要的特殊方法 -- apply方法;

  • apply方法通常定义在伴生对象中,目的是通过伴生类的构造函数功能,来实现伴生对象的构造函数功能;
  • 通常我们会在类的伴生对象中定义apply方法,当遇到类名(参数1,...参数n)时apply方法会被调用
  • 在创建伴生对象或伴生类的对象时,通常不会使用new class/class() 的方式,而是直接使用class()隐式的调用伴生对象的 apply 方法,这样会让对象创建的更加简洁;
package com.ch.part04

class Student(name: String, age: Int) {

  private var gender: String = _

  def sayHi(): Unit = {
    println(s"大家好,我是$name,$gender 生")
  }
}

object Student {

  //apply方法需要定义在伴生对象中
  def apply(name: String, age: Int): Student = new Student(name, age)

  def main(args: Array[String]): Unit = {

    //直接使用class(参数...)这种方式隐式调用伴生对象中的apply方法来创建class Student对象
    // 实际是, 实例化单例对象Student, 自动调用了其中的apply方法, 并把参数传入, 返回一个Student类的对象
    val student = Student("jacky", 30)
    
    //伴生类与伴生对象可以相互访问私有成员
    student.gender = "男"

    student.sayHi()
  }
}

 

问题:在Scala中实现工厂方法,让子类声明哪种对象应该被创建,保持对象创建在同一位置。例如,假设要创建Animal工厂,让其返回Cat和Dog类的实例,基于这个需求,通过实现Animal伴生对象的apply方法,工厂的使用者可以像这样创建新的Cat和Dog实例。

abstract class Animal {
  def speak
}

class Dog extends Animal {
  override def speak: Unit = {
    println("woof")
  }
}

class Cat extends Animal {
  override def speak: Unit = {
    println("meow")
  }
}

object Animal {
  def apply(str: String): Animal = {
    if (str == "dog")
      new Dog
    else
      new Cat
  }

  def main(args: Array[String]): Unit = {
    val cat = Animal("cat")
    cat.speak
    val dog = Animal("dog")
    dog.speak
  }
}

 

第五部分 继承

第1节 继承的概念

Scala中继承类的方式和Java一样,也是使用extends关键字:

class Employee extends Person{
  var salary=1000
}

 

和Java一样,可在定义中给出子类需要而父类没有的字段和方法,或者重写父类的方法。

package com.ch.part05

class Person(name: String, age: Int){
  println("这是父类Person!")
}

class Student(name: String, age: Int, var stuNo: String) extends Person(name, age){
  println("这是子类Student!")
}

object ExtendsDemo {
  def main(args: Array[String]): Unit = {
    val student = new Student("jacky", 30, "1001")
      
    // 使用var修饰的字段可以在外部访问
    student.stuNo="1002"
    println(student.stuNo)
  }
}

 

上面继承部分的代码等效于下面的Java代码

//Person类
class Person {
  private String name;
  private int age;
  public Person (String name, int age) {
    this.name = name;
    this.age = age;
  }
}

//Student继承Person类
class Student extends Person {
  private String studentNo;
  public Student (string name, int age, String studentNo) {
    super (name, age);
    this.sutdentNo = studentNo;
  }
}

 

第2节 构造器执行顺序

Scala在继承的时候构造器的执行顺序:首先执行父类的主构造器,其次执行子类自身的主构造器。

类有一个主构造器和任意数量的辅助构造器,而每个辅助构造器都必须以对先前定义的辅助构造器或主构造器的调用开始。

子类的辅助构造器最终都会调用主构造器。只有主构造器可以调用父类的构造器。

//Person类
class Person(name: String, age: Int) {
  println("这是父类Person")
}

//Student继承Person类
class Student(name: String, age: Int, studentNo: String) extends Person(name, age) {
  println("这是子类Student")
}

object Demo {
  def main(args: Array[String]): Unit = {
    //下面的语句执行时会打印下列内容:
    //这是父类Person
    //这是子类Student
    //也就是说,构造Student对象之前,首先会调用Person的主构造器
    val student = new Student("john", 18, "1024")
  }
}

 

第3节 override方法重写

方法重写指的是当子类继承父类的时候,从父类继承过来的方法不能满足子类的需要,子类希望有自己的实现,这时需要对父类的方法进行重写,方法重写是实现多态的关键。
Scala中的方法重写同Java一样,也是利用override关键字标识重写父类的方法。

package com.ch.part05

class Programmer(name: String, age: Int) {
  def coding(): Unit = {
    println("我在写代码。。。")
  }
}

class ScalaProgrammer(name: String, age: Int, workNo: String) extends Programmer(name, age) {
  override def coding(): Unit = {
    //调用父类的方法
    super.coding()
    //增加自己实现
    println("我在写Scala代码。。。")
  }
}

object OverrideDemo {
  def main(args: Array[String]): Unit = {
    val scalaProgrammer = new ScalaProgrammer("jacky", 30, "10010")
    scalaProgrammer.coding()  // 我在写代码。。。    我在写Scala代码。。。
  }
}

 

需要强调一点:如果父类是抽象类,则override关键字可以不加。如果继承的父类是抽象类(假设抽象类为AbstractClass,子类为SubClass),在SubClass类中,AbstractClass对应的抽象方法如果没有实现的话,那SubClass也必须定义为抽象类,否则的话必须要有方法的实现。

//抽象的Person类
abstract class Person(name: String, age: Int) {
  def walk(): Unit
}

//Student继承抽象Person类
class Student(name: String, age: Int, var studentNo: String) extends Person(name, age) {
  //重写抽象类中的walk方法,可以不加override关键字
  def walk(): Unit = {
    println("walk like a elegant swan")
  }
}

object Demo {
  def main(args: Array[String]): Unit = {
    val stu = new Student("john", 18, "1024")
    stu.walk()
  }
}

 

第4节 类型检查与转换

要测试某个对象是否属于某个给定的类,可以用isInstanceOf方法。如果测试成功,可以用asInstanceOf方法进行类型转换。

if(p.isInstanceOf[Employee]){
  //s的类型转换为Employee
  val s = p.asInstanceOf[Employee]
}

 

如果p指向的是Employee类及其子类的对象,则p.isInstanceOf[Employee]将会成功。
如果p是null,则p.isInstanceOf[Employee]将返回false,且p.asInstanceOf[Employee]将返回null。
如果p不是一个Employee,则p.asInstanceOf[Employee]将抛出异常。
如果想要测试p指向的是一个Employee对象但又不是其子类,可以用:

if(p.getClass == classOf[Employee])

 

classOf方法定义在scala.Preder对象中,因此会被自动引入。
不过,与类型检查和转换相比,模式匹配通常是更好的选择。

p match{
  //将s作为Employee处理
  case s: Employee => ...
  //p不是Employee的情况
  case _ => ....
}
package com.ch.part05

class Person2

class Student2 extends Person2

object InstanceDemo {
  def main(args: Array[String]): Unit = {
    val p: Person2 = new Student2
    var s: Student2 = null

    //如果对象s是null,那么isInstanceOf会返回false
    println(s.isInstanceOf[Student2])           // false

    if (p.isInstanceOf[Student2]) {
      // 把 p 转换成 Student2类型, 再赋值给 s
      s = p.asInstanceOf[Student2]
    }
    println(s.isInstanceOf[Student2])           // true

    println(p.getClass == classOf[Person2])     // false
    println(p.getClass == classOf[Student2])    // true

    println("=====================================")

    p match {
      case s: Student2 => println("它是Student2类型的对象")    // 它是Student2类型的对象
      case _ => println("它啥也不是!")
    }
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值