7. Scala面向对象编程(中级部分)

7.1 包 

  7.1.1 看一个应用场景 

      现在有两个程序员共同开发一个项目,程序员xiaoming希望定义一个类取名Dog,程序员xiaohong也想定一个类也叫Dog,两个程序员还为此吵了起来,该怎么办?

      --->使用包即可解决这个问题

  7.1.2 回顾-Java包的三大作用 

      1) 区分相同名字的类

      2) 当类很多时,可以很好的管理类

      3) 控制访问范围

  7.1.3 回顾-Java打包命令 

      -打包基本语法

        package com.c;

      -打包的本质分析

        实际上就是创建不同的文件夹来保存类文件

  7.1.4 快速入门 

      使用打包技术来解决上面的问题,不同包下Dog类

public class TestTiger {
    public static void main(String[] args) {
        //使用xm的Tiger
        com.c.scala_exercise.javapackage.xm.Tiger tiger01 = new com.c.scala_exercise.javapackage.xm.Tiger();
        //使用xh的Tiger
        com.c.scala_exercise.javapackage.xh.Tiger tiger02 = new com.c.scala_exercise.javapackage.xh.Tiger();

        System.out.println("tiger01=" + tiger01 + "tiger02=" + tiger02);
    }
}

  7.1.5 Scala包的基本介绍 

      和Java一样,Scala中管理项目可以使用包,但Scala中的包的功能更加强大,使用也相对复杂些

  7.1.6 Scala包快速入门 

      使用打包技术来解决上面的问题,不同包下Dog类

object boke_demo01 {
  def main(args: Array[String]): Unit = {
    //使用xh的Tiger
    val tiger1 = new com.c.scala_exercise.scalapackage.xh.Tiger
    //使用xm的Tiger
    val tiger2 = new com.c.scala_exercise.scalapackage.xm.Tiger

    println(tiger1 + " " + tiger2)

  }
}

  7.1.7 Scala包的特点概述 

      -基本语法

        package 包名

      -Scala包的作用(和Java一样)

        1) 区分相同名字的类

        2) 当类很多时,可以很好的管理类

        3) 控制访问范围

        4) 可以对类的功能进行扩展

      -Scala中包名和源码所在的系统文件目录结构可以不一致,但是编译后的字节码文件路径和包名会保持一致(这个工作由编译器完成)

object boke_demo01 {
  def main(args: Array[String]): Unit = {
    //使用xh的Tiger
    val tiger1 = new com.c.scala_exercise.scalapackage.xh.Tiger
    //使用xm的Tiger
    val tiger2 = new com.c.scala_exercise.scalapackage.xm.Tiger

    println(tiger1 + " " + tiger2)

  }
}

class Employee {

}

  7.1.8 包的命名 

      -命名规则:

        只能包含数字、字母、下划线、小圆点.,但是不能用梳子开头,也不要使用关键字

        demo.class.exercise  //错误,因为class是关键字

        demo.12a  //错误,因为不能以梳子开头

      -命名规范:

        一般是小写字母+小圆点一般是

        com.公司名.项目名.业务模块名 比如:com.baidu.io.model  com.baidu.io.controller

  7.1.9 Scala包注意事项和使用细节 

      1) Scala进行package打包时,可以有如下形式

