zoukankan      html  css  js  c++  java
  • Scala面向对象编程

    [toc]

    ##

    > Scala语言是面向对象的:
    >
    > 1. Java是面向对象的编程语言,由于历史原因,`Java中还存在着非面向对象的内容:基本类型 ,null,静态方法等。`
    > 2. Scala语言来自于Java,所以天生就是面向对象的语言,而且==Scala是纯粹的面向对象的语言,即在Scala中,一切皆为对象。==
    > 3. 在面向对象的学习过程中可以对比着Java语言学习。

    ### 1. 类与对象

    > 1. `类`是抽象的,概念的,代表一类事物,比如人类,猫类等
    > 2. `对象`是具体的,实际的,代表一个具体事物
    > 3. `类`是对象的模板,`对象`是类的一个个体,对应一个实例
    > 4. Scala中类和对象的区别和联系和Java是一样的。

    ~~~~scala
    /**
    * @Date 2021/3/25 17:29
    * @Version 10.21
    * @Author DuanChaojie
    */
    object OopCatDemo {
    def main(args: Array[String]): Unit = {
    val cat = new Cat

    /**
    * 说明
    * 1. cat.name = "小白" 其实不是直接访问属性,而是 cat.name_$eq("小白")
    * 2. cat.name 等价于 cat.name()
    */
    cat.name = "小白"
    cat.age = 10
    cat.color = "白色"

    printf("\n小猫的信息如下: %s %d %s", cat.name, cat.age, cat.color)

    }
    }

    /**
    * 定义一个类Cat
    * 一个class Cat 对应的字节码文件只有一个 Cat.class ,默认是public
    */
    class Cat {
    /**
    * 定义/声明三个属性
    * 说明
    * 1. 当我们声明了 var name :String时, 在底层对应 private name
    * 2. 同时会生成 两个public方法 name() <=类似=> getter public name_$eq() => setter
    */
    var name: String = "" //给初始值
    var age: Int = _ // _ 表示给age 一个默认的值 ,如果Int 默认就是0
    var color: String = _ // _ 给 color 默认值,如果String ,默认是就是null
    }

    /** 反编译后得到的结果如下:
    * public class Cat{
    * private String name = "";
    * private int age;
    * private String color;
    * *
    * public String name(){return this.name;}
    * public void name_$eq(String x$1) { this.name = x$1; }
    * *
    * public int age() { return this.age; }
    * public void age_$eq(int x$1) { this.age = x$1; }
    * *
    * public String color() { return this.color; }
    * public void color_$eq(String x$1) { this.color = x$1; }
    * }
    */
    ~~~~

    > Scala如何定义类:
    >
    > - ~~~~scala
    > [修饰符] class 类名 {
    > 类体
    > }
    > ~~~~
    >
    > Scala定义类的注意事项:
    >
    > 1. Scala语法中,类并不声明为public,所有这些类都具有公有可见性(即默认就是public),[修饰符在后面再详解]。
    > 2. 一个Scala源文件可以包含多个类。

    #### 属性

    > 属性是类的一个组成部分,一般是值数据类型,也可是引用类型。比如我们前面定义猫类 的 age 就是属性。
    >
    > 属性注意事项和细节说明:
    >
    > 1. 属性的定义语法同变量,示例:`[访问修饰符] var 属性名称 [:类型] = 属性值`
    >
    > 2. 属性的定义类型可以为任意类型,包含值类型或引用类型
    >
    > 3. Scala中声明一个属性,必须显示的初始化,然后根据初始化数据的类型自动推断,属性类型可以省略,==这点和Java不同==。
    >
    > 4. 如果赋值为null,则一定要加类型,因为不加类型, 那么该属性的类型就是Null类型。
    >
    > 5. 如果在定义属性时,暂时不赋值,也可以使用符号`_`(下划线),让系统分配默认值。
    >
    > ![image-20210325222129832](assets/image-20210325222129832.png)
    >
    > 6. 不同对象的属性是独立,互不影响,一个对象对属性的更改,不影响另外一个。这点和java完全一样
    >
    > 7. 属性的高级部分:属性的高级部分和构造器(构造方法/函数) 相关,我们把属性高级部分放到构造器那里讲解。

    #### 方法

    > Scala中的方法其实就是函数,声明规则请参考函数式编程中的函数声明。

    ~~~~scala
    def 方法名(参数列表) [:返回值类型] = {
    方法体
    }
    ~~~~

    > 给DogSon类添加cal方法,可以计算两个数的和

    ~~~~scala
    /**
    * @Date 2021/3/25 18:48
    * @Version 10.21
    * @Author DuanChaojie
    */
    object MethodDemo01 {
    def main(args: Array[String]): Unit = {
    val dogSon = new DogSon
    println(dogSon.cal(10,11))
    }
    }

    class DogSon {

    //属性
    private var sal: Double = _
    var food: String = _

    //方法
    def cal(n1: Int, n2: Int): Int = {
    n1 + n2
    }
    }
    ~~~~

    > 方法的调用机制原理:
    >
    > 1. 当我们Scala开始执行时,先在栈区开辟一个main栈。main栈是最后被销毁
    > 2. 当Scala程序在执行到一个方法时,总会开一个新的栈。
    > 3. 每个栈是独立的空间,变量(基本数据类型)是独立的,相互不影响(引用类型除外)
    > 4. 当方法执行完毕后,该方法开辟的栈就会被jvm机回收。
    >
    > 课堂练习题:

    ~~~~scala
    /**
    * @Date 2021/3/25 18:56
    * @Version 10.21
    * @Author DuanChaojie
    */
    object MethodExercise {
    def main(args: Array[String]): Unit = {
    method01()

    val matrix = new Matrix
    matrix.width = 2.2
    matrix.length = 2.51
    val res1 = matrix.method()
    println(res1.formatted("%.2f"))

    method02(4, 2)

    val res2 = method03(4.22, 3.11)
    println(res2.formatted("%.2f"))

    val res3 = method05(9, 3, '/')
    println(res3)
    }


    /**
    * 编程一个方法,方法不需要参数,
    * 在方法中打印一个 10*8 的矩形,在main方法中调用该方法。
    */
    def method01(): Unit = {
    for (i <- 1 to 10) {
    for (j <- 1 to 8) {
    print("*")
    }
    println()
    }
    }

    def method02(m: Int, n: Int): Unit = {
    for (i <- 1 to m) {
    for (j <- 1 to n) {
    print("*")
    }
    println()
    }
    }

    /**
    * 修改上一个程序,编写一个方法,提供m和n两个参数,
    * 方法中打印一个m*n的矩形,再编写一个方法算该矩形的面积(可以接收长length,和宽width),
    * 将其作为方法返回值。在main方法中调用该方法,接收返回的面积值并打印。
    *
    * @param width
    * @param length
    * @return
    */
    def method03( Double, length: Double): Double = {
    width * length
    }

    /**
    * 判断一个数是奇数odd还是偶数
    */
    def method04(n: Int): Unit = {
    if (n % 2 == 0) {
    println("是偶数")
    } else {
    println("是奇数")
    }
    }

    /**
    * 定义小小计算器类(Calcuator),实现加减乘除四个功能
    * 用一个方法搞定
    */
    def method05(m: Int, n: Int, oper: Char): Int = {
    if (oper == '+') {
    m + n
    } else if (oper == '-') {
    m - n
    } else if (oper == '*') {
    m * n
    } else {
    m / n
    }
    }

    }

    class Matrix {
    var length: Double = _
    var Double = _
    /**
    * 修改上一个程序,编写一个方法中,方法不需要参数,计算该矩形的面积,
    * 并将其作为方法返回值。在main方法中调用该方法,接收返回的面积值并打印(结果保留小数点2位)。
    */
    def method(): Double = {
    length * width
    }
    }
    ~~~~

    #### 创建对象

    > ~~~scala
    > val | var 对象名 [:类型] = new 类型()
    > ~~~
    >
    > 1. 如果我们不希望改变对象的引用(即:内存地址),`应该声明为val 性质的,否则声明为var, Scala设计者推荐使用val ,因为一般来说,在程序中,我们只是改变对象属性的值,而不是改变对象的引用。`
    > 2. Scala在声明对象变量时,可以根据创建对象的类型自动推断,所以类型声明可以省略,`但当类型和后面new 对象类型有继承关系即多态时,`就必须写了

    ~~~~scala
    /**
    * @Date 2021/3/25 18:33
    * @Version 10.21
    * @Author DuanChaojie
    */
    object OopCreateObj {
    def main(args: Array[String]): Unit = {
    // emp1类型就是Emp
    val emp1 = new Emp

    // 如果我们希望将子类对象,交给父类的引用,这时就需要写上类型
    val emp2: Person = new Emp
    }
    }


    class Person {

    }

    class Emp extends Person {

    }
    ~~~~

    #### 类与对象应用实例

    > 小狗案例:
    >
    > 1. 编写一个Dog类,包含name(String)、age(Int)、weight(Double)属性。
    > 2. 类中声明一个say方法,返回String类型,方法返回信息中包含所有属性值。
    > 3. 在另一个TestDog类中的main方法中,创建Dog对象,并访问say方法和所有属性,将调用结果打印输出。

    ~~~~scala
    /**
    * @Date 2021/3/25 19:41
    * @Version 10.21
    * @Author DuanChaojie
    */
    object DogCaseTest {
    def main(args: Array[String]): Unit = {
    val dog = new Dog
    dog.name = "tomcat"
    dog.age = 2
    dog.weigth = 6
    println(dog.say())
    }
    }

    /**
    * 小狗案例:
    * 编写一个Dog类,包含name(String)、age(Int)、weight(Double)属性
    * 类中声明一个say方法,返回String类型,方法返回信息中包含所有属性值。
    * 在另一个DogCaseTest类中的main方法中,创建Dog对象,并访问say方法和所有 属性,将调用结果打印输出。
    */
    class Dog {
    var name = ""
    var age = 0
    var weigth = 0.0

    def say(): String = {
    "小狗信息如下: name=" + this.name + "\t age=" + this.age + "\t weight=" + this.weigth
    }
    }
    ~~~~

    #### 类和对象的内存分配策略

    ~~~~scala
    /**
    * @Date 2021/3/25 18:37
    * @Version 10.21
    * @Author DuanChaojie
    */
    object OopMemState {
    def main(args: Array[String]): Unit = {
    val p1 = new PersonTo
    p1.name = "jack"
    p1.age = 10
    var p2 = p1
    println(p1.toString)
    p2.name = "tom"
    println(p1.toString)

    }
    }

    class PersonTo {
    var name = ""
    // 如果是使用 _ 方式给默认值,则属性必须指定类型
    var age: Int = _

    override def toString: String = {
    "name = " + name + "\nage = " + age
    }
    }
    ~~~~

    ![image-20210325223241221](assets/image-20210325223241221.png)

    ### 2. 构造器

    > 1. 前面我们在创建Person的对象时,是先把一个对象创建好后,再给他的年龄和姓名属性赋值,如果现在我要求,在创建人类的对象时,就直接指定这个对象的年龄和姓名,该怎么做? 这时就可以使用构造方法/构造器。
    > 2. `构造器(constructor)又叫构造方法,是类的一种特殊的方法,它的主要作用是完成对新对象的初始化。`

    #### 回顾Java构造器

    ~~~~java
    [修饰符] 方法名(参数列表){
    构造方法体
    }
    ~~~~

    > Java构造器的特点:
    >
    > 1. 在Java中一个类可以定义多个不同的构造方法,构造方法重载
    > 2. 如果程序员没有定义构造方法,系统会自动给类生成一个默认无参构造方法(也叫默认构造器),比如 Person (){}
    > 3. 一旦定义了自己的构造方法(构造器),默认的构造方法就覆盖了,就不能再使用默认的无参构造方法,除非显示的定义一下,即: Person(){};
    >
    > Java构造器的案例:
    >
    > 1. Java构造器的案例 在前面定义的Person类中添加两个构造器:
    > 2. 第一个无参构造器:利用构造器设置所有人的age属性初始值都为18
    > 3. 第二个带name和age两个参数的构造器:使得每次创建Person对象的同时初始化对象的age属性值和name属性值。

    ~~~~java
    class Person{
    public String name;
    public int age;
    public String getInfo(){
    return name+"\t"+age;
    }
    public Person(){
    age = 18;
    }
    public Person(String name,int age){
    this.name = name;
    this.age = age;
    }
    }
    ~~~~

    #### Scala构造器

    > 1. 和Java一样,Scala构造对象也需要调用构造方法,并且可以有任意多个构造方法(即scala中构造器也支持重载)。
    >
    > 2. Scala类的构造器包括: ==主构造器 和 辅助构造器==
    >
    > 3. ~~~~scala
    > class 类名(形参列表) { // 主构造器
    >
    > // 类体
    >
    > def this(形参列表) { // 辅助构造器
    > }
    >
    > def this(形参列表) { //辅助构造器可以有多个...
    > }
    > }
    > ~~~~
    >
    > 4. 辅助构造器 函数的名称this, 可以有多个,编译器通过不同参数来区分。
    >
    > 5. Scala构造器的快速入门
    >
    > - 创建PersonA对象的同时初始化对象的age属性值和name属性值。

    ~~~~scala
    /**
    * @Date 2021/3/25 20:16
    * @Version 10.21
    * @Author DuanChaojie
    */
    object ConstructorDemo01 {
    def main(args: Array[String]): Unit = {
    //val person = new PersonA("tom",99)//不会走主构造器
    val person = new PersonA("tom")
    println(person)

    val a1 = new A
    val a2 = new A()
    }
    }

    /**
    * 构造器的快速入门
    * 创建Person对象的同时初始化对象的age属性值和name属性值
    */
    class PersonA(inName: String, inAge: Int) {
    var name: String = inName
    var age: Int = inAge
    age += 10

    println("age=" + age)

    def this(name: String) {
    //辅助构造器,必须在第一行显式调用主构造器(可以是直接,也可以是间接)
    this("jack", 10)
    println("------------------")
    //this
    this.name = name //重新赋值
    }

    //重写了toString,便于输出对象的信息
    override def toString: String = {
    "name=" + this.name + "\t age" + this.age
    }
    }

    class A() {

    }
    ~~~~

    > Scala构造器注意事项和细节:
    >
    > 1. Scala构造器作用是完成对新对象的初始化,构造器没有返回值。
    > 2. 主构造器的声明直接放置于类名之后 [反编译]。
    > 3. 主构造器会执行类定义中的所有语句,这里可以体会到Scala的函数式编程和面向对象编程融合在一起,即:构造器也是方法(函数),传递参数和使用方法和前面的函数部分内容没有区别【案例演示+反编译】
    > 4. 如果主构造器无参数,小括号可省略,构建对象时调用的构造方法的小括号也可以省略。

    ```scala
    /**
    * @Date 2021/3/25 21:47
    * @Version 10.21
    * @Author DuanChaojie
    */
    object ConstructorDemo02 {
    def main(args: Array[String]): Unit = {
    val a = new AA("jack")
    //输出的顺序是
    //1. BB~~~ 父类
    //2. AA~~~ 主构造器
    //3. AA this(name:String) 辅助构造器
    }
    }

    class BB() {
    println("BB~~~")
    }

    class AA() extends BB() {
    println("AA~~~")

    def this(name: String) {
    this
    println("AA this(name:String)")
    }
    }
    ```

    > 5. ==辅助构造器名称为this(这个和Java是不一样的)==,多个辅助构造器通过不同参数列表进行区分, 在底层就是f构造器重载。

    ```scala
    /**
    * @Date 2021/3/25 21:50
    * @Version 10.21
    * @Author DuanChaojie
    */
    object ConstructorDemo03 {
    def main(args: Array[String]): Unit = {
    val person = new PersonT("jack")
    person.showInfo()
    }
    }

    /**
    * 定义了一个PersonT类
    * PersonT 有几个构造器?四个
    */
    class PersonT private() {
    var name: String = _
    var age: Int = _

    def this(name: String) {
    //辅助构造器无论是直接或间接,最终都一定要调用主构造器,执行主构造器的逻辑
    //而且需要放在辅助构造器的第一行
    // [这点和java一样,java中一个构造器要调用同类的其它构造器,也需要放在第一行]
    this() //直接调用主构造器
    this.name = name
    }

    //辅助构造器
    def this(name: String, age: Int) {
    this() //直接调用主构造器
    this.name = name
    this.age = age
    }

    def this(age: Int) {
    this("匿名") //调用主构造器,因为 def this(name : String) 中调用了主构造器!
    this.age = age
    }

    def showInfo(): Unit = {
    println("PersonT信息如下:")
    println("name=" + this.name)
    println("age=" + this.age)
    }
    }
    ```

    > 6. 如果想让主构造器变成私有的,`可以在()之前加上private`,这样用户只能通过辅助构造器来构造对象了【反编译】
    > 7. 辅助构造器的声明不能和主构造器的声 明一致,会发生错误(即构造器名重复)

    ### 3. 属性高级

    前面我们讲过属性了,这里我们再对属性的内容做一个加强。

    #### 构造器参数

    > 1. Scala类的主构造器的形参未用任何修饰符修饰,那么这个参数是局部变量。
    > 2. 如果参数使用val关键字声明,那么Scala会将参数作为类的私有的只读属性使用 【案例+反编译】
    > 3. 如果参数使用var关键字声明,那么那么Scala会将参数作为类的成员属性使用,`并会提供属性对应的xxx()[类似getter]/xxx_$eq()[类似setter]方法`,即这时的成员属性是私有的,但是可读写。

    ```scala
    /**
    * @Date 2021/3/25 21:54
    * @Version 10.21
    * @Author DuanChaojie
    */
    object ConstructorDemo04 {
    def main(args: Array[String]): Unit = {
    val worker1 = new Worker("smith1")
    //不能访问 inName
    //println(worker1.inName)

    val worker2 = new Worker2("smith2")
    //可以访问 inName
    println(worker2.inName)
    val worker3 = new Worker3("smith3")
    worker3.inName = "mary"

    println(worker3.inName)
    }
    }

    //1. 如果 主构造器是Worker(inName: String) ,那么 inName就是一个局部变量
    class Worker(inName: String) {
    var name = inName
    }

    //2.如果 主构造器是Worker2(val inName: String) ,那么 inName就是Worker2的一个private的只读属性
    class Worker2(val inName: String) {
    var name = inName
    }

    //3.如果 主构造器是Worker3(var inName: String) ,那么 inName就是Worker3的一个
    // 一个private 的可以读写属性
    class Worker3(var inName: String) {
    var name = inName
    }
    ```

    #### Bean属性

    > 1. JavaBeans规范定义了Java的属性是像getXxx()和setXxx()的方法。许多Java工具(框架)都依赖这个命名习惯。为了Java的互操作性。`将Scala字段加@BeanProperty时,这样会自动生成规范的 setXxx/getXxx 方法。这时可以使用 对象.setXxx() 和 对象.getXxx() 来调用属性。`
    > 2. 注意:给某个属性加入@BeanPropetry注解后,会生成getXXX和setXXX的方法
    > 3. `并且对原来底层自动生成类似xxx(),xxx_$eq()方法,没有冲突,二者可以共存。`

    ```scala
    import scala.beans.BeanProperty

    /**
    * @Date 2021/3/25 22:05
    * @Version 10.21
    * @Author DuanChaojie
    */
    object BeanPropertDemo {
    def main(args: Array[String]): Unit = {
    val car = new Car
    car.name = "宝马"
    println(car.name)

    //使用 @BeanProperty 自动生成 getXxx 和 setXxx
    car.setName("奔驰")
    println(car.name)
    println(car.getName())
    }
    }

    class Car {
    @BeanProperty var name: String = null
    }
    ```

    ### 4. Scala对象的创建流程

    ~~~~scala
    class Person {
    var age: Short = 90
    var name: String = _
    def this(n: String, a: Int) {
    this()
    this.name = n
    this.age = a
    }
    }

    //var p : Person = new Person("小倩",17)
    ~~~~

    > 流程分析(面试题-写出)
    >
    > 1. 加载类的信息(属性信息,方法信息)
    > 2. 在内存中(堆)开辟空间
    > 3. 使用父类的构造器(主和辅助)进行初始
    > 4. 使用主构造器对属性进行初始化 【age:90, naem null】
    > 5. 使用辅助构造器对属性进行初始化 【 age:17, naem 小倩 】
    > 6. 将开辟的对象的地址赋给 p 这个引用。

    ### 5. Scala包详解☆

    #### 回顾Java中的包

    > 现在有两个程序员共同开发一个项目,程序员xiaoming希望定义一个类取名 Dog ,程序员xiaoqiang也想定义一个类也叫 Dog。两个程序员为此还吵了起来,怎么办?
    >
    > 使用Java包解决问题:
    >
    > 1. Java包的三大作用
    >
    > 1. 区分相同名字的类
    > 2. 当类很多时,可以很好的管理类
    > 3. 控制访问范围
    >
    > 2. Java打包命令
    >
    > 1. 打包基本语法:package com.atguigu;
    >
    > 3. Java如何引入包
    >
    > 1. 语法: import 包
    >
    > 2. ~~~java
    > import java.awt.*;
    > ~~~
    >
    > 3. 我们引入一个包的主要目的是要使用该包下的类
    >
    > 4. `比如 import java.util.Scanner; 就只是引入一个类Scanner。`
    >
    > 4. Java包的特点
    >
    > 1. java中包名和源码所在的系统文件目录结构要一致,并且编译后的字节码文件路径也和包名保持一致。
    >
    > 5. 打包的本质分析
    >
    > 1. 实际上就是创建不同的文件夹来保存类文件
    > 2. 使用打包技术来解决上面的问题,不同包下Dog类
    >
    > 小红的老虎

    ~~~~java
    package com.atguigu.chapter07.javapackage.xh;

    /**
    * @Date 2021/3/27 17:56
    * @Version 10.21
    * @Author DuanChaojie
    */
    public class Tiger {
    public String name = "xh--Tiger";
    }
    ~~~~

    > 小明的老虎

    ~~~~java
    package com.atguigu.chapter07.javapackage.xm;

    /**
    * @Date 2021/3/27 17:56
    * @Version 10.21
    * @Author DuanChaojie
    */
    public class Tiger {
    public String name = "xm--Tiger";
    }`
    ~~~~

    > 测试

    ~~~java
    package com.atguigu.chapter07.javapackage;

    import com.atguigu.chapter07.javapackage.xh.Tiger;

    /**
    * @Date 2021/3/27 17:57
    * @Version 10.21
    * @Author DuanChaojie
    */
    public class TestTiger {
    public static void main(String[] args) {
    // xh的Tiger
    Tiger tiger1 = new com.atguigu.chapter07.javapackage.xh.Tiger();
    // xm的Tiger
    com.atguigu.chapter07.javapackage.xm.Tiger tiger2 = new com.atguigu.chapter07.javapackage.xm.Tiger();

    //xh--Tiger
    System.out.println(tiger1.name);
    //xm--Tiger
    System.out.println(tiger2.name);
    }
    }
    ~~~

    #### Scala包入门

    > 和Java一样,Scala中管理项目可以使用包,但Scala中的包的功能更加强大,使用也相对复杂些,下面我们学习Scala包的使用和注意事项。
    >
    > Scala包快速入门
    >
    > - 使用打包技术来解决上面的问题,不同包下Dog类
    >
    > 小红的老虎

    ~~~~scala
    package com.atguigu.chapter07.scalapackage.xh

    /**
    * @Date 2021/3/27 18:00
    * @Version 10.21
    * @Author DuanChaojie
    */
    class Tiger {
    val name: String = "xh---Tiger"
    }
    ~~~~

    > 小明的老虎

    ~~~scala
    package com.atguigu.chapter07.scalapackage.xm

    /**
    * @Date 2021/3/27 18:01
    * @Version 10.21
    * @Author DuanChaojie
    */
    class Tiger {
    val name: String = "xm---Tiger"
    }
    ~~~

    > 测试

    ~~~~scala
    package com.atguigu.chapter07.scalapackage
    /**
    * @Date 2021/3/27 18:02
    * @Version 10.21
    * @Author DuanChaojie
    */
    object TestTiger {
    def main(args: Array[String]): Unit = {
    val tiger1 = new com.atguigu.chapter07.scalapackage.xh.Tiger()
    val tiger2 = new com.atguigu.chapter07.javapackage.xm.Tiger()
    //xh---Tiger
    println(tiger1.name)
    //xm--Tiger
    println(tiger2.name)
    }
    }
    ~~~~

    #### Scala包特点☆

    > 1. 基本语法:`package 包名`
    > 2. Scala包的四大作用(和Java一样)
    > 1. 区分相同名字的类
    > 2. 当类很多时,可以很好的管理类
    > 3. 控制访问范围
    > 4. `可以对类的功能进行扩展`
    >
    > 3. ==Scala中包名和源码所在的系统文件目录结构要可以不一致==,但是编译后的字节码文件路径和包名会保持一致(这个工作由编译器完成)。
    >
    > 4. 包的命名:
    >
    > 1. `命名规则:`
    > 1. 只能包含数字、字母、下划线、`小圆点.` 但不能用数字开头, 也不要使用关键字。
    > 1. demo.class.exec1 //错误 , 因为class是关键字
    > 2. demo.12a // 错误,因为不能以数字开头
    > 2. `命名规范:`
    > 1. `一般是小写字母+小圆点一般是`
    > 2. `com.公司名.项目名.业务模块名`,比如:`com.atguigu.oa.controller`
    >
    > 5. Scala会自动引入常用包
    >
    > ![image-20210329154208525](assets/image-20210329154208525.png)

    #### Scala包使用细节

    > 1. scala进行package 打包时,可以有如下形式。
    >
    > 2. 包也可以像嵌套类那样嵌套使用(包中有包), 这个在前面的第三种打包方式已经讲过了,在使用第三种方式时的好处是:程序员可以在同一个文件中,将类(class / object)、trait 创建在不同的包中,这样就非常灵活了。
    >
    > 3. `作用域原则:`可以直接向上访问。即: ==Scala中子包中直接访问父包中的内容, 大括号体现作用域。==(`提示:Java中子包使用父包的类,需要import`)。在子包和父包 类重名时,默认采用就近原则,如果希望指定使用某个类,则带上包名即可。
    >
    > 4. 父包要访问子包的内容时,需要import对应的类等
    >
    > 5. 可以在同一个.scala文件中,声明多个并列的package`(建议嵌套的pakage不要超过3层)`
    >
    > ~~~~scala
    >
    > /** 代码说明
    > * 1. package com.atguigu{} 表示我们创建了包 com.atguigu ,在{}中
    > * 我们可以继续写它的子包 scala //com.atguigu.scala,
    > * 还可以写类,特质trait,还可以写object
    > * 2. 即sacla支持,在一个文件中,可以同时创建多个包,以及给各个包创建类,trait和object
    > */
    > package com.atguigu {
    >
    > import com.atguigu.scala.Person
    > object Test {
    > def main(args: Array[String]): Unit = {
    > // 父包要访问子包的内容时,需要import对应的类等
    > val person = new Person
    > println(person.name)
    > }
    > }
    > /**包对象后面详解
    > *说明
    > * 1. 在包中直接写方法,或者定义变量,就错误==>使用包对象的技术来解决
    > * 2. package object scala 表示创建一个包对象 scala, 他是 com.atguigu.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 {
    > class Person {
    > val name: String = "mm"
    >
    > def eat(): Unit = {
    > println(this.name + "吃吃吃~~~")
    > }
    > }
    >
    > import com.atguigu.java.Catt
    >
    > object Test {
    > def main(args: Array[String]): Unit = {
    > val p = new Person()
    > println(p.eat())
    > println("Object Test")
    > val c = new Catt
    > println(c.name)
    >
    > println(name)//king
    > }
    > }
    >
    > }
    >
    > package java {
    >
    > class Catt {
    > val name = "小甜心"
    > }
    > package test {
    >
    > object Test {
    > def main(args: Array[String]): Unit = {
    > val cat = new Catt
    > println(cat.name)
    > }
    > }
    > }
    > }
    > }
    > ~~~~
    >
    > 6. 包名可以相对也可以绝对,比如,访问BeanProperty的绝对路径是:`_root_. scala.beans.BeanProperty` ,在一般情况下:我们使用相对路径来引入包,只有当包名冲突时,使用绝对路径来处理。
    >
    > ~~~~scala
    > package com.atguigu.chapter07.scalapackage
    >
    > /**
    > * @Date 2021/3/27 18:49
    > * @Version 10.21
    > * @Author DuanChaojie
    > */
    > object TestBean {
    > def main(args: Array[String]): Unit = {
    > val m = new Manager("jack")
    > println("m.age = " + m.age3)
    > }
    > }
    >
    > class Manager(var name: String) {
    >
    > import scala.beans.BeanProperty
    >
    > //第一种形式 [使用相对路径引入包]
    > @BeanProperty var age: Int = _
    > //第二种形式, 和第一种一样,都是相对路径引入
    > @scala.beans.BeanProperty var age2: Int = _
    > //第三种形式, 是绝对路径引入,可以解决包名冲突
    > @_root_.scala.beans.BeanProperty var age3: Int = _
    > }
    > ~~~~

    #### Scala的包对象

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

    ~~~~scala

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

    import com.atguigu.scala.Person
    object Test {
    def main(args: Array[String]): Unit = {
    // 父包要访问子包的内容时,需要import对应的类等
    val person = new Person
    println(person.name)
    }
    }
    /**包对象后面详解
    *说明
    * 1. 在包中直接写方法,或者定义变量,就错误==>使用包对象的技术来解决
    * 2. package object scala 表示创建一个包对象 scala, 他是 com.atguigu.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 {
    class Person {
    val name: String = "mm"
    def eat(): Unit = {
    println(this.name + "吃吃吃~~~")
    }
    }
    }
    }
    ~~~~

    > 包对象的底层实现机制分析:
    >
    > - 一个包对象会生成两个类package和 package$
    > - ![image-20210329160446330](assets/image-20210329160446330.png)
    > - 说明了包去使用包对象的变量或者方法的原理
    > - 当创建包对象后,在该包下生成 `public final class package 和 public final class package$`
    > - 通过 package$ 的一个静态实例完成对包对象中的属性和方法的调用。
    >
    > 包对象的注意事项:
    >
    > - 每个包都可以有一个包对象。你需要在父包中定义它。
    > - 包对象名称需要和包名一致,一般用来对包的功能补充

    #### 包的可见性

    ##### Java

    > java提供四种访问控制修饰符号控制方法和变量的访问权限(范围):
    >
    > 1. 公开级别:用public 修饰,对外公开
    > 2. 受保护级别:用protected修饰,对子类和同一个包中的类公开
    > 3. 默认级别:没有修饰符号,向同一个包的类公开
    > 4. 私有级别:用private修饰,只有类本身可以访问,不对外公开
    >
    > ![image-20210329160938962](assets/image-20210329160938962.png)
    >
    > Java访问修饰符使用注意事项
    >
    > 1. 修饰符可以用来修饰类中的属性,成员方法以及类
    > 2. 只有默认的和public才能修饰类!,并且遵循上述访问权限的特点。

    ##### Scala

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

    ~~~~scala
    package com.atguigu.chapter07.scalavisit

    /**
    * @Date 2021/3/27 19:05
    * @Version 10.21
    * @Author DuanChaojie
    */
    object TestVisit {
    def main(args: Array[String]): Unit = {
    val clerk = new Clerk
    clerk.showInfo()
    Clerk.test(clerk)
    }
    }

    /**
    * 类
    */
    class Clerk {
    var name: String = "jack"
    private var sal: Double = 9999.9
    protected var age = 10
    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. 伴生类 写非静态的内容 伴生对象 就是静态内容
    */
    object Clerk {
    def test(c: Clerk): Unit = {
    //这里体现出在伴生对象中,可以访问c.sal
    println("test() name=" + c.name + " sal= " + c.sal)
    }
    }
    ~~~~

    > Scala中包的可见性和访问修饰符的使用
    >
    > 1. 当属性访问权限为默认时,从底层看属性是private的,`但是因为提供了xxx_$eq()[类似setter]/xxx()[类似getter] 方法`,因此从使用效果看是任何地方都可以访问)
    > 2. 当方法访问权限为默认时,默认为public访问权限
    > 3. `private为私有权限,只在类的内部和伴生对象中可用` 【案例演示】
    > 4. ==protected为受保护权限,scala中受保护权限比Java中更严格,只能子类访问,同包无法访问 (编译器)==
    > 5. 在scala中没有public关键字,即不能用public显式的修饰属性和方法。
    > 6. 包访问权限(表示属性有了限制。同时包也有了限制),这点和Java不一样,体现出Scala包使用的灵活性
    >
    > ~~~scala
    >
    > class Person {
    > /**
    > * 这里我们增加一个包访问权限
    > * 下面private[scalavisit] :
    > * 1,仍然是private
    > * 2. 在scalavisit包(包括子包)下也可以使用name ,相当于扩大访问范围
    > */
    > protected[scalavisit] val name = "jack"
    > }
    > ~~~

    #### 包的引入

    > 1. Scala引入包也是使用import, 基本的原理和机制和Java一样,但是Scala中的import功能更加强大,也更灵活。
    > 2. 因为Scala语言源自于Java,所以`java.lang包中的类会自动引入到当前环境中,而Scala中的scala包和Predef包的类也会自动引入到当前环境中,`即起其下面的类可以直接使用。
    > 3. 如果想要把其他包中的类引入到当前环境中,需要使用import
    >
    > Scala引入包的细节和注意事项:
    >
    > 1. ==在Scala中,import语句可以出现在任何地方==,并不仅限于文件顶部,import语句的作用一直延伸到包含该语句的块末尾。这种语法的好处是:在需要时在引入包,`缩小import 包的作用范围`,提高效率。
    > 2. Java中如果想要导入包中所有的类,`可以通过通配符*,Scala中采用下 _`
    > 3. 如果不想要某个包中全部的类,而是其中的几个类,可以采用选取器(大括号)
    > 4. 如果引入的多个包中含有相同的类,那么可以将不需要的类进行重命名进行区分,这个就是重命名。
    > 5. 如果某个冲突的类根本就不会用到,那么这个类可以直接隐藏掉。

    ~~~scala
    package com.atguigu.chapter07.scalapackage

    import scala.collection.mutable

    /**
    * @Date 2021/3/27 19:48
    * @Version 10.21
    * @Author DuanChaojie
    */
    object TestImport {
    def main(args: Array[String]): Unit = {
    // 可以使用选择器,选择引入包的内容,这里,我们只引入HashMap, HashSet
    import scala.collection.mutable.{HashMap, HashSet}
    var map = new HashMap()
    var set = new HashSet()
    }

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

    }

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

    }
    ~~~

    ### 6. Scala面向对象编程☆

    > 面向对象编程方法—抽象
    >
    > 那么怎么理解抽象呢?
    >
    > 我们在前面去定义一个类时候,实际上就是把一类事物的共有的属性和行为提取出来,形成一个物理模型(模板)。这种研究问题的方法称为抽象。
    >
    > ![image-20210329163201629](assets/image-20210329163201629.png)

    ~~~~scala
    package com.atguigu.chapter07.scalaoop

    /**
    * @Date 2021/3/27 20:00
    * @Version 10.21
    * @Author DuanChaojie
    */
    object BankDemo {
    def main(args: Array[String]): Unit = {
    val acc = new Account("1021", 999999, "1015")
    acc.query("1015")

    val balance = acc.withDraw("1015", 9999)
    println("取出后的余额:" + balance)
    }
    }

    /**
    * 编写一个Account类
    *
    * @param inAccount 主构造器形参
    * @param inBalance 主构造器形参
    * @param inPwd 主构造器形参
    */
    class Account(inAccount: String, inBalance: Double, inPwd: String) {
    /**
    * 属性:账号,余额,密码
    * 方法:查询,取款,存款
    */
    private val accountNo = inAccount
    private var balance = inBalance
    private var pwd = inPwd

    /**
    * 查询账号余额的方法
    *
    * @param pwd 根据密码进行查询余额
    */
    def query(pwd: String): Unit = {
    if (!this.pwd.equals(pwd)) {
    println("密码错误")
    return
    }

    printf("账号为%s 当前余额是%.2f\n", this.accountNo, this.balance)
    }

    /**
    * 取款的方法
    *
    * @param pwd 根据密码取钱
    * @param money 取多少钱
    * @return
    */
    def withDraw(pwd: String, money: Double): Any = {
    if (!this.pwd.equals(pwd)) {
    println("密码错误")
    return
    }
    //判断money是否合理
    if (this.balance < money) {
    println("余额不足")
    return
    }
    this.balance -= money
    this.balance
    }
    }
    ~~~~

    > ==面向对象编程有三大特征:封装、继承和多态==。下面我们一一为同学们进行详细的讲解。

    #### 封装

    > `封装(encapsulation)`就是把抽象出的数据和对数据的操作封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作(成员方法),才能对数据进行操作。
    >
    > 封装的理解和好处:
    >
    > 1. 隐藏实现细节
    > 2. 提可以对数据进行验证,保证安全合理
    > 3. 同时可以加入业务逻辑
    >
    > 如何体现封装:
    >
    > 1. 对类中的属性进行封装
    > 2. 通过成员方法,包实现封装
    >
    > 封装的实现步骤:
    >
    > ~~~~scala
    > //1、将属性进行私有化
    > //2、提供一个公共的set方法,用于对属性判断并赋值
    > def setXxx(参数名 : 类型) : Unit = {
    > //加入数据验证的业务逻辑
    > 属性 = 参数名
    > }
    >
    > //3、提供一个公共的get方法,用于获取属性的值
    > def getXxx() [: 返回类型] = {
    > return 属性
    > }
    > ~~~~
    >
    > 快速入门案例:
    >
    > 那么在Scala中如何实现这种类似的控制呢?请大家看一个小程序(TestEncap.scala),不能随便查看人的年龄,工资等隐私,并对输入的年龄进行合理的验证[要求1-120之间]
    >
    > ~~~~scala
    > class Person {
    > var name: String = _
    > //var age ; //当是public时,可以随意的进行修改,不安全
    > private var age: Int = _
    > private var salary: Float = _
    > private var job: String = _
    >
    > def setAge(age: Int): Unit = {
    > if (age >= 0 && age <= 120) {
    > this.age = age
    > } else {
    > println("输入的数据不合理");
    > //可考虑给一个默认值
    > this.age = 17
    > }
    > }
    > }
    >
    > ~~~~
    >
    > Scala封装的注意事项和细节:
    >
    > 前面讲的Scala的封装特性,大家发现和Java是一样的,下面我们看看Scala封装还有哪些特点。
    >
    > 1. Scala中为了简化代码的开发,当声明属性时,本身就自动提供了对应setter/getter方法,如果属性声明为private的,那么自动生成的setter/getter方法也是private的,如果属性省略访问权限修饰符,那么自动生成的setter/getter方法是public的[案例+反编译+说明]
    >
    > ![image-20210329214929806](assets/image-20210329214929806.png)
    >
    > 2. 因此我们如果只是对一个属性进行简单的set和get ,只要声明一下该属性(属性使用默认访问修饰符) 不用写专门的get/set,默认会创建,访问时,直接对象.变量。这样也是为了保持访问一致性 [案例]
    >
    > 3. 从形式上看 dog.food 直接访问属性,其实底层仍然是访问的方法, 看一下反编译的代码就明白
    >
    > 4. 有了上面的特性,目前很多新的框架,在进行反射时,也支持对属性的直接反射。

    #### 继承

    > Java继承回顾:

    ~~~java
    class 子类名 extends 父类名 { 类体 }
    //子类继承父类的属性和方法
    ~~~

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

    ~~~scala
    //Scala继承的基本语法
    class 子类名 extends 父类名 { 类体 }
    ~~~

    > 编写一个Student 继承 Person的案例,体验一下Scala继承的特点:

    ~~~~scala
    package com.atguigu.chapter07.scalaoop

    /**
    * @Date 2021/3/27 20:18
    * @Version 10.21
    * @Author DuanChaojie
    */
    object Extends01 {
    def main(args: Array[String]): Unit = {
    val student = new Student
    // 调用了student.name() ,是从Person继承过来的
    student.name = "dd"
    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中....")
    }
    }
    ~~~~

    > Scala继承给编程带来的便利
    >
    > 1. 代码的复用性提高了
    > 2. 代码的扩展性和维护性提高了【面试官问:当我们修改父类时,对应的子类就会继承相应的方法和属性】
    >
    > scala子类继承了什么,怎么继承了?
    >
    > 1. 子类继承了所有的属性,只是私有的属性不能直接访问,需要通过公共的方法去访问

    ~~~~scala
    package com.atguigu.chapter07.scalaoop

    /**
    * @Date 2021/3/27 20:36
    * @Version 10.21
    * @Author DuanChaojie
    */
    object Extends02 {
    def main(args: Array[String]): Unit = {
    //1. 在scala中,子类继承了父类的所有属性
    //2. 但是private的属性和方法无法访问
    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
    }
    }
    ~~~~

    ##### 重写方法

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

    ```scala
    package com.atguigu.chapter07.scalaoop

    /**
    * @Date 2021/3/27 20:44
    * @Version 10.21
    * @Author DuanChaojie
    */
    object MethodOverride01 {
    def main(args: Array[String]): Unit = {
    val emp = new Emp100
    emp.printName()
    }
    }

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

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

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

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

    ##### Scala中类型检查和转换

    > 要测试某个对象是否属于某个给定的类,`可以用isInstanceOf方法。用asInstanceOf方法将引用转换为子类的引用。classOf获取对象的类名。`
    >
    > 1. `classOf[String]就如同Java的 String.class 。`
    > 2. `obj.isInstanceOf[T]`就如同`Java的obj instanceof T` 判断obj是不是T类型。
    > 3. `obj.asInstanceOf[T]`就如同`Java的(T)obj` 将obj强转成T类型。

    ```scala
    /**
    * @Date 2021/3/27 20:47
    * @Version 10.21
    * @Author DuanChaojie
    */
    object TypeConvert01 {
    def main(args: Array[String]): Unit = {
    // ClassOf的使用,可以得到类名
    println(classOf[String])

    val str = "king"
    // 使用反射机制
    println(str.getClass.getName)

    // isInstanceOf asInstanceOf
    var person = new Person200
    var emp = new Emp200

    //将子类引用给父类(向上转型,自动)
    person = emp

    //将父类的引用重新转成子类引用(多态),即向下转型
    var emp200 = person.asInstanceOf[Emp200]
    emp200.sayHello()
    }
    }

    /**
    * Person200类
    */
    class Person200 {
    var name: String = "tom"

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

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

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

    def sayHello(): Unit = {
    println("say hello ~~")
    }
    }
    ```

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

    ```scala
    package com.atguigu.chapter07.scalaoop

    /**
    * @Date 2021/3/27 20:55
    * @Version 10.21
    * @Author DuanChaojie
    */
    object TypeConvert02 {
    def main(args: Array[String]): Unit = {
    val stu = new Student400
    val emp = new Emp400
    convert(stu)
    convert(emp)
    }

    /**
    * 写了一个参数多态代码
    * 因为在oop中一个父类的引用可以接收所有子类的引用,多态(参数多态)
    */
    def convert(person: Person400): Unit = {
    //使用Scala中类型检查和转换
    if (person.isInstanceOf[Emp400]) {
    //person.asInstanceOf[Emp400],对p的类型没有任何变化,而是返回的是Emp400
    person.asInstanceOf[Emp400].showInfo()
    } else if (person.isInstanceOf[Student400]) {
    person.asInstanceOf[Student400].cry()
    } else {
    println("转换失败~")
    }
    }
    }

    /**
    * Person400
    */
    class Person400 {
    def printName(): Unit = {
    println("Person400 printName")
    }

    def sayOk(): Unit = {
    println("Person400 sayOk")
    }
    }

    /**
    * Student400
    */
    class Student400 extends Person400 {
    val stuId = 100

    override def printName(): Unit = {
    println("Student400 printName")
    }

    def cry(): Unit = {
    println("学生的id=" + this.stuId)
    }
    }

    /**
    * Emp400
    */
    class Emp400 extends Person400 {
    val empId = 800

    override def printName(): Unit = {
    println("Emp400 printName")
    }

    def showInfo(): Unit = {
    println("雇员的id=" + this.empId)
    }
    }
    ```

    ##### Scala中超类的构造

    回顾-Java中超类的构造

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

    ~~~~java
    package com.atguigu.chapter07.scalaoop;

    /**
    * @Date 2021/3/29 13:14
    * @Version 10.21
    * @Author DuanChaojie
    */
    public class JavaBaseConstractor {
    public static void main(String[] args) {
    //A()
    //B()
    B b = new B();

    //A(String name)mm
    //B(String name)mm
    B mm = new B("mm");

    }
    }

    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);
    }
    }
    ~~~~

    > Scala超类的构造说明:
    >
    > 1. 类有一个主构器和任意数量的辅助构造器,而每个辅助构造器都必须先调用主构造器(也可以是间接调用.),这点在前面我们说过了。
    > 2. 只有主构造器可以调用父类的构造器。辅助构造器不能直接调用父类的构造器。在Scala的构造器中,你不能调用super(params)

    ~~~scala
    /**
    * @Date 2021/3/27 22:27
    * @Version 10.21
    * @Author DuanChaojie
    */
    object ScalaBaseConstrator {
    def main(args: Array[String]): Unit = {
    // Person...
    // Emp ....
    val emp1 = new Emp700("dd",17)

    println("-------------------------------------")
    //Person...
    //Emp ....
    //Emp 辅助构造器~
    val emp2 = new Emp700("mm")
    }
    }

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

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

    }
    }

    /**
    * 子类Emp继承Person
    * @param eName
    * @param eAge
    */
    class Emp700(eName:String,eAge:Int) extends Person700(eName) {

    println("Emp ....")

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

    this(name,100) // 必须调用主构造器
    //this 的话打印以下流程:
    // Person...
    // 默认的名字
    // Emp ....
    // Emp 辅助构造器~
    this.name = name
    println("Emp 辅助构造器~")
    }

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

    ##### 覆写字段

    > 1. 在Scala中,子类改写父类的字段,我们称为覆写/重写字段。覆写字段需使用 override修饰。
    > 2. 回顾:在Java中只有方法的重写,没有属性/字段的重写,准确的讲,是隐藏字段代替了重写。
    > 3. 回顾-Java另一重要特性: 动态绑定机制
    > 1. 如果调用的是方法,则Jvm机会将该方法和对象的内存地址绑定
    > 2. 如果调用的是一个属性,则没有动态绑定机制,在哪里调用,就返回对应值

    ~~~~java
    /**
    * @Date 2021/3/27 22:42
    * @Version 10.21
    * @Author DuanChaojie
    */
    public class JavaDaynamicBind {
    public static void main(String[] args) {
    /**
    * 将一个子类的对象地址,交给了一个AA(父类的)引用
    * java的动态绑定机制的小结
    * 1.如果调用的是方法,则Jvm机会将该方法和对象的内存地址绑定
    * 2.如果调用的是一个属性,则没有动态绑定机制,在哪里调用,就返回对应值
    */
    AA obj = new BB();
    System.out.println(obj.sum()); //40 //? 30
    System.out.println(obj.sum1()); //30 //? 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 sum() {
    // return i + 20;
    // }
    public int getI() {
    return i;
    }
    // public int sum1() {
    // return i + 10;
    // }
    }
    ~~~~

    > Scala覆写字段快速入门:
    >
    > 我们看一个关于覆写字段的案例。

    ~~~~scala
    /**
    * @Date 2021/3/29 13:19
    * @Version 10.21
    * @Author DuanChaojie
    */
    object ScalaFiledOverride {
    def main(args: Array[String]): Unit = {
    val obj1: AAA = new BBB
    val obj2: BBB = new BBB
    println(obj1.age)//20
    println(obj2.age)//20
    }
    }

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

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

    ![image-20210329222804715](assets/image-20210329222804715.png)

    > 覆写字段的注意事项和细节
    >
    > 1. def只能重写另一个def(即:方法只能重写另一个方法)
    > 2. val只能重写另一个val 属性 或 重写不带参数的def
    > 3. var只能重写另一个抽象的var属性

    ```scala
    /**
    * @Date 2021/3/29 13:27
    * @Version 10.21
    * @Author DuanChaojie
    */
    object ScalaFieldOverrideDetail01 {
    def main(args: Array[String]): Unit = {
    val b1 = new BBBBB()
    println(b1.sal) //0

    val b2: AAAAA = new BBBBB()
    println(b2.sal()) //0
    }
    }

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

    class BBBBB extends AAAAA {
    // 重写不带参数的def
    override val sal: Int = 0 //底层 public sal
    }
    ```

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

    ~~~~scala
    /**
    * @Date 2021/3/29 13:29
    * @Version 10.21
    * @Author DuanChaojie
    */
    object ScalaFieldOverrideDetail02 {
    def main(args: Array[String]): Unit = {
    //var 只能重写另一个抽象的var属性
    }
    }

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

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

    > 抽象属性:声明未初始化的变量就是抽象的属性,抽象属性在抽象类
    >
    > var重写抽象的var属性小结:
    >
    > 1. 一个属性没有初始化,那么这个属性就是抽象属性
    > 2. 抽象属性在编译成字节码文件时,属性并不会声明,但是会自动生成抽象方法,所以类必须声明为抽象类
    > 3. 如果是覆写一个父类的抽象属性,那么override 关键字可省略 [原因:父类的抽象属性,生成的是抽象方法,因此就不涉及到方法重写的概念,因此override可省略]

    ##### 抽象类

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

    快速入门案例:我们看看如何把Animal做成抽象类, 包含一个抽象的方法cry()

    ~~~~scala

    /**
    * @Date 2021/3/29 13:49
    * @Version 10.21
    * @Author DuanChaojie
    */
    object AbstractDemo01 {
    def main(args: Array[String]): Unit = {
    println("通过jd-gui查看反编译后的代码")
    }
    }

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

    > 抽象类的价值更多是在于设计,是设计者设计好后,让子类继承并实现抽象类(即:实现抽象类的抽象方法)
    >
    > Scala抽象类使用的注意事项和细节讨论:
    >
    > 1. 抽象类不能被实例
    >
    > ~~~~scala
    > /**
    > * @Date 2021/3/29 13:54
    > * @Version 10.21
    > * @Author DuanChaojie
    > */
    > object AbstractClassDetail01 {
    > def main(args: Array[String]): Unit = {
    > /**
    > * 默认情况下,一个抽象类是不能实例化的,但是你实例化时,
    > * 动态的实现了抽象类的所有抽象方法,也可以
    > */
    > val animal = new Animal03 {
    > override def sayHello(): Unit = {
    > println("say Hello ~")
    > }
    >
    > override var food: String = _
    > }
    > animal.sayHello()
    > }
    > }
    > ~~~~
    >
    > 2. 抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法
    >
    > ~~~~scala
    > abstract class Animal02 {
    > //在抽象类中可以有实现的方法
    > def sayHi(): Unit = {
    > println("xxx")
    > }
    > }
    > ~~~~
    >
    > 3. 一旦类包含了抽象方法或者抽象属性,则这个类必须声明为abstract
    >
    > 4. 抽象方法不能有主体,不允许使用abstract修饰。
    >
    > 5. 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法和抽象属性,除非它自己也声明为abstract类。【案例演示+反编译】
    >
    > ~~~scala
    >
    > abstract class Animal03 {
    > def sayHello()
    >
    > var food: String
    > }
    >
    >
    > class Dog extends Animal03 {
    > override def sayHello(): Unit = {
    > println("小狗汪汪叫!")
    > }
    >
    > override var food: String = ""
    > }
    > ~~~
    >
    > 6. 抽象方法和抽象属性不能使用private、final 来修饰,因为这些关键字都是和重写/实现相违背的。
    >
    > 7. 抽象类中可以有实现的方法。
    >
    > 8. 子类重写抽象方法不需要override,写上也不会错

    ##### 匿名子类

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

    ```java
    /**
    * @Date 2021/3/29 13:59
    * @Version 10.21
    * @Author DuanChaojie
    */
    public class NoNameDemo01 {
    public static void main(String[] args) {
    //在java中去创建一个匿名子类对象
    A2 a2 = new A2() {
    @Override
    public void cry() {
    System.out.println("cry...");
    }
    };
    a2.cry();
    }
    }

    abstract class A2 {
    abstract public void cry();
    }
    ```

    > scala匿名子类案例

    ~~~scala
    /**
    * @Date 2021/3/29 14:07
    * @Version 10.21
    * @Author DuanChaojie
    */
    object ScalaNoNameDemo01 {
    def main(args: Array[String]): Unit = {
    val monster = new Monster {
    override var name: String = _

    override def cry(): Unit = {
    println("妖怪嗷嗷叫...:)")
    }
    }

    monster.cry()
    }
    }

    abstract class Monster {
    var name: String

    def cry()
    }
    ~~~

    ##### 继承层级

    ![image-20210329224450767](assets/image-20210329224450767.png)

    > 继承层级图小结:
    >
    > 1. 在scala中,所有其他类都是AnyRef的子类,类似Java的Object。
    > 2. AnyVal和AnyRef都扩展自Any类。Any类是根节点
    > 3. Any中定义了isInstanceOf、asInstanceOf方法,以及哈希方法等。
    > 4. Null类型的唯一实例就是null对象。可以将null赋值给任何引用,但不能赋值给值类型的变量[案例演示]。
    > 5. Nothing类型没有实例。它对于泛型结构是有用处的,举例:空列表Nil的类型是List[Nothing],它是List[T]的子类型,T可以是任何类。

    #### 多态

    ##### 静态属性和静态方法

    > 有一群小孩在玩堆雪人,不时有新的小孩加入,请问如何知道现在共有多少人在玩?请使用面向对象的思想,编写程序解决。
    >
    > Scala中静态的概念-伴生对象
    >
    > - Scala语言是完全面向对象(万物皆对象)的语言,所以并没有静态的操作(即在Scala中没有静态的概念)。但是为了能够和Java语言交互(因为Java中有静态概念),就产生了一种特殊的对象来模拟类对象,我们称之为类的伴生对象。这个类的所有静态内容都可以放置在它的伴生对象中声明和调用。

    ~~~~scala
    package com.atguigu.chapter08

    /**
    * @Date 2021/3/30 17:13
    * @Version 10.21
    * @Author DuanChaojie
    */
    object AccompanyObject {
    def main(args: Array[String]): Unit = {
    println(ScalaPerson.sex) //true 在底层等价于 ScalaPerson$.MODULE$.sex()
    ScalaPerson.sayHi() //在底层等价于 ScalaPerson$.MODULE$.sayHi()
    }
    }

    /**
    * 说明
    * 1. 当在同一个文件中,有 class ScalaPerson 和 object ScalaPerson
    * 2. class ScalaPerson 称为伴生类,将非静态的内容写到该类中
    * 3. object ScalaPerson 称为伴生对象,将静态的内容写入到该对象(类)
    * 4. class ScalaPerson 编译后底层生成 ScalaPerson类 ScalaPerson.class
    * 5. object ScalaPerson 编译后底层生成 ScalaPerson$类 ScalaPerson$.class
    * 6. 对于伴生对象的内容,我们可以直接通过 ScalaPerson.属性 或者方法
    */

    //伴生类
    class ScalaPerson { //
    var name: String = _
    }

    //伴生对象
    object ScalaPerson { //
    var sex: Boolean = true

    def sayHi(): Unit = {
    println("object ScalaPerson sayHI~~")
    }
    }
    ~~~~

    ![image-20210330215916913](assets/image-20210330215916913.png)

    > 伴生对象的小结:
    >
    > 1. Scala中伴生对象采用object关键字声明,伴生对象中声明的全是 "静态"内容,可以通过伴生对象名称直接调用。
    > 2. 伴生对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致。
    > 3. 伴生对象中的属性和方法都可以通过伴生对象名(类名)直接调用访问
    > 4. 从语法角度来讲,所谓的伴生对象其实就是类的静态方法和成员的集合
    > 5. 从技术角度来讲,scala还是没有生成静态的内容,只不过是将伴生对象生成了一个新的类,实现属性和方法的调用。[反编译看源码]
    > 6. 从底层原理看,伴生对象实现静态特性是依赖于 public static final MODULE$ 实现的。
    > 7. 伴生对象的声明应该和伴生类的声明在同一个源码文件中`(如果不在同一个文件中会运行错误!)`,但是如果没有伴生类,也就没有所谓的伴生对象了,所以放在哪里就无所谓了。
    > 8. 如果 class A 独立存在,那么A就是一个类, 如果 object A 独立存在,那么A就是一个"静态"性质的对象[即类对象], 在 object A中声明的属性和方法可以通过 A.属性 和 A.方法 来实现调用。
    > 9. 当一个文件中,存在伴生类和伴生对象时,文件的图标会发生变化
    >
    > ![image-20210330220150662](assets/image-20210330220150662.png)
    >
    > 伴生对象解决小孩游戏问题
    >
    > 如果,设计一个var total Int表示总人数,我们在创建一个小孩时,就把total加1,并且 total是所有对象共享的就ok了!,我们使用伴生对象来解决!

    ```scala
    /**
    * @Date 2021/3/30 17:21
    * @Version 10.21
    * @Author DuanChaojie
    */
    object ChildJoinGame {
    def main(args: Array[String]): Unit = {

    // 创建三个小孩
    val child0 = new Child("白骨精")
    val child1 = new Child("蜘蛛精")
    val child2 = new Child("黄鼠狼精")

    Child.joinGame(child0)
    Child.joinGame(child1)
    Child.joinGame(child2)

    Child.showNum()
    }
    }


    /**
    * 伴生类,
    *
    * @param cName
    */
    class Child(cName: String) {
    var name = cName
    }

    /**
    * 伴生对象,静态内容
    */
    object Child {

    // 统计共有多少小孩的属性
    var totalChildNum = 0

    def joinGame(child: Child): Unit = {
    printf("%s 小孩加入了游戏\n", child.name)
    // totalChildNum 加1
    totalChildNum += 1
    }

    def showNum(): Unit = {
    printf("当前有%d小孩玩游戏\n", totalChildNum)
    }
    }
    ```

    > 伴生对象 apply 方法
    >
    > - 在伴生对象中定义apply方法,可以实现: 类名(参数) 方式来创建对象实例。

    ~~~~scala
    /**
    * @Date 2021/3/30 17:31
    * @Version 10.21
    * @Author DuanChaojie
    */
    object ApplyDemo01 {
    def main(args: Array[String]): Unit = {
    val pig1 = new Pig("小花")

    // 使用apply方法来创建对象
    val pig2 = Pig("小黑猪") //自动 apply(pName: String)
    val pig3 = Pig() // 自动触发 apply()

    println("pig2.name=" + pig2.name) //小黑猪
    println("pig3.name=" + pig3.name) //匿名猪猪
    }
    }

    /**
    * 伴生类,演示apply方法
    *
    * @param pName
    */
    class Pig(pName: String) {
    var name: String = pName
    }

    object Pig {
    // 编写一个apply方法
    def apply(pName: String): Pig = new Pig(pName)

    def apply(): Pig = new Pig("匿名猪猪")
    }

    ~~~~

    ==Scala单例对象这个部分我们放在scala设计模式专题进行讲解==

    ##### Java接口interface回顾

    声明接口

    - `interface 接口名`

    实现接口

    - `class 类名 implements 接口名1,接口2`

    Java接口的使用小结:

    - 在Java中, 一个类可以实现多个接口。
    - 在Java中,接口之间支持多继承
    - 接口中属性都是常量
    - 接口中的方法都是抽象的

    Scala接口的介绍:

    - 从面向对象来看,接口并不属于面向对象的范畴,Scala是纯面向对象的语言,在Scala中,没有接口。
    - ==Scala语言中,采用特质trait(特征)来代替接口的概念,也就是说,多个类具有相同的特征(特征)时,就可以将这个特质(特征)独立出来,采用关键字trait声明。 理解trait 等价于(interface + abstract class)==
    - ![image-20210330221053920](assets/image-20210330221053920.png)

    ##### 特质trait

    trait 的声明

    ~~~~scala
    // trait 命名 一般首字母大写
    trait 特质名 {
    trait体
    }
    ~~~~

    在scala中,java中的接口可以当做特质使用。Serializable: 就是scala的一个特质。

    ~~~~scala
    /**
    * @Date 2021/3/30 17:41
    * @Version 10.21
    * @Author DuanChaojie
    */
    object TraitDemo01 {
    def main(args: Array[String]): Unit = {

    }
    }
    //trait Serializable extends Any with java.io.Serializable
    //在scala中,java的接口都可以当做trait来使用(如上面的语法)
    trait T1 extends Serializable{

    }

    trait T2 extends Cloneable{

    }
    ~~~~

    > Scala中trait 的使用:
    >
    > 一个类具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素,所以在使用时,也采用了extends关键字,如果有多个特质或存在父类,那么需要采用with关键字连接。

    ~~~~scala
    //没有父类
    class 类名 extends 特质1 with 特质2 with 特质3 ..

    //有父类
    class 类名 extends 父类 with 特质1 with 特质2 with 特质3
    ~~~~

    > 特质的快速入门案例:
    >
    > 1. 可以把特质可以看作是对继承的一种补充
    > 2. Scala的继承是单继承,也就是一个类最多只能有一个父类,这种单继承的机制可保证类的纯洁性,比c++中的多继承机制简洁。但对子类功能的扩展有一定影响。
    > 3. 所以我们认为: Scala引入trait特征 第一可以替代Java的接口, 第二个也是对单继承机制的一种补充
    > 4. ![image-20210330221654786](assets/image-20210330221654786.png)

    ~~~~scala
    /**
    * @Date 2021/3/30 17:44
    * @Version 10.21
    * @Author DuanChaojie
    */
    object TraitDemo02 {
    def main(args: Array[String]): Unit = {
    val c = new C
    val f = new F
    // 连接mysql数据库...
    c.getConnect()
    // 连接oracle数据库..
    f.getConnect()
    }
    }


    /**
    * 按照要求定义一个trait
    */
    trait Trait01 {
    // 定义一个规范
    def getConnect()
    }

    /**
    * 先将六个类的关系写出
    */
    class A {}

    class B extends A {}

    class C extends A with Trait01 {
    override def getConnect(): Unit = {
    println("连接mysql数据库...")
    }
    }

    class D {}

    class E extends D {}

    class F extends D with Trait01 {
    override def getConnect(): Unit = {
    println("连接oracle数据库..")
    }
    }
    ~~~~

    > 特质trait 的再说明:
    >
    > Scala提供了特质(trait),特质可以同时拥有抽象方法和具体方法,一个类可以实现/继承多个特质。【案例演示+反编译】

    ~~~scala
    /**
    * @Date 2021/3/30 17:48
    * @Version 10.21
    * @Author DuanChaojie
    */
    object TraitDemo03 {
    def main(args: Array[String]): Unit = {
    // 创建sheep
    val sheep = new Sheep
    sheep.sayHi()
    sheep.sayHello()
    }
    }

    /**
    * 当一个trait有抽象方法和非抽象方法时:
    * 1. 一个trait在底层对应两个 Trait03.class 接口
    * 2. 还对应 Trait03$class.class Trait03$class抽象类
    */
    trait Trait03 {
    // 抽象方法
    def sayHi()

    // 普通方法
    def sayHello(): Unit = {
    println("say Hello~")
    }
    }

    /**
    * 当trait有接口和抽象类是
    * 1.class Sheep extends Trait03 在底层 对应
    * 2.class Sheep implements Trait03
    * 3.当在 Sheep 类中要使用 Trait03的实现的方法,就通过 Trait03$class
    */
    class Sheep extends Trait03 {
    override def sayHi(): Unit = {
    println("say Hi~")
    }
    }
    ~~~

    ![image-20210330221945017](assets/image-20210330221945017.png)

    > 2. 特质中没有实现的方法就是抽象方法。类通过extends继承特质,通过with可以继承多个特质
    > 3. 所有的java接口都可以当做Scala特质使用 【案例演示+小结】
    >
    > 带有特质的对象,动态混入:
    >
    > 1. 除了可以在类声明时继承特质以外,还可以在构建对象时混入特质,扩展目标类的功能【反编译看动态混入本质】
    > 2. 此种方式也可以应用于对抽象类功能进行扩展
    > 3. ==动态混入是Scala特有的方式(java没有动态混入),可在不修改类声明/定义的情况下,扩展类的功能,非常的灵活,耦合性低 。==
    > 4. 动态混入可以在不影响原有的继承关系的基础上,给指定的类扩展功能。[如何理解]
    > 5. 如果抽象类中有 抽象的方法,如何动态混入特质?

    ~~~~scala
    /**
    * @Date 2021/3/30 18:14
    * @Version 10.21
    * @Author DuanChaojie
    */
    object MixInDemo01 {
    def main(args: Array[String]): Unit = {
    // ocp原则
    // 在不修改 类的定义基础,让他们可以使用trait方法
    val oracleDB = new OracleDB with Operate3
    oracleDB.insert(100)

    val mySQL = new MySQL3 with Operate3
    mySQL.insert(200)

    //如果一个抽象类有抽象方法,如何动态混入特质
    val mySql_ = new MySQL3_ with Operate3 {
    override def say(): Unit = {
    println("say~")
    }
    }

    mySql_.insert(999)
    mySql_.say()
    }
    }

    trait Operate3 { //特质
    def insert(id: Int): Unit = { //方法(实现)
    println("插入数据 = " + id)
    }
    }

    class OracleDB { //空
    }

    abstract class MySQL3 {
    }

    abstract class MySQL3_ {
    def say()
    }
    ~~~~

    > 在Scala中创建对象共有几种方式?
    >
    > 1. new 对象
    > 2. apply 创建
    > 3. 匿名子类方式
    > 4. 动态混入
    >
    > 回顾一下:在Java中创建对象共有几种方式?
    >
    > 1. new对象
    > 2. 反射创建对象
    > 3. 反序列化的方式
    > 4. clone对象的方式
    >
    > 叠加特质
    >
    > 构建对象的同时如果混入多个特质,称之为叠加特质,==那么特质声明顺序从左到右,方法执行顺序从右到左。==
    >
    > 叠加特质应用案例:
    >
    > - `目的:分析叠加特质时,对象的构建顺序,和执行方法的顺序`

    ~~~~scala
    /**
    * @Date 2021/3/30 18:34
    * @Version 10.21
    * @Author DuanChaojie
    */
    object AddTraits {
    def main(args: Array[String]): Unit = {
    /** 创建 MySQL4实例时,动态的混入 DB4 和 File4
    * 第一个问题:初始化流程:
    * 1. Operate4...
    * 2. Data4
    * 3. DB4
    * 4. File4
    * Scala在叠加特质的时候,会首先从后面的特质开始执行(即从左到右)
    */
    val mysql = new MySQL4 with DB4 with File4
    println("------------------------------------------------------")

    /**
    * 当我们执行一个动态混入对象的方法,其执行顺序是:
    * 1. 向文件
    * [2. 向数据库] ,如果File4类中insert方法是这个 super.insert(id) ,则有第二步
    * 3. 插入数据 = 100000
    * 顺序是:
    * (1) 从右到左开始执行
    * (2) 当执行到super时,是指的左边的特质
    * (3) 如果左边没有特质了,则super就是父特质
    */
    mysql.insert(100000)


    println("------------------------------------------------------")
    /**
    * 练习题:
    * 初始化流程:
    * 1. Operate4...
    * 2. Data4
    * 3. File4
    * 4. DB4
    * 方法执行顺序:
    * 1. 向数据库
    * 2. 向文件
    * 3. 插入数据 = 1111111
    */

    val mySQL4 = new MySQL4 with File4 with DB4

    mySQL4.insert(1111111)
    }
    }


    trait Operate4 {
    println("Operate4...")

    // 抽象方法
    def insert(id: Int)
    }

    /**
    * 特质,继承了Operate4
    */
    trait Data4 extends Operate4 {
    println("Data4")

    // 实现/重写 Operate4 的insert
    override def insert(id: Int): Unit = {
    println("插入数据 = " + id)
    }
    }

    /**
    * 特质,继承 Data4
    */
    trait DB4 extends Data4 {
    println("DB4")

    // 重写 Data4 的insert
    override def insert(id: Int): Unit = {
    println("向数据库")
    super.insert(id)
    }
    }

    /**
    * 特质,继承 Data4
    */
    trait File4 extends Data4 {
    println("File4")

    // 重写 Data4 的insert
    override def insert(id: Int): Unit = {
    println("向文件")
    //super.insert(id) //调用了insert方法(难点),这里super在动态混入时,不一定是父类
    //如果我们希望直接调用Data4的insert方法,可以指定,如下
    //说明:super[?] ?的类型,必须是当前的特质的直接父特质(超类)
    super[Data4].insert(id)
    }
    }

    class MySQL4 {} //普通类
    ~~~~

    > 叠加特质注意事项和细节:
    >
    > 1. 特质声明顺序从左到右。
    > 2. Scala在执行叠加对象的方法时,会首先从后面的特质(从右向左)开始执行
    > 3. Scala中特质中如果调用super,并不是表示调用父特质的方法,而是向前面(左边)继续查找特质,如果找不到,才会去父特质查找
    > 4. 如果想要调用具体特质的方法,可以指定:`super[特质].xxx(…).`其中的泛型必须是该特质的直接超类类型
    >
    > 当作富接口使用的特质
    >
    > 富接口:即该特质中既有抽象方法,又有非抽象方法

    ~~~~scala
    trait Operate {
    def insert( id : Int ) //抽象
    def pageQuery(pageno:Int, pagesize:Int): Unit = { //实现
    println("分页查询")
    }
    }
    ~~~~

    > 特质中的具体字段
    >
    > 特质中可以定义具体字段,如果初始化了就是具体字段,如果不初始化就是抽象字段。混入该特质的类就具有了该字段,字段不是继承,而是直接加入类,成为自己的字段。

    ```scala
    /**
    * @Date 2021/3/30 19:18
    * @Version 10.21
    * @Author DuanChaojie
    */
    object MixInPro {
    def main(args: Array[String]): Unit = {
    val mySql = new MySQL6 with DB6 {
    override var sal = 11
    }
    }
    }

    trait DB6 {
    // 抽象字段
    var sal: Int

    var opertype: String = "insert"

    def insert(): Unit = {

    }
    }

    class MySQL6 {}
    ```

    反编译后的代码

    ![QQ截图20210330192853](assets/QQ截图20210330192853.png)

    > 特质中的抽象字段
    >
    > - 特质中未被初始化的字段在具体的子类中必须被重写。
    >
    > 特质构造顺序
    >
    > - 特质也是有构造器的,构造器中的内容由“字段的初始化”和一些其他语句构成。具体实现请参考“特质叠加”
    > - 第一种特质构造顺序(声明类的同时混入特质)
    > - 第二种特质构造顺序(在构建对象时,动态混入特质)
    >
    > 分析两种方式对构造顺序的影响
    >
    > - 第1种方式实际是构建类对象, 在混入特质时,该对象还没有创建。
    > - 第2种方式实际是构造匿名子类,可以理解成在混入特质时,对象已经创建了。

    ~~~~scala
    /**
    * @Date 2021/3/30 19:31
    * @Version 10.21
    * @Author DuanChaojie
    */
    object MixInSeq {
    def main(args: Array[String]): Unit = {
    /**
    * 这时FF是这样 形式 class FF extends EE with CC with DD
    * 调用当前类的超类构造器
    * 第一个特质的父特质构造器
    * 第一个特质构造器
    * 第二个特质构造器的父特质构造器, 如果已经执行过,就不再执行
    * 第二个特质构造器
    * .......重复4,5的步骤(如果有第3个,第4个特质)
    * 当前类构造器
    * E...
    * A...
    * B....
    * C....
    * D....
    * F....
    */
    val ff = new FF
    println("----------------------------")
    /** 这时我们是动态混入
    * 先创建 new KK 对象,然后再混入其它特质
    * 调用当前类的超类构造器
    * 当前类构造器
    * 第一个特质构造器的父特质构造器
    * 第一个特质构造器.
    * 第二个特质构造器的父特质构造器, 如果已经执行过,就不再执行
    * 第二个特质构造器
    * .......重复5,6的步骤(如果有第3个,第4个特质)
    * 当前类构造器 [案例演示]
    * E...
    * K....
    * A...
    * B....
    * C....
    * D....
    */

    val kk = new KK with CC with DD
    }
    }


    trait AA {
    println("A...")
    }

    trait BB extends AA {
    println("B....")
    }

    trait CC extends BB {
    println("C....")
    }

    trait DD extends BB {
    println("D....")
    }

    //普通类
    class EE {
    println("E...")
    }

    /**
    * 先继承了EE类,然后再继承CC 和DD
    */
    class FF extends EE with CC with DD {
    println("F....")
    }

    /**
    * KK直接继承了普通类EE
    */
    class KK extends EE {
    println("K....")
    }

    ~~~~

    > 扩展类的特质
    >
    > 1. 特质可以继承类,以用来拓展该类的一些功能
    > 2. 所有混入该特质的类,会自动成为那个特质所继承的超类的子类
    > 3. 如果混入该特质的类,已经继承了另一个类(A类),则要求A类是特质超类的子类,否则就会出现了多继承现象,发生错误。

    ```scala
    /**
    * @Date 2021/3/30 19:48
    * @Version 10.21
    * @Author DuanChaojie
    */
    object ExtendTraitDemo01 {
    def main(args: Array[String]): Unit = {
    println("-----------------------")
    }
    }

    /**
    * 说明
    * 1. LoggedException 继承了 Exception
    * 2. LoggedException 特质就可以 Exception 功能
    */
    trait LoggedException extends Exception {
    def log(): Unit = {
    // 方法来自于Exception类
    println(getMessage())
    }
    }

    /**
    * 因为 UnhappyException 继承了 LoggedException
    * 而 LoggedException 继承了 Exception
    * UnhappyException 就成为 Exception子类
    */
    class UnhappyException extends LoggedException{
    // 已经是Exception的子类了,所以可以重写方法
    override def getMessage = "错误消息!"
    }

    /**
    * 如果混入该特质的类,已经继承了另一个类(A类),则要求A类是特质超类的子类
    * 否则就会出现了多继承现象,发生错误。
    */
    class UnhappyException2 extends IndexOutOfBoundsException with LoggedException{
    // 已经是Exception的子类了,所以可以重写方法
    override def getMessage = "错误消息!"
    }

    class CCC {}

    /**
    * 错误的原因是 CCC 不是 Exception子类
    */
    //class UnhappyException3 extends CCC with LoggedException{
    // // 已经是Exception的子类了,所以可以重写方法
    // override def getMessage = "错误消息!"
    //}
    ```

    > 自身类型
    >
    > - 自身类型:主要是为了解决特质的循环依赖问题,同时可以确保特质在不扩展某个类的情况下,依然可以做到限制混入该特质的类的类型。
    > - 举例说明自身类型特质,以及如何使用自身类型特质

    ~~~~scala
    /**
    * @Date 2021/3/30 20:04
    * @Version 10.21
    * @Author DuanChaojie
    */
    object SelfTypeDemo {
    def main(args: Array[String]): Unit = {
    println("-------------------------------------- ")
    }
    }

    /**
    * Logger就是自身类型特质,当这里做了自身类型后,那么
    * trait Logger extends Exception,要求混入该特质的类也是 Exception子类
    */
    trait Logger {
    // 明确告诉编译器,我就是Exception,如果没有这句话,下面的getMessage不能调用
    this: Exception =>
    def log(): Unit ={
    // 既然我就是Exception, 那么就可以调用其中的方法
    println(getMessage)
    }
    }

    //class Console extends Logger {} //对吗? 错误
    class Console extends Exception with Logger {}//对吗?
    ~~~~

    ##### 嵌套类

    > 在Scala中,你几乎可以在任何语法结构中内嵌任何语法结构。如在类中可以再定义一个类,这样的类是嵌套类,其他语法结构也是一样。
    >
    > 嵌套类类似于Java中的内部类。
    >
    > 面试题:Java中,类共有五大成员,请说明是哪五大成员
    >
    > 1. 属性
    > 2. 方法
    > 3. 内部类
    > 4. 构造器
    > 5. 代码块
    >
    > Java内部类的简单回顾
    >
    > 在Java中,一个类的内部又完整的嵌套了另一个完整的类结构。被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类。内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系

    ```java
    /**
    * @Date 2021/3/30 20:12
    * @Version 10.21
    * @Author DuanChaojie
    */
    public class JavaInnerClass {
    public static void main(String[] args) {

    //使用
    //创建一个外部类对象
    OuterClass outer1 = new OuterClass();
    //创建一个外部类对象
    OuterClass outer2 = new OuterClass();

    // 创建Java成员内部类
    // 说明在Java中,将成员内部类当做一个属性,因此使用下面的方式来创建 outer1.new InnerClass().
    OuterClass.InnerClass inner1 = outer1.new InnerClass();
    OuterClass.InnerClass inner2 = outer2.new InnerClass();

    //下面的方法调用说明在java中,内部类只和类型相关,也就是说,只要是
    //OuterClass.InnerClass 类型的对象就可以传给 形参 InnerClass ic
    inner1.test(inner2);
    inner1.test(inner1);

    inner2.test(inner1);

    // 创建Java静态内部类
    // 因为在java中静态内部类是和类相关的,使用 new OuterClass.StaticInnerClass()
    OuterClass.StaticInnerClass staticInner = new OuterClass.StaticInnerClass();
    }
    }

    /**
    * 外部类
    */
    class OuterClass {
    class InnerClass { //成员内部类
    //test方法可以接收 InnerClass实例
    public void test(InnerClass ic) {
    System.out.println(ic);
    }
    }

    static class StaticInnerClass { //静态内部类
    }
    }
    ```

    > Scala嵌套类的使用1
    >
    > 请编写程序,定义Scala 的成员内部类和静态内部类,并创建相应的对象实例

    ~~~~scala
    /**
    * @Date 2021/3/30 20:24
    * @Version 10.21
    * @Author DuanChaojie
    */
    object ScalaInnerClassDemo {
    def main(args: Array[String]): Unit = {
    // 创建外部类
    val outer1:ScalaOuterClass1 = new ScalaOuterClass1();
    val outer2:ScalaOuterClass1 = new ScalaOuterClass1();

    // Scala创建内部类的方式和Java不一样,将new 关键字放置在前
    // 使用 对象.内部类 的方式创建
    val inner1 = new outer1.ScalaInnerClass1
    val inner2 = new outer2.ScalaInnerClass1

    // 创建静态内部类对象
    val staticInner = new ScalaOuterClass1.ScalaStaticInnerClass
    println(staticInner)

    }
    }

    /**
    * 伴生对象
    */
    class ScalaOuterClass1{
    // 成员内部类
    class ScalaInnerClass1{

    }
    }

    /**
    * 伴生对象
    */
    object ScalaOuterClass1{
    // 静态内部类
    class ScalaStaticInnerClass{

    }
    }
    ~~~~

    > Scala嵌套类的使用2
    >
    > 请编写程序,在内部类中访问外部类的属性。
    >
    > - 方式1:内部类如果想要访问外部类的属性,可以通过外部类对象访问。即:访问方式:==外部类名.this.属性名==

    ```scala
    /**
    * 外部类
    * 内部类访问外部类的属性的方法1
    * 外部类名.this.属性
    */
    class ScalaOuterClass2 {
    //定义两个属性
    var name = "scoot"
    private var sal = 30000.9

    class ScalaInnerClass { //成员内部类,

    def info() = {
    // 访问方式:外部类名.this.属性名
    // 怎么理解 ScalaOuterClass.this 就相当于是 ScalaOuterClass 这个外部类的一个实例,
    // 然后通过 ScalaOuterClass.this 实例对象去访问 name 属性
    // 只是这种写法比较特别,学习java的同学可能更容易理解 ScalaOuterClass.class 的写法.
    println("name = " + ScalaOuterClass2.this.name
    + " sal =" + ScalaOuterClass2.this.sal)
    }
    }

    }
    ```

    > 方式2:内部类如果想要访问外部类的属性,也可以通过外部类别名访问(推荐)。即:访问方式:外部类名别名.属性名 【外部类名.this 等价 外部类名别名】
    >
    >

    ```scala
    /**
    * 外部类
    * 内部类访问外部类的属性的方法2 使用别名的方式
    * 1. 将外部类属性,写在别名后面
    */
    class ScalaOuterClass3 {
    // 这里我们可以这里理解 外部类的别名 看做是外部类的一个实例
    myouter => class ScalaInnerClass3 { //成员内部类,

    def info() = {
    // 访问方式:外部类别名.属性名
    // 只是这种写法比较特别,学习java的同学可能更容易理解 ScalaOuterClass.class 的写法.
    println("name~ = " + myouter.name
    + " sal~ =" + myouter.sal)
    }
    //这里有一个方法,可以接受ScalaInnerClass实例
    //下面的 ScalaOuterClass#ScalaInnerClass 类型投影的作用就是屏蔽 外部对象对内部类对象的影响
    def test(ic: ScalaOuterClass3#ScalaInnerClass3): Unit = {
    System.out.println("使用了类型投影" + ic)
    }

    }
    //定义两个属性
    var name = "jack"
    private var sal = 800.9
    }

    object ScalaOuterClass3 { //伴生对象
    class ScalaStaticInnerClass3{ //静态内部类
    }
    }
    ```

    > 解决方式-使用类型投影
    >
    > 类型投影是指:在方法声明上,如果使用 `外部类#内部类` 的方式,表示忽略内部类的对象关系,等同于Java中内部类的语法操作,我们将这种方式称之为 类型投影(即:忽略对象的创建方式,只考虑类型)【案例演示】
    >
    > 完整代码:

    ~~~~scala
    /**
    * @Date 2021/3/30 20:24
    * @Version 10.21
    * @Author DuanChaojie
    */
    object ScalaInnerClassDemo {
    def main(args: Array[String]): Unit = {
    // 创建外部类
    val outer1:ScalaOuterClass1 = new ScalaOuterClass1();
    val outer2:ScalaOuterClass1 = new ScalaOuterClass1();

    // Scala创建内部类的方式和Java不一样,将new 关键字放置在前
    // 使用 对象.内部类 的方式创建
    val inner1 = new outer1.ScalaInnerClass1
    val inner2 = new outer2.ScalaInnerClass1

    // 创建静态内部类对象
    val staticInner = new ScalaOuterClass1.ScalaStaticInnerClass
    println(staticInner)

    }
    }

    /**
    * 伴生对象
    */
    class ScalaOuterClass1{
    // 成员内部类
    class ScalaInnerClass1{

    }
    }

    /**
    * 伴生对象
    */
    object ScalaOuterClass1{
    // 静态内部类
    class ScalaStaticInnerClass{

    }
    }


    /**
    * 外部类
    * 内部类访问外部类的属性的方法1
    * 外部类名.this.属性
    */
    class ScalaOuterClass2 {
    //定义两个属性
    var name = "scoot"
    private var sal = 30000.9

    class ScalaInnerClass { //成员内部类,

    def info() = {
    // 访问方式:外部类名.this.属性名
    // 怎么理解 ScalaOuterClass.this 就相当于是 ScalaOuterClass 这个外部类的一个实例,
    // 然后通过 ScalaOuterClass.this 实例对象去访问 name 属性
    // 只是这种写法比较特别,学习java的同学可能更容易理解 ScalaOuterClass.class 的写法.
    println("name = " + ScalaOuterClass2.this.name
    + " sal =" + ScalaOuterClass2.this.sal)
    }
    }

    }


    /**
    * 外部类
    * 内部类访问外部类的属性的方法2 使用别名的方式
    * 1. 将外部类属性,写在别名后面
    */
    class ScalaOuterClass3 {
    // 这里我们可以这里理解 外部类的别名 看做是外部类的一个实例
    myouter => class ScalaInnerClass3 { //成员内部类,

    def info() = {
    // 访问方式:外部类别名.属性名
    // 只是这种写法比较特别,学习java的同学可能更容易理解 ScalaOuterClass.class 的写法.
    println("name~ = " + myouter.name
    + " sal~ =" + myouter.sal)
    }
    //这里有一个方法,可以接受ScalaInnerClass实例
    //下面的 ScalaOuterClass#ScalaInnerClass 类型投影的作用就是屏蔽 外部对象对内部类对象的影响
    def test(ic: ScalaOuterClass3#ScalaInnerClass3): Unit = {
    System.out.println("使用了类型投影" + ic)
    }

    }
    //定义两个属性
    var name = "jack"
    private var sal = 800.9
    }
    object ScalaOuterClass3 { //伴生对象
    class ScalaStaticInnerClass3{ //静态内部类
    }
    }

    ~~~~

    ## ☆

  • 相关阅读:
    自定义瀑布流
    传值 属性 block 单例 协议
    sqlite数据库中 保存和读取UIData对象
    SQL
    关于在Xcode控制台打印的注意点
    synthesize的作用
    iPhone屏幕尺寸/launch尺寸/icon尺寸
    关于TableView上有一段留白的解决方法
    mac显示隐藏文件
    多线程之GCD
  • 原文地址:https://www.cnblogs.com/huaobin/p/15764417.html
Copyright © 2011-2022 走看看