scala学习复习笔记超详细(面向对象)

Scala面向对象编程

Scala语言是面向对象的:

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

1. 类与对象

  1. 是抽象的,概念的,代表一类事物,比如人类,猫类等
  2. 对象是具体的,实际的,代表一个具体事物
  3. 是对象的模板,对象是类的一个个体,对应一个实例
  4. Scala中类和对象的区别和联系和Java是一样的。
/**
  * @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如何定义类:

  • [修饰符] class 类名 {
        类体
    } 
    

Scala定义类的注意事项:

  1. Scala语法中,类并不声明为public,所有这些类都具有公有可见性(即默认就是public),[修饰符在后面再详解]。
  2. 一个Scala源文件可以包含多个类。
属性

属性是类的一个组成部分,一般是值数据类型,也可是引用类型。比如我们前面定义猫类 的 age 就是属性。

属性注意事项和细节说明:

  1. 属性的定义语法同变量,示例:[访问修饰符] var 属性名称 [:类型] = 属性值

  2. 属性的定义类型可以为任意类型,包含值类型或引用类型

  3. Scala中声明一个属性,必须显示的初始化,然后根据初始化数据的类型自动推断,属性类型可以省略,这点和Java不同

  4. 如果赋值为null,则一定要加类型,因为不加类型, 那么该属性的类型就是Null类型。

  5. 如果在定义属性时,暂时不赋值,也可以使用符号_(下划线),让系统分配默认值。

在这里插入图片描述

  1. 不同对象的属性是独立,互不影响,一个对象对属性的更改,不影响另外一个。这点和java完全一样

  2. 属性的高级部分:属性的高级部分和构造器(构造方法/函数) 相关,我们把属性高级部分放到构造器那里讲解。

方法

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

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

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

/**
  * @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机回收。

课堂练习题:

/**
  * @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(width: 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 width: Double = _
  /**
    * 修改上一个程序,编写一个方法中,方法不需要参数,计算该矩形的面积,
    * 并将其作为方法返回值。在main方法中调用该方法,接收返回的面积值并打印(结果保留小数点2位)。
    */
  def method(): Double = {
    length * width
  }
}
创建对象
val | var 对象名 [:类型]  = new 类型()
  1. 如果我们不希望改变对象的引用(即:内存地址),应该声明为val 性质的,否则声明为var, Scala设计者推荐使用val ,因为一般来说,在程序中,我们只是改变对象属性的值,而不是改变对象的引用。
  2. Scala在声明对象变量时,可以根据创建对象的类型自动推断,所以类型声明可以省略,但当类型和后面new 对象类型有继承关系即多态时,就必须写了