//代码说明
//1. package com.boke{}  表示我们创建了包 com.boke ,在{}中
//   我们可以继续写它的子包 scala //com.boke.scala, 还可以写类,特质trait,还可以写object
//2. 即Sacla支持,在一个文件中,可以同时创建多个包,以及给各个包创建类,trait和object
package com.boke { //包 com.boke

  //  class User { // 在com.boke包下创建个 User类
  //    def sayHello(): Unit = {
  //      //想使用 com.boke.scala2包下的 Monster
  //      import com.boke.scala2.Monster
  //      val monster = new Monster()
  //    }
  //  }
  //
  //  package scala2 { // 创建包 com.boke.scala2
  //    class User { // 在com.boke.scala2 包下创建个 User类
  //   }
  //
  //    class Monster { //
  //
  //    }
  //
  //  }


  //
  //说明
  //1. 在包中直接写方法,或者定义变量,就错误==>使用包对象的技术来解决
  //2. package object scala表示创建一个包对象scala, 它是com.boke.scala这个包对应的包对象
  //3. 每一个包都可以有一个包对象
  //4. 包对象的名字需要和子包一样
  //5. 在包对象中可以定义变量,方法
  //6. 在包对象中定义的变量和方法,就可以在对应的包中使用
  //7. 在底层这个包对象会生成两个类 package.class  和 package$.class
  package object scala {
    var name = "king"

    def sayHiv(): Unit = {
      println("package object scala sayHI~")
    }
  }


  package scala { //包 com.boke.scala

    class Person { // 表示在 com.boke.scala下创建类 Person
      val name = "Nick"

      def play(message: String): Unit = {
        println(this.name + " " + message)
      }
    }

    class User {
      def testUser(): Unit = {
        println("name = " + name)
        sayHiv()
      }
    }

    object Test1 { //表示在 com.boke.scala 创建object Test1
      def main(args: Array[String]): Unit = {

        println("name=" + name)
        name = "yy"
        sayHiv()

        //        println("ok")
        //        //我们可以直接使用父包的内容
        //        //1.如果有同名的类,则采用就近原则来使用内容(比如包)
        //        //2.如果就是要使用父包的类,则指定路径即可
        //        val user = new User
        //        println("user=" + user) //
        //        val user2 = new com.boke.User()
        //        println("user2" + user2)

      }
    }

  }

}

      2) 包也可以像嵌套类那样嵌套使用(包中有包),好处:可以在同一个文件中,将类(class/object)、trait创建在不同的包中,这样就非常灵活了

      3) 作用域原则:可以直接向上访问。即Scala中子包中直接访问父包的内容,大括号体现作用域。(提示:Java中子包使用父包的类,需要import)。在子包和父包类重名时,默认采用就近原则,如果希望指定使用某个类,则带上包名即可

      4) 父包要访问子包的内容时,需要import对应的类等

      5) 可以在同一个.scala文件中声明多个并列的package(建议嵌套的package不要超过3层)

      6) 包名可以相对也可以绝对,比如访问 BeanProperty 的绝对路径是: _root_.scala.beans.BeanProperty,在一般情况下,我们使用相对路径来引入包,只有当包名冲突时,使用绝对路径来处理

import scala.beans.BeanProperty

class Manager(var name: String) {
  //第一种形式 [使用相对路径引入包]
  @BeanProperty var age: Int = _
  //第二种形式, 和第一种一样,都是相对路径引入
  @scala.beans.BeanProperty var age2: Int = _
  //第三种形式, 是绝对路径引入,可以解决包名冲突
  @_root_.scala.beans.BeanProperty var age3: Int = _
}

object TestBean {
  def main(args: Array[String]): Unit = {
    val m = new Manager("jack")
    println("m=" + m)
  }
}

  7.1.10 包对象 

      基本介绍:包可以包含类、对象和特质(trait),但不能包含函数/方法或变量的定义。这是Java虚拟机的局限。为了弥补这一点,Scala提供了包对象的概念来解决这个问题

  7.1.11 包对象的应用案例

/说明
//1. 在包中直接写方法,或者定义变量,就错误==>使用包对象的技术来解决
//2. package object scala表示创建一个包对象scala, 它是com.boke.scala这个包对应的包对象
//3. 每一个包都可以有一个包对象
//4. 包对象的名字需要和子包一样
//5. 在包对象中可以定义变量,方法
//6. 在包对象中定义的变量和方法,就可以在对应的包中使用
//7. 在底层这个包对象会生成两个类 package.class  和 package$.class
package object scala {
  var name = "king"

  def sayHiv(): Unit = {
    println("package object scala sayHI~")
  }
}


package scala { //包 com.boke.scala

  class Person { // 表示在 com.boke.scala下创建类 Person
    val name = "Nick"

    def play(message: String): Unit = {
      println(this.name + " " + message)
    }
  }

  class User {
    def testUser(): Unit = {
      println("name = " + name)
      sayHiv()
    }
  }

  object Test1 { //表示在 com.boke.scala 创建object Test1
    def main(args: Array[String]): Unit = {

      println("name=" + name)
      name = "yy"
      sayHiv()

    }
  }

}

  7.1.12 包对象的底层的实现机制 

 如图所示:一个包对象会生成两个类package和package$

如图所示:说明了包去使用包对象的变量或者方法的原理

  7.1.13 包对象的注意事项 

      1) 每个包都可以有一个包对象,需要在父包中定义它

      2) 包对象名称需要和包名一致,一般用来对包的功能补充

7.2 包的可见性问题 

  7.2.1 回顾-Java访问修饰符基本介绍 

      Java提供四种访问控制修饰符号来控制方法和变量的访问权限(范围)

        1) 公开级别:用public修饰,对外公开

        2) 受保护级别:用protected修饰,对于子类和同一包中的类公开

        3) 默认级别:没有修饰符,向同一个包中的类公开

        4) 私有级别:用private修饰,只有类本身可以访问,不对外公开

  7.2.2 回顾-Java中4中访问修饰符的访问范围 

  7.2.3 回顾-Java访问修饰符使用注意事项 

      1) 修饰符可以用来修饰类中的属性,成员方法以及类

      2) 只有默认的和public才能修饰类!并且遵循上述访问权限的特点

  7.2.4 Scala中包的可见性介绍 

      在Java中,访问权限分为:public,private,protected和默认。在Scala中,可以通过类似的修饰符达到同样的效果,但是使用上有所区别

      案例演示

object boke_demo01 {

  def main(args: Array[String]): Unit = {
    val c = new Clerk()
    c.showInfo()
    Clerk.test(c)

  }
}

//类
class Clerk {
  var name: String = "jack" //
  private var sal: Double = 9999.9
  protected var age = 23
  var job: String = "大数据工程师"

  def showInfo(): Unit = {
    //在本类可以使用私有的
    println(" name " + name + " sal= " + sal)
  }
}

object Clerk {
  def test(c: Clerk): Unit = {
    //这里体现出在伴生对象中,可以访问c.sal
    println("test() name=" + c.name + " sal= " + c.sal)
  }
}

  7.2.5 Scala中包的可见性和访问修饰符的使用 

      1) 当属性访问权限为默认时,从底层看属性是private的,但是因为提供了xxx_$eq()[类似setter]/xxx()[类似getter]方法,因此从使用效果看是任何地方都可以访问

      2) 当方法访问权限为默认时,默认为public访问权限

      3) private为私有权限,只有在类的内部和伴生对象中可用

      4) protected为受保护权限,Scala中受保护权限比Java中更为严格,只能子类访问,同包无法访问

      5) 在Scala中没有public关键字,即不能用public显示的修饰属性和方法

      6) 包访问权限(表示属性有了限制,同时包也有了限制),这点和Java不一样,体现出Scala包的灵活性

package com.scala.exercise

class Person {

  //增加包访问权限后
  //1.private同时起作用,不仅同类可以使用
  //2.com.scala.exercise中包下的其它类也可以使用
  private[exercise]  val name = "Jack"
  //当然,也可以将可见度延展到上层包
  private[scala] val age = 23
  //说明:private可以变化,比如protected[scala],非常的灵活
}

      7) 整体的案例演示

object boke_demo01 {

  def main(args: Array[String]): Unit = {
    val c = new Clerk()
    c.showInfo()
    Clerk.test(c)

  }
}

//类
class Clerk {
  var name: String = "jack" //
  private var sal: Double = 9999.9
  protected var age = 23
  var job: String = "大数据工程师"