/**
  * @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方法和所有属性,将调用结果打印输出。
/**
  * @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
  }
}
类和对象的内存分配策略
/**
  * @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
  }
}

在这里插入图片描述

2. 构造器

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

Java构造器的特点:

  1. 在Java中一个类可以定义多个不同的构造方法,构造方法重载
  2. 如果程序员没有定义构造方法,系统会自动给类生成一个默认无参构造方法(也叫默认构造器),比如 Person (){}
  3. 一旦定义了自己的构造方法(构造器),默认的构造方法就覆盖了,就不能再使用默认的无参构造方法,除非显示的定义一下,即: Person(){};

Java构造器的案例:

  1. Java构造器的案例在前面定义的Person类中添加两个构造器:
  2. 第一个无参构造器:利用构造器设置所有人的age属性初始值都为18
  3. 第二个带name和age两个参数的构造器:使得每次创建Person对象的同时初始化对象的age属性值和name属性值。
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. class 类名(形参列表) {  // 主构造器
        
       // 类体
        
       def  this(形参列表) {  // 辅助构造器
       }
        
       def  this(形参列表) {  //辅助构造器可以有多个...
       }
    } 
    
  4. 辅助构造器 函数的名称this, 可以有多个,编译器通过不同参数来区分。

  5. Scala构造器的快速入门

    • 创建PersonA对象的同时初始化对象的age属性值和name属性值。
/**
  * @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. 如果主构造器无参数,小括号可省略,构建对象时调用的构造方法的小括号也可以省略。
/**
  * @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)")
  }
}
  1. 辅助构造器名称为this(这个和Java是不一样的),多个辅助构造器通过不同参数列表进行区分, 在底层就是f构造器重载。
/**
  * @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)
  }
}
  1. 如果想让主构造器变成私有的,可以在()之前加上private,这样用户只能通过辅助构造器来构造对象了【反编译】
  2. 辅助构造器的声明不能和主构造器的声明一致,会发生错误(即构造器名重复)

3. 属性高级

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

构造器参数
  1. Scala类的主构造器的形参未用任何修饰符修饰,那么这个参数是局部变量。
  2. 如果参数使用val关键字声明,那么Scala会将参数作为类的私有的只读属性使用 【案例+反编译】
  3. 如果参数使用var关键字声明,那么那么Scala会将参数作为类的成员属性使用,并会提供属性对应的xxx()[类似getter]/xxx_$eq()[类似setter]方法,即这时的成员属性是私有的,但是可读写。
/**
  * @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()方法,没有冲突,二者可以共存。
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对象的创建流程

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. import java.awt.*;
      
    3. 我们引入一个包的主要目的是要使用该包下的类

    4. 比如 import java.util.Scanner; 就只是引入一个类Scanner。

  4. Java包的特点

    1. java中包名和源码所在的系统文件目录结构要一致,并且编译后的字节码文件路径也和包名保持一致。
  5. 打包的本质分析

    1. 实际上就是创建不同的文件夹来保存类文件
    2. 使用打包技术来解决上面的问题,不同包下Dog类

小红的老虎

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";
}

小明的老虎

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";
}`

测试

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类

小红的老虎

package com.atguigu.chapter07.scalapackage.xh

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

小明的老虎

package com.atguigu.chapter07.scalapackage.xm

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

测试

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会自动引入常用包

在这里插入图片描述

Scala包使用细节
  1. scala进行package 打包时,可以有如下形式。

  2. 包也可以像嵌套类那样嵌套使用(包中有包), 这个在前面的第三种打包方式已经讲过了,在使用第三种方式时的好处是:程序员可以在同一个文件中,将类(class / object)、trait 创建在不同的包中,这样就非常灵活了。

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

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

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

    
    /** 代码说明
      * 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 ,在一般情况下:我们使用相对路径来引入包,只有当包名冲突时,使用绝对路径来处理。

    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提供了包对象的概念来解决这个问题。


/** 代码说明
  * 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$
  • 在这里插入图片描述
    • 说明了包去使用包对象的变量或者方法的原理
    • 当创建包对象后,在该包下生成 public final class package 和 public final class package$
    • 通过 package$ 的一个静态实例完成对包对象中的属性和方法的调用。

包对象的注意事项:

  • 每个包都可以有一个包对象。你需要在父包中定义它。
  • 包对象名称需要和包名一致,一般用来对包的功能补充
包的可见性
Java

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

  1. 公开级别:用public 修饰,对外公开
  2. 受保护级别:用protected修饰,对子类和同一个包中的类公开
  3. 默认级别:没有修饰符号,向同一个包的类公开
  4. 私有级别:用private修饰,只有类本身可以访问,不对外公开

在这里插入图片描述

Java访问修饰符使用注意事项

  1. 修饰符可以用来修饰类中的属性,成员方法以及类
  2. 只有默认的和public才能修饰类!,并且遵循上述访问权限的特点。
Scala

在Java中,访问权限分为: public,private,protected和默认。在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包使用的灵活性

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. 如果某个冲突的类根本就不会用到,那么这个类可以直接隐藏掉。
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面向对象编程☆

面向对象编程方法—抽象

那么怎么理解抽象呢?

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

在这里插入图片描述

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. 通过成员方法,包实现封装

封装的实现步骤:

//1、将属性进行私有化
//2、提供一个公共的set方法,用于对属性判断并赋值
def  setXxx(参数名 : 类型) : Unit = {	
    //加入数据验证的业务逻辑	
    属性 = 参数名   
}

//3、提供一个公共的get方法,用于获取属性的值
def getXxx() [: 返回类型] = {
	return 属性
}

快速入门案例:

那么在Scala中如何实现这种类似的控制呢?请大家看一个小程序(TestEncap.scala),不能随便查看人的年龄,工资等隐私,并对输入的年龄进行合理的验证[要求1-120之间]

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的[案例+反编译+说明]

在这里插入图片描述

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

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

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

继承

Java继承回顾:

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

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

和Java一样,Scala也支持类的单继承

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

编写一个Student 继承 Person的案例,体验一下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. 子类继承了所有的属性,只是私有的属性不能直接访问,需要通过公共的方法去访问
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关键字

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类型。
/**
  * @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 ~~")
  }
}

最佳实践:

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

里也体现出多态的特点。

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中,创建子类对象时,子类的构造器总是去调用一个父类的构造器(显式或者隐式调用)。

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)
/**
  * @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. 如果调用的是一个属性,则没有动态绑定机制,在哪里调用,就返回对应值
/**
 * @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覆写字段快速入门:

我们看一个关于覆写字段的案例。

/**
  * @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()
}

在这里插入图片描述

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

  1. def只能重写另一个def(即:方法只能重写另一个方法)
  2. val只能重写另一个val 属性 或 重写不带参数的def
  3. var只能重写另一个抽象的var属性
/**
  * @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属性

/**
  * @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()


/**
  * @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. 抽象类不能被实例

    /**
      * @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方法

    abstract class Animal02 {
      //在抽象类中可以有实现的方法
      def sayHi(): Unit = {
        println("xxx")
      }
    }
    
  3. 一旦类包含了抽象方法或者抽象属性,则这个类必须声明为abstract

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

  5. 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法和抽象属性,除非它自己也声明为abstract类。【案例演示+反编译】

    
    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一样,可以通过包含带有定义或重写的代码块的方式创建一个匿名的子类

/**
 * @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匿名子类案例

/**
  * @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()
}
继承层级

在这里插入图片描述

继承层级图小结:

  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中有静态概念),就产生了一种特殊的对象来模拟类对象,我们称之为类的伴生对象。这个类的所有静态内容都可以放置在它的伴生对象中声明和调用。
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~~")
  }
}

在这里插入图片描述

伴生对象的小结:

  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. 当一个文件中,存在伴生类和伴生对象时,文件的图标会发生变化

在这里插入图片描述

伴生对象解决小孩游戏问题

如果,设计一个var total Int表示总人数,我们在创建一个小孩时,就把total加1,并且 total是所有对象共享的就ok了!,我们使用伴生对象来解决!

/**
  * @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方法,可以实现: 类名(参数) 方式来创建对象实例。
/**
  * @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)
    -在这里插入图片描述
特质trait

trait 的声明

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

在scala中,java中的接口可以当做特质使用。Serializable: 就是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关键字连接。

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

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

特质的快速入门案例:

  1. 可以把特质可以看作是对继承的一种补充
  2. Scala的继承是单继承,也就是一个类最多只能有一个父类,这种单继承的机制可保证类的纯洁性,比c++中的多继承机制简洁。但对子类功能的扩展有一定影响。
  3. 所以我们认为: Scala引入trait特征 第一可以替代Java的接口, 第二个也是对单继承机制的一种补充
  4. 在这里插入图片描述
/**
  * @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),特质可以同时拥有抽象方法和具体方法,一个类可以实现/继承多个特质。【案例演示+反编译】

/**
  * @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~")
  }
}

在这里插入图片描述

  1. 特质中没有实现的方法就是抽象方法。类通过extends继承特质,通过with可以继承多个特质
  2. 所有的java接口都可以当做Scala特质使用 【案例演示+小结】

带有特质的对象,动态混入:

  1. 除了可以在类声明时继承特质以外,还可以在构建对象时混入特质,扩展目标类的功能【反编译看动态混入本质】
  2. 此种方式也可以应用于对抽象类功能进行扩展
  3. 动态混入是Scala特有的方式(java没有动态混入),可在不修改类声明/定义的情况下,扩展类的功能,非常的灵活,耦合性低 。
  4. 动态混入可以在不影响原有的继承关系的基础上,给指定的类扩展功能。[如何理解]
  5. 如果抽象类中有 抽象的方法,如何动态混入特质?
/**
  * @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对象的方式

叠加特质

构建对象的同时如果混入多个特质,称之为叠加特质,那么特质声明顺序从左到右,方法执行顺序从右到左。

叠加特质应用案例:

  • 目的:分析叠加特质时,对象的构建顺序,和执行方法的顺序
/**
  * @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(…).其中的泛型必须是该特质的直接超类类型

当作富接口使用的特质

富接口:即该特质中既有抽象方法,又有非抽象方法

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

特质中的具体字段

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

/**
  * @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 {}

反编译后的代码
在这里插入图片描述

特质中的抽象字段

  • 特质中未被初始化的字段在具体的子类中必须被重写。

特质构造顺序

  • 特质也是有构造器的,构造器中的内容由“字段的初始化”和一些其他语句构成。具体实现请参考“特质叠加”
  • 第一种特质构造顺序(声明类的同时混入特质)
  • 第二种特质构造顺序(在构建对象时,动态混入特质)

分析两种方式对构造顺序的影响

  • 第1种方式实际是构建类对象, 在混入特质时,该对象还没有创建。
  • 第2种方式实际是构造匿名子类,可以理解成在混入特质时,对象已经创建了。
/**
  * @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类是特质超类的子类,否则就会出现了多继承现象,发生错误。
/**
  * @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 = "错误消息!"
//}

自身类型

  • 自身类型:主要是为了解决特质的循环依赖问题,同时可以确保特质在不扩展某个类的情况下,依然可以做到限制混入该特质的类的类型。
  • 举例说明自身类型特质,以及如何使用自身类型特质
/**
  * @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),嵌套其他类的类称为外部类。内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系

/**
 * @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 的成员内部类和静态内部类,并创建相应的对象实例

/**
  * @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.属性名
/**
  * 外部类
  * 内部类访问外部类的属性的方法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 等价 外部类名别名】

/**
  * 外部类
  *  内部类访问外部类的属性的方法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中内部类的语法操作,我们将这种方式称之为 类型投影(即:忽略对象的创建方式,只考虑类型)【案例演示】

完整代码:

/**
  * @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{ //静态内部类
  }
}

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱敲代码的小黑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值