  def showInfo(): Unit = {
    //在本类可以使用私有的
    println(" name " + name + " sal= " + sal)
  }
}

//当一个文件中出现了 class Clerk 和 object Clerk
//1. class Clerk 称为伴生类
//2. object Clerk 的伴生对象
//3. 因为Scala设计者将static拿掉, 他就是设计了 伴生类和伴生对象的概念
//4. 伴生类 写非静态的内容 伴生对象 就是静态内容
//5.
object Clerk {
  def test(c: Clerk): Unit = {
    //这里体现出在伴生对象中,可以访问c.sal
    println("test() name=" + c.name + " sal= " + c.sal)
  }
}

class Person {
  //这里我们增加一个包访问权限
  //下面private[scala] : 1,仍然是private 2. 在scala包(包括子包)下也可以使用name ,相当于扩大访问范围

  protected[scala] val name = "Jack"
}

7.3 包的引入 

  7.3.1 Scala引入包基本介绍 

      Scala引入包也是使用import,基本的原理跟机制和Java一样,但是Scala中的import功能更佳强大,也更灵活。因为Scala语言源自Java,所以java.lang包中的类会自动引入到当前环境中,而Scala中的scala包和predef包的类也会自动引入到当前环境中,即起其下的类可以直接使用。如果想要把其它包中的类引入到当前环境中,需要使用import

  7.3.2 Scala引入包的细节和注意事项 

      1) 在Scala中,import语句可以出现在任何地方,并不仅限于文件顶部,import语句的作用一直延伸到包含该语句的块末尾。这种语法的好处是:在需要时引入包,缩小import包的作用范围,提高效率

class User {
  import scala.beans.BeanProperty 
  @BeanProperty var name : String = ""
}

class Dog {
  @BeanProperty var name : String = "" //可以吗?  No
}

      2) Java中如果想要导入包中所有的类,可以通过通配符*,Scala中采用_(下划线)

      3) 如果不想要某个包中全部的类,而是其中几个类,可以采用选取器(大括号)

def test(): Unit = {
    //可以使用选择器,选择引入包的内容,这里,我们只引入 HashMap, HashSet
    import scala.collection.mutable.{HashMap, HashSet}
    var map = new HashMap()
    var set = new HashSet()
  }

      4) 如果引入的多个包中含有相同的类,那么可以将不需要的类进行重命名进行区分,这个就是重命名

def test2(): Unit = {
    //下面的含义是 将 java.util.HashMap 重命名为 JavaHashMap
    import java.util.{HashMap => JavaHashMap}
    import scala.collection.mutable._
    var map = new HashMap() // 此时的 HashMap 指向的是 scala 中的 HashMap
    var map1 = new JavaHashMap(); // 此时使用的 java 中 hashMap 的别名

}

      5) 如果某个冲突的类根本就不会用到,那么这个类可以直接隐藏掉

 import java.util.{ HashMap=>_, _} // 含义为 引入 java.util 包的所有类,但是忽略 HahsMap 类.
 var map = new HashMap()

7.4 面向对象编程方法-抽象 

      -如何理解抽象

        我们在前面去定义一个类的时候,实际上就是把一类事物的共有的属性和行为提取出来,形成一个物理模型(模版),这种研究问题的方法称为抽象

                        

7.5 面向对象编程三大特征 

  7.5.1 基本介绍 

      面向对象有三大特征:封装、继承、多态

  7.5.2 封装介绍 

      封装(encapsulation)就是把抽象出的数据和对数据的操作封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作(成员变量),才能对数据进行操作

  7.5.3 封装的理解和好处 

      1) 隐藏实现细节

      2) 可以对数据进行验证,保证安全合理

      3) 同时可以加入业务逻辑

  7.5.4 如何体现封装 

      1) 对类中的属性进行封装

      2) 通过成员方法,包实现封装

  7.5.5 封装的实现步骤 

      1) 将属性进行私有化

      2) 提供一个公共的set方法,用于对属性判断并赋值

def setXxx(参数名 : 类型) : Unit = { 
    //加入数据验证的业务逻辑
    属性 = 参数名
}

      3) 提供一个公共的get方法,用于获取属性的值

def getXxx() [: 返回类型] = {
    return 属性 
}

  7.5.6 Scala封装的注意事项的小结 

      1) Scala中为了简化代码的开发,当声明属性var时,本身就自动提供了对应setter/getter方法,如果属性声明为private的,那么自动生成的setter/getter方法也是private的,如果属性省略访问权限修饰符,那么自动生成的setter/getter方法时public的

      2) 因此我们如果只是对一个属性进行简单的set和get,只要声明一下该属性(属性使用默认访问修饰符),不用写专门的set和get,默认会创建,访问时,直接对象.变量。这样也是为了保持访问一致性

      3) 从形式上看 dog.food 直接访问属性,其实底层仍然是访问的方法,看一下反编译的代码就会明白

      4) 有了上面的特性,目前很多新的框架,在进行反射时,也支持对属性的直接反射

7.6 面向对象编程-继承 

  7.6.1 Java继承的简单回顾

class 子类名 extends 父类名 { 类体 }

  7.6.2 继承基本介绍和示意图 

      继承可以解决代码复用,让我们的编程更佳靠近人类的思维,当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类(比如Student),在父类中定义这些相同的属性和方法,所有的子类不需要重复定义这些属性和方法,只需要通过extends语句来声明继承父类即可。和Java一样,Scala也支持类的单继承

  7.6.3 Scala继承的基本语法 

class 子类名 extends 父类名 { 类体 }

  7.6.4 Scala继承快速入门 

object boke_demo01 {

  def main(args: Array[String]): Unit = {
    //使用
    val student = new Student
    student.name = "jack" //调用了student.name()
    student.studying()
    student.showInfo()
  }
}

class Person { //Person类
  var name: String = _
  var age: Int = _

  def showInfo(): Unit = {
    println("学生信息如下:")
    println("名字:" + this.name)
  }
}

//Student类继承Person
class Student extends Person {
  def studying(): Unit = {
    //这里可以使用父类的属性
    println(this.name + "学习 scala中....")
  }
}

  7.6.5 Scala继承给编程带来的便利 

      1) 代码的复用性提高了

      2) 代码的扩展性和维护性提高了

  7.6.6 Scala子类继承了什么,怎么继承了 

      子类继承了所有的属性,只是私有的属性不能直接访问,需要通过公共的方法去访问[debug代码验证可以看到]

//说明
//1. 在scala中,子类继承了父类的所有属性
//2. 但是private的属性和方法无法访问

object boke_demo01 {

  def main(args: Array[String]): Unit = {
    val sub = new Sub()
    sub.sayOk()
    //sub.test200() //编译器不让过.
  }
}

//父类(基类)
class Base {
  var n1: Int = 1 //public n1() , public n1_$eq()
  protected var n2: Int = 2
  private var n3: Int = 3 // private n3() , private n3_$eq()

  def test100(): Unit = { // 默认 public test100()
    println("base 100")
  }

  protected def test200(): Unit = { // public
    println("base 200")
  }

  private def test300(): Unit = { //private
    println("base 300")
  }

  //编译原理->业务逻辑->性能优化
}

//Sub 继承 Base
class Sub extends Base {

  def sayOk(): Unit = {
    this.n1 = 20 //这里访问本质this.n1_$eq()
    this.n2 = 40

    println("范围" + this.n1 + this.n2)

    test100() //
    test200() //在子类中使用protected
  }
}

  7.6.7 Scala重写方法 

      说明:Scala明确规定,重写一个非抽象方法需要用override修饰符,调用超类的方法使用super关键字

object boke_demo01 {

  def main(args: Array[String]): Unit = {
    val emp = new Emp
    emp.printName()
  }
}

//Person类
class Person {
  var name: String = "tom"

  def printName() { //输出名字
    println("Person printName() " + name)
  }

  def sayHi(): Unit = {
    println("sayHi...")
  }
}

//这里我们继承Person
class Emp extends Person {
  //这里需要显式的使用override
  override def printName() {
    println("Emp printName() " + name)
    //在子类中需要去调用父类的方法,使用super
    super.printName()
    sayHi()
  }
}

  7.6.8 Scala中类型检查和转换 

      -基本介绍

        要测试某个对象是否属于某个给定的类,可以用isInstanceOf方法。用asInstanceOf方法将引用转换为子类的引用。classOf获取对象名

        classOf[String]就如同Java的String.class

        obj.isInstanceOf[T]就如同Java的 obj instanceofT 判断obj是不是T类型

        obj.asInstanceOf[T] 就如同Java的(T)obj 将obj强转成T类型

      -案例演示

object boke_demo01 {

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

    //ClassOf的使用,可以得到类名
    println(classOf[String]) // 输出
    val s = "king"
    println(s.getClass.getName) //使用反射机制

    //isInstanceOf asInstanceOf
    var p1 = new Person
    var emp = new Emp
    //将子类引用给父类(向上转型,自动)
    p1 = emp
    //将父类的引用重新转成子类引用(多态),即向下转型
    var emp2 = p1.asInstanceOf[Emp]
    emp2.sayHello()

  }
}

//Person类
class Person {
  var name: String = "tom"

  def printName() { //输出名字
    println("Person printName() " + name)
  }

  def sayHi(): Unit = {
    println("sayHi...")
  }
}

//这里我们继承Person
class Emp extends Person {
  //这里需要显式的使用override
  override def printName() {
    println("Emp printName() " + name)
    //在子类中需要去调用父类的方法,使用super
    super.printName()
    sayHi()
  }

  def sayHello(): Unit = {

  }
}

      -最佳实践

        类型检查和转换的最大价值在于:可以判断传入对象的类型,然后转成对应的子类对象,进行相关操作,这里也体现出多态的特点

  7.6.9 Java中超类的构造  

      说明:从代码可以看出,在Java中,创建子类对象时,子类的构造器总是去调用一个父类的构造器(显示或者隐式调用)

public class JavaBaseConstractor {

    public static void main(String[] args) {

        //1.A()
        //2.B()
        B b = new B();

        //1.A(String name) jack
        //2.B(String name) jack
        B b2 = new B("jack");

    }
}

class A {
    public A() {
        System.out.println("A()");
    }

    public A(String name) {
        System.out.println("A(String name)" + name);
    }
}

class B extends A {
    public B() {
        //这里会隐式调用super(); 就是无参的父类构造器A()
        //super();
        System.out.println("B()");
    }

    public B(String name) {
        super(name);
        System.out.println("B(String name)" + name);
    }
}

  7.6.10 Scala中超类的构造 

      1) 类有一个主构造器和任意数量的辅助构造器,而每个辅助构造器都必须先调用主构造器(也可以是间接调用)

      2) 只有主构造器可以调用父类的构造器,辅助构造器不能直接调用父类的构造器,在Scala的构造器中,不能调用super(params)

      

      3) 案例演示

object boke_demo01 {

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

    //分析执行的顺序
    //1.Person...
    //2.Emp ....
    //3.Emp 辅助构造器~
    val emp1 = new Emp("smith")

    println("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%")
    //Person.. 
    //Emp ....
    val emp2 = new Emp("terry", 10)
    emp2.showInfo() // 雇员的名字 terry

  }
}

//父类Person
class Person(pName: String) {
  var name = pName
  println("Person...")

  def this() {
    this("默认的名字")
    println("默认的名字")

  }
}

//子类Emp继承Person
class Emp(eName: String, eAge: Int) extends Person(eName) {

  println("Emp ....")

  //辅助构造器
  def this(name: String) {

    this(name, 100) // 必须调用主构造器
    this.name = name
    println("Emp 辅助构造器~")
  }

  def showInfo(): Unit = {
    println("雇员的名字 ", name)
  }
}

  7.6.11 覆写字段 

      -基本介绍

        在Scala中,子类改写父类的字段,我们称之为覆写/重写字段。覆写字段需要使用override修饰 

      -回顾

        在Java中只有方法的重写,没有属性/字段的重写,准确的讲,是隐藏字段代替了重写

      -回顾-Java另一重要特性:动态绑定机制

        动态绑定机制:

        1) 如果调用的是方法,则JVM机会将改方法和对象的内存地址绑定

        2) 如果调用的是一个属性,则没有动态绑定机制,在哪里调用就返回对应值

public class JavaDaynamicBind {
    public static void main(String[] args) {

        //将一个子类的对象地址,交给了一个AA(父类的)引用
        //java的动态绑定机制的小结
        //1.如果调用的是方法,则Jvm机会将该方法和对象的内存地址绑定
        //2.如果调用的是一个属性,则没有动态绑定机制,在哪里调用,就返回对应值
        AA obj = new BB();
        System.out.println(obj.sum());  // 30
        System.out.println(obj.sum1()); // 20

    }
}

class AA {
    public int i = 10;

    public int sum() {
        return getI() + 10;
    }

    public int sum1() {
        return i + 10;
    }

    public int getI() {
        return i;
    }
}

class BB extends AA {
    public int i = 20;

    public int getI() {
        return i;
    }

}

       -Scala覆写字段快速入门

object ScalaFiledOverride {

  def main(args: Array[String]): Unit = {
    val obj1: AAA = new AAA
    val obj2: BBB = new BBB
    //obj1.age => obj1.age() //动态绑定机制
    //obj2.age => obj2.age()
    println("obj1.age=" + obj1.age + "\t obj2.age=" + obj2.age)
  }
}

class AAA {
  val age: Int = 10 // 会生成 public age()
}

class BBB extends AAA {
  override val age: Int = 20 // 会生成 public age()
}

       反编译后的代码:

      -覆写字段的注意事项和细节

        1) def只能重写另一个def(即:方法只能重写另一个方法)

        2) val只能重写另一个val属性 或 重写不带参数的def

        -案例演示1(val只能重写另一个val属性)

object ScalaFiledOverride {

  def main(args: Array[String]): Unit = {
    val obj1: AAA = new AAA
    val obj2: BBB = new BBB
    //obj1.age => obj1.age() //动态绑定机制
    //obj2.age => obj2.age()
    println("obj1.age=" + obj1.age + "\t obj2.age=" + obj2.age)
  }
}

//如果 val age 改成 var 报错
class AAA {
  val age: Int = 10 // 会生成 public age()
}

class BBB extends AAA {
  override val age: Int = 20 // 会生成 public age()
}

      -案例演示2(重写不带参数的def)

object boke_demo01 {

  def main(args: Array[String]): Unit = {
    val b1 = new BB()
    println(b1.sal) // 0
    val b2: AA = new BB()
    println("b2.sal=" + b2.sal()) // 0
  }
}

class AA {
  def sal(): Int = {
    return 10
  }
}

class BB extends AA {
  override val sal: Int = 0 //底层 public sal
}

        3) var只能重写另一个抽象的var属性

object boke_demo01 {
  
  def main(args: Array[String]): Unit = {
    println("hello~")
  }
}

//在AA中,有一个抽象的字段(属性)
//1. 抽象的字段(属性):就是没有初始化的字段(属性)
//2. 当一个类含有抽象属性时,则该类需要标记为abstract
//3. 对于抽象的属性,在底层不会生成对应的属性声明,而是生成两个对应的抽象方法(name name_$eq)
abstract class AA {
  var name: String //抽象
  var age: Int = 10
}

class Sub_AA extends AA {
  //说明
  //1. 如果我们在子类中去重写父类的抽象属性,本质是实现了抽象方法
  //2. 因此这里我们可以写override ,也可以不写
  override var name: String = ""

}

      -抽象属性:声明未初始化的变量就是抽象的属性,抽象属性在抽象类中

      -var重写抽象的var属性小结

        1) 一个属性没有初始化,那么这个属性就是抽象属性

        2) 抽象属性在编译成字节码文件时,属性并不会声明,但是会自动生成抽象方法,所以类必须声明为抽象类

        3) 如果是覆写一个父类的抽象,那么override关键字可以省略[原因:父类的抽象属性,生成的是抽象方法,因此不涉及到方法重写的概念,override可以省略]

  7.6.12 抽象类 

      -基本介绍

        在Scala中,通过abstract关键字标记不能被实力化的类。方法不用标记abstract,只要省掉方法体即可,抽象类可以拥有抽象字段,抽象字段/属性就是没有初始值的字段

      -快速入门案例

        将Animal做成抽象类,包含一个抽象方法cry()

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

//抽象类
abstract class Animal {
  var name: String //抽象的字段
  var age: Int // 抽象的字段
  var color: String = "black" //普通属性
  def cry() //抽象方法,不需要标记 abstract
}

  7.6.13 Scala抽象类使用的注意事项和细节 

      1) 抽象类不能被实例

//默认情况下,一个抽象类是不能实例化的,但是你实例化时,动态的实现了抽象类的所有 //抽象方法,也可以,如下
val animal = new Animal {
  override def sayHello (): Unit = {
  println ("say hello~~~~")
  }
}
animal.sayHello ()

      2) 抽象类不一定要包含abstract方法,也就是说,抽象类可以没有abstract方法

abstract class Animal { 
  //在抽象类中可以有实现的方法 
  def sayHi (): Unit = {
  println("hello") }
}

      3) 一旦类包含了抽象方法或者抽象属性,则这个类必须声明为abstract

      4) 抽象方法不能有主体,不允许使用abstract修饰

      5) 如果一个类继承了抽象类,则它必须实现抽象类中所有的抽象方法和抽象属性,除非它自己也声明为abstract类

abstract class Animal {

  def sayHello()
  var food: String
}

class Dog extends Animal {

  override def sayHello(): Unit = {
    println("小狗汪汪叫!")
  }

  override var food: String = _
}

      6) 抽象方法和抽象属性不能使用private、final来修饰,因为这些关键字都是和重写/实现相违背的

      7) 抽象类中可以有实现的方法

      8) 子类重写抽象方法不需要override,写上也不会错

  7.6.14 匿名子类

      -基本介绍

        和Java一样,可以通过包含带有定义或重写的代码块的方式创建一个匿名的子类

      -回顾-Java中匿名子类的使用

public class NoNameDemo01 {
    public static void main(String[] args) {
        //在java中去创建一个匿名子类对象
        A a = new A() {
            @Override
            public void cry() {
                System.out.println("cry...");
            }
        };
        a.cry();
    }
}

abstract class A {
    abstract public void cry();
}

      -Scala匿名子类的使用 

object boke_demo01 {
  
  def main(args: Array[String]): Unit = {
    val monster = new Monster {
      override def cry(): Unit = {
        println("...:)")
      }

      override var name: String = _
    }
    monster.cry()
  }
}

abstract class Monster {
  var name: String

  def cry()
}

  7.6.15 继承层级 

      -Scala继承层级一览图

      -对上图的一个小结

        1) 在Scala中,所有其它类都是AnyRef的子类,类似Java的Obiect

        2) AnyVal和AnyRef都扩展自Any类,Any类是子节点

        3) Any中定义了isInstanceOf、asInstanceOf方法,以及哈希方法等

        4) Null类型的唯一实例就是null对象,可以将null赋值给任何引用,但不能赋值给值类型的变量

        5) Nothing类型没有实例,它对于泛型结构是有用处的,举例:空列表Nil的类型就是List[Nothing],它是List[T]的子类型,T可以是任何类

       

猜你喜欢

转载自www.cnblogs.com/zhanghuicheng/p/10788213.html