尚硅谷Scala(6-8)

六、面向对象编程 (基础部分)

6.1 类与对象

6.1.1 Scala 语言是面向对象的 

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

6.1.2 快速入门-面向对象的方式解决养猫问题

object CatDemo {
  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=18
    cat.color="白色"
    println("ok~~")
    printf("\n小猫信息如下:%s %d %s",cat.name,cat.age,cat.color)

  }
}

//定义一个类
//一个 class Cat 对应的字节码文件只有一个 Cat.class ,默认是 public
class Cat{
  //定义/声明三个属性

  //说明
  //1. 当我们声明了 var name :String 时, 在底层对应 private name
  //2. 同时会生成 两个 public 方法 name() <=类似=> getter public name_$eq() => setter
  var name:String ="" //要给初始值,scala中没有默认初始值
  var age:Int = _ // _表示给age一个默认的值,如果是Int,默认就是0,而且只能使用在var中
  var color:String=_ // _给color一个默认值,如果是String,默认就是""

}

/*
  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; }
  }
*/

6.1.3 类和对象的区别和联系

通过上面的案例和讲解我们可以看出 :
1) 类是抽象的,概念的,代表一类事物 , 比如人类 , 猫类 ..
2) 对象是具体的,实际的,代表一个具体事物
3) 类是对象的模板,对象是类的一个个体,对应一个实例
4) Scala 中类和对象的区别和联系 和 Java 是一样的。

6.1.4 如何定义类

 6.1.5 属性

属性是类的一个组成部分,一般是值数据类型 , 也可是引用类型。比如我们前面定义猫类 的 age
是属性
class Dog{
var name = "jack" var lover = new Fish
}
class Fish{
}

6.1.6 属性/成员变量

注意事项和细节说明
1) 属性的定义语法同变量,示例: [ 访问修饰符 ] var 属性名称 [ :类型 ] = 属性值
2) 属性的定义类型可以为任意类型,包含值类型或引用类型
3) Scala 中声明一个属性 , 必须显示的初始化,然后根据初始化数据的类型自动推断,属性类型可
以省略 ( 这点和 Java 不同 )
4) 如果赋值为 null, 则一定要加类型,因为不加类型 , 那么该属性的类型就是 Null 类型 .
5) 如果在定义属性时,暂时不赋值,也可以使用符号 _( 下划线 ) ,让系统分配默认值
class A {
var var1 :String = _ // null
var var2 :Byte = _ // 0
var var3 :Double = _ //0.0
var var4 :Boolean = _ //false
}

6.1.7 属性的高级部分

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

6.1.8 如何创建对象

基本语法
val | var 对象名 [ :类型 ] = new 类型 ()
说明
1) 如果我们不希望改变对象的引用 ( 即:内存地址 ), 应该声明为 val 性质的,否则声明为 var, scala
设计者推荐使用 val , 因为一般来说,在程序中,我们只是改变对象属性的值,而不是改变对象的引用
2) scala 在声明对象变量时,可以根据创建对象的类型自动推断,所以类型声明可以省略, 但当类
型和后面 new 对象类型有继承关系即多态时 ,就必须写了
object CreateObj {
  def main(args: Array[String]): Unit = {
    val emp =new Emp  //emp类型就是Emp
    //如果我们希望将子类对象,交给父类的引用,这时就需要写上类型
    val emp1:Person = new Emp
  }
}

class Person{

}

class Emp extends Person{

}

6.1.9 类和对象的内存分配机制

object MemState {
def main(args: Array[String]): Unit = {
val p1 = new Person2
p1.name = "jack" p1.age = 10
val p2 = p1
println(p1 == p2) // true
p1.name = "tom" println("p2.name=" + p2.name)
}
}
class Person2 {
var name = "" var age: Int = _ //如果是用 _ 方式给默认值,则属性必须指定类型
}
上面的测试代码对应的内存布局图

 验证:

object MemState {
  def main(args: Array[String]): Unit = {
    val p=new Person2
    p.name="jack"
    p.age=18

    val p2=p
    println(p2==p)  //true
    p.name="tom"
    p.age=100
    printf("这个人的姓名:%s \n年龄:%d",p.name,p.age)
  }

}

class Person2{
  var name= ""
  var age:Int=_
}

6.2 方法

6.2.1基本说明

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

6.2.2 基本语法

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

6.2.3 方法案例演示

object MethodDemo01 {
  def main(args: Array[String]): Unit = {
    //使用一下
    val dog = new Dog
    println(dog.cal(10,20))

  }
}

class Dog {
  private var sal: Double = _
  var food: String = _

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

object MethodDemo02 {
  def main(args: Array[String]): Unit = {
    /*编写类(MethodExec),编程一个方法,方法不需要参数,在方法中打印一个
10*8 的矩形,在 main 方法中调用该方法。*/
    val exec = new MethodExec
    exec.printRect()

    /*
    修改上一个程序,编写一个方法中,方法不需要参数,计算该矩形的面积,并将其作为方法
    返回值。在 main 方法中调用该方法,接收返回的面积值并打印(结果保留小数点 2 位)
    分析
    1. 我们的矩形的长和宽需要设计成属性
*/

    exec.width=2.1
    exec.len=3.4
    println("面积为:"+exec.area())
  }
}

class MethodExec{
  var len=0.0
  var width=0.0

  def printRect(): Unit ={
    for(i <- 1 to 10){
      for(j <- 1 to 8){
        print("*")
      }
      println()
    }
  }

  //计算面积的方法
  def area() ={
    (len*width).formatted("%.2f")
  }
}

 6.3 类与对象应用实例

object DogCaseTest {
  def main(args: Array[String]): Unit = {
    val dog = new Dog
    dog.name="Tomcat"
    dog.age=20
    dog.weight=87.23
    println(dog.say())

  }
}

class Dog{
  var name:String=_
  var age:Int =_
  var weight:Double=_


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

6.4 构造器

6.4.1看一个需求

我们来看一个需求:前面我们在创建 Person 的对象时,是先把一个对象创建好后,再给他的年龄
和姓名属性赋值,如果现在我要求,在创 建人类的对象时,就直接指定这个对象的年龄和姓名 ,该怎么做? 这时就可以使用构造方法 / 构造器。

6.4.2 回顾-Java 构造器基本语法

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

6.4.3 回顾-Java 构造器的特点

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

6.4.4 Java 构造器的案例

在前面定义的 Person 类中添加两个构造器:
第一个无参构造器:利用构造器设置所有人的 age 属性初始值都为 18
第二个带 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;
}}

6.4.5 Scala 构造器的介绍

Java 一样, Scala 构造对象也需要调用构造方法,并且可以有任意多个构造方法(即 scala 中构造器也支持重载)。
Scala 类的构造器包括: 主构造器 和 辅助构造器

6.4.6 Scala 构造器的基本语法

class 类名 ( 形参列表 ) { // 主构造器
        // 类体
        def this(形参列表 ) { // 辅助构造器
        }
        def this(形参列表 ) { // 辅助构造器可以有多个 ...
        }
}
//1. 辅助构造器 函数的名称 this, 可以有多个,编译器通过不同参数来区分

6.4.7 Scala 构造器的快速入门

创建 Person 对象的同时初始化对象的 age 属性值和 name 属性值
object ConDemo01 {
  def main(args: Array[String]): Unit = {
    val p1 = new Person("jack",18)
    println(p1)

    val a = new A

    //下面这句话会调用def this(name:String)
    val p2 = new Person("tom")
    println(p2)

  }

}


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

  age+=10
  println("==============")

  //重写了toString方法
  override def toString = s"Person($name, $age)"

  println("ok++++")
  println("age="+age)


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

class A{

}

//输出结果
//==============
//ok++++
//age=28
//Person(jack, 28)
//==============
//ok++++
//age=28
//Person(tom, 28)

6.4.8 Scala 构造器注意事项和细节

1) Scala 构造器作用是完成对新对象的初始化,构造器没有返回值。
2) 主构造器的声明直接放置于类名之后
3) 主构造器会执行类定义中的所有语句 ,这里可以体会到 Scala 的函数式编程和面向对象编程融
合在一起,即:构造器也是方法(函数),传递参数和使用方法和前面的函数部分内容没有区别
4) 如果主构造器无参数,小括号可省略,构建对象时调用的构造方法的小括号也可以省略
5) 辅助构造器名称为 this (这个和 Java 是不一样的),多个辅助构造器通过不同参数列表进行区
分, 在底层就是 f 构造器重载
object ConDemo03 {
  def main(args: Array[String]): Unit = {
    val p1 = new Person2()
  }
}
//定义了一个 Person 类
//Person 有几个构造器 4
class Person2() {
  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("person 信息如下:")
  println("name=" + this.name)
  println("age=" + this.age)
  }

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

6.5 属性高级

6.5.1构造器参数

1) Scala 类的主构造器的形参未用任何修饰符修饰,那么这个参数是局部变量。
2) 如果参数使用 val 关键字声明 ,那么 Scala 会将参数作为类的私有的只读属性使用
3) 如果参数使用 var 关键字声明 ,那么那么 Scala 会将参数作为类的成员属性使用 , 并会提供属性
对应的 xxx()[ 类似 getter]/xxx_$eq()[ 类似 setter]方法,即这时的成员属性是私有的,但是可读写。
object ConDemo04 {
  def main(args: Array[String]): Unit = {
    val worker = new worker("joker")
    worker.name //不能访问isName

    val worker1 = new worker2("joker")
    worker1.inName  //只能读不能写
    worker1.name
    println("hello")

    val worker2 = new worker3("joker")
    worker2.inName  //可读可写
    worker2.name


  }
}

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

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

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

6.5.2 Bean 属性

JavaBeans 规范定义了 Java 的属性是像 getXxx ()和 setXxx ()的方法。许多 Java 工具(框架) 都依赖这个命名习惯。为了 Java 的互操作性。将 Scala 字段加 @BeanProperty 时,这样会自动生成规范的 setXxx/getXxx 方法。这时可以使用 对象 .setXxx() 和 对象 .getXxx() 来调用属性。
注意 : 给某个属性加入 @BeanPropetry 注解后,会生成 getXXX setXXX 的方法
并且对原来 底层自动生成类似 xxx(),xxx_$eq() 方法,没有冲突,二者可以共
object BeanPropertyDemo {
  def main(args: Array[String]): Unit = {
    val car = new Car
    car.name="宝马"
    println(car.name)

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


class Car{
  @BeanProperty var name:String=_
}

6.6 scala 对象创建的流程分析

6.6.1 看一个案例

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("小倩",20)

6.6.2 流程分析(面试题-写出)

1) 加载类的信息 ( 属性信息,方法信息 )
2) 在内存中 ( ) 开辟空间
3) 使用父类的构造器 ( 主和辅助 ) 进行初始
4) 使用主构造器对属性进行初始化 【age:90, naem nul】
5) 使用辅助构造器对属性进行初始化 【 age:20, naem 小倩 】
6) 将开辟的对象的地址赋给 p 这个引用

七、面向对象编程 (中级部分)

7.1 包

7.1.1看一个应用场景

现在有两个程序员共同开发一个项目 , 程序员 xiaoming 希望定义一个类取名 Dog , 程序员 xiaoqiang
也想定义一个类也叫 Dog 。两个程序员为此还吵了起来 , 怎么办 ? ==》使用包即可以解决这个问题 .

7.1.2 回顾-Java 包的三大作用

1) 区分相同名字的类
2) 当类很多时 , 可以很好的管理类
3) 控制访问范围

7.1.3 Scala 包的基本介绍

Java 一样, Scala 中管理项目可以使用包,但 Scala 中的包的功能更加强大 ,使用 也相对复杂些 ,下面我们学习 Scala 包的使用和注意事项。

7.1.4 Scala 包快速入门

object TestTiger {
  def main(args: Array[String]): Unit = {
    //使用小红的Tiger
    val tigerXh = new Tiger
    //使用小明的Tiger
    val tigerXm = new xm.Tiger

    println(tigerXh+"\t"+tigerXm)
  }
}

org.example.chapter07.scalaPackage.xh.Tiger@50675690	org.example.chapter07.scalaPackage.xm.Tiger@31b7dea0

7.1.5 Scala 包的特点概述

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

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

代码说明
//1. package com.atguigu{} 表示我们创建了包 com.atguigu ,在{}
1) scala 进行 package 打包时,可以有如下形式
中
// 我们可以继续写它的子包 scala //com.atguigu.scala, 还可以写类,特质 trait,还可以写 object
//2. 即 sacla 支持,在一个文件中,可以同时创建多个包,以及给各个包创建类,trait 和 object
package com.atguigu { //包 com.atguigu
package scala { //包 com.atguigu.scala
class Person { // 表示在 com.atguigu.scala 下创建类 Person
val name = "Nick" def play(message: String): Unit = {
println(this.name + " " + message)
}
}
object Test100 { //表示在 com.atguigu.scala 创建 object Test
def main(args: Array[String]): Unit = {
println("ok")
}
}
}
}
2) 包也可以像嵌套类那样嵌套使用( 包中有包 , 这个在前面的第三种打包方式已经讲过了,在
使用第三种方式时的好处是: 程序员可以在同一个文件中,将类 (class / object) trait 创建在不同的包 ,这样就非常灵活了
3) 作用域原则:可以直接向上访问。即 : Scala 中子包中直接访问父包中的内容 , 大括号体现作用
域。 ( 提示: Java 中子包使用父包的类,需要 import) 。在子包和父包 类重名时,默认采用就近原则,如果希望指定使用某个类,则带上包名即可。
4) 父包要访问子包的内容时,需要 import 对应的类等
5) 可以在同一个 .scala 文件中,声明多个并列的 package( 建议嵌套的 pakage 不要超过 3 )
6) 包名可以相对也可以绝对,比如,访问 BeanProperty 的绝对路径是: _root_.scala.beans.BeanProperty ,在一般情况下:我们使用相对路径来引入包,只有当包名冲突时,使用绝对路径来处理

7.1.7 包对象

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

7.1.8 包对象的应用案例

package com.atguigu { //包 com.atguigu
//说明
//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 { //包 com.atguigu.scala
class Person { // 表示在 com.atguigu.scala 下创建类 Person
val name = "Nick" def play(message: String): Unit = {
println(this.name + " " + message)
}
}
class User {
def testUser(): Unit = {
println("name = " + name)
sayHiv()
}
}
object Test100 { //表示在 com.atguigu.scala 创建 object Test
def main(args: Array[String]): Unit = {
println("name=" + name)
name = "yy"
sayHiv()
}
}
}
}

7.1.9 分析了包对象的底层的实现机制

 7.1.9 包对象的注意事项

7.2 包的可见性问题

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

java 提供四种访问控制修饰符号控制方法和变量的访问权限(范围) :
1) 公开级别 : public 修饰 , 对外公开
2) 受保护级别 : protected 修饰 , 对子类和同一个包中的类公开
3) 默认级别 : 没有修饰符号 , 向同一个包的类公开 .
4) 私有级别 : private 修饰 , 只有类本身可以访问 , 不对外公开 .

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

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

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

7.2.4 Scala 中包的可见性介绍

Java 中,访问权限分为 : public private protected 和默认。在 Scala 中,你可以通过类似的修饰符达到同样的效果。但是使用上有区别
object TestVisit {
  def main(args: Array[String]): Unit = {
    val c = new Clerk()
    c.showInfo()
    Clerk.test(c)

  }
}


class Clerk{
  var name:String="jack"
  private var sal:Double=9999.9

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

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

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

7.3 包的引入

7.3.1Scala 引入包基本介绍

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

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

1) Scala 中, import 语句可以出现在任何地方,并不仅限于文件顶部 import 语句的作用一直
延伸到包含该语句的块末尾。这种语法的 好处 是:在需要时在引入包, 缩小 import 包的作用范围 ,提高效率。
class User {
import scala.beans.BeanProperty
@BeanProperty var name : String = "" }
class Dog {
@BeanProperty var name : String = "" //可以吗?
}
2) Java 中如果想要导入包中所有的类,可以通过通配符 * Scala 中采用下 _
3) 如果不想要某个包中全部的类,而是其中的几个类,可以采用选取器 ( 大括号 )
def test(): Unit = {
    //可以使用选择器,选择引入包的内容,这里,我们只引入 HashMap, HashSet
    import scala.collection.mutable.{HashMap, HashSet}
    var map = new HashMap()
    var set = new HashSet()
}
4) 如果引入的多个包中含有相同的类,那么可以将不需要的类进行重命名进行区分,这个就是重
命名
def test2(): Unit = {
    //下面的含义是 将 java.util.HashMap 重命名为 JavaHashMap
    import java.util.{ HashMap=>JavaHashMap, List}
    import scala.collection.mutable._
    var map = new HashMap() // 此时的 HashMap 指向的是 scala 中的 HashMap
    var map1 = new JavaHashMap(); // 此时使用的 java 中 hashMap 的别名
}
5) 如果某个冲突的类根本就不会用到,那么这个类可以直接隐藏掉
import java.util.{ HashMap=>_, _} 
// 含义为 引入 java.util 包的所有类,但是忽略 HahsMap 类.     
var map = new HashMap() // 此时的 HashMap 指向的是 scala 中的 HashMap, 而且 idea 工具,的提示也不会显示 java.util 的 HashMaple

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

object BankDemo {
  def main(args: Array[String]): Unit = {
    //开卡
    val account = new Account("gh001", 500.00, "123456")
    account.query("123456")
    account.withDraw("123456",20.3)
    account.query("123456")
    account.deposit("123456",12.1)
    account.query("123456")

//    账号为gh001 当前余额为500.00
//    账号为gh001 当前余额为479.70
//    账号为gh001 当前余额为491.80



  }
}

//编写一个Account类
class Account(inAccount:String,inBalance:Double,inPwd:String){
  /*
    属性:  账号,余额,密码
    方法:查询、取款、存款
*/
  private val accountNO=inAccount
  private var balance=inBalance
  private var pwd=inPwd

  //查询
  def query(pwd:String): Unit ={
    if(!this.pwd.equals(pwd)){
      println("密码错误")
      return
    }
    printf("账号为%s 当前余额为%.2f\n",this.accountNO,this.balance);
  }

  //取款
  def withDraw(pwd:String,money:Double): Any = {
    if (!this.pwd.equals(pwd)) {
      println("密码错误")
      return
    }
    if (money > this.balance) {
      println("余额不足")
      return
    } else {
      this.balance -= money
      return money
    }

  }

  //存款
  def deposit(pwd:String,money:Double): Any ={
      if(!this.pwd.equals(pwd)){
        println("密码错误")
        return
      }

    if(money>0){
      this.balance+=money
      return money
    }else{
      return
    }
  }
}

7.5 面向对象编程三大特征

7.5.1基本介绍

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

7.5.2 封装介绍

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

7.5.3封装的理解和好处

1) 隐藏实现细节
2) 提可以对数据进行验证,保证安全合理
3) 同时可以加入业务逻辑

7.5.4 如何体现封装

1) 对类中的属性进行封装
2) 通过成员方法,包实现封装

7.5.5 封装的实现步骤

7.5.6 快速入门案例 

请大家看一个小程序 (TestEncap.scala), 不能随便查看人的年龄 , 工资等隐私,并对输入的年龄进行合理的验证[ 要求 1-120 之间]

7.5.7 scala 封装的注意事项的小结

前面讲的 Scala 的封装特性,大家发现和 Java 是一样的,下面我们看看 Scala 封装还有哪些特点。
1) Scala 中为了简化代码的开发,当声明属性 var 时,本身就自动提供了对应 setter/getter 方法,如果属性声明为 private 的,那么自动生成的 setter/getter 方法也是 private 的,如果属性省略访问权限修饰符,那么自动生成的 setter/getter 方法是 public
2) 因此我们如果只是对一个属性进行简单的 set get ,只要声明一下该属性 ( 属性使用默认访问
修饰符 ) 不用写专门的 getset ,默认会创建,访问时,直接对象 . 变量。这样也是为了保持访问一致性 
3) 从形式上看 dog.food 直接访问属性,其实底层仍然是访问的方法 , 看一下反编译的代码就明
4) 有了上面的特性,目前很多新的框架,在进行反射时,也支持对属性的直接反射

7.6 面向对象编程-继承

7.6.1Java 继承的简单回顾

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

7.6.2 继承基本介绍和示意图

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

7.6.3 Scala 继承的基本语法

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

7.6.4 Scala 继承快速入门

编写一个 Student 继承 Person 的案例,体验一下 Scala 继承的特点
object Extends01 {
  def main(args: Array[String]): Unit = {
    val student = new Student
    student.name="joke"    //调用student.name()
    student.studying()
    student.showInfo()

//    joke学习 scala中
//    学生信息如下:
//    名字joke
  }
}

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

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

class Student extends Person{
  def studying(): Unit ={
    println(this.name+"学习 scala中")
  }

}

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

1) 代码的复用性提高了
2) 代码的扩展性和维护性提高了
【面试官问 : 当我们修改父类时,对应的子类就会继承相应的方法和属性】

7.6.6 scala 子类继承了什么,怎么继承了?

子类继承了所有的属性,只是私有的属性不能直接访问,需要通过公共的方法去访问

7.6.7 重写方法

说明 : scala 明确规定,重写一个非抽象方法需要用 override 修饰符,调用超类的方法使用 super 关键字
object MethodOverride01 {
  def main(args: Array[String]): Unit = {
    val emp = new Emp
    emp.printName()

//    Emp printName()tom
//    Person printName()tom
//    sayHi....
  }
}

class Person100{
  var name:String = "tom"

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


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


class Emp extends Person100{
  override def printName(): Unit = {
    println("Emp printName()"+name)
    //在子类中需要去调用父类的方法,使用 super
    super.printName()
    syHi()
  }
}

7.6.8 Scala 中类型检查和转换

基本介绍
要测试某个对象是否属于某个给定的类,可以用 isInstanceOf 方法。用 asInstanceOf 方法将引用转换为子类的引用。classOf 获取对象的类名。
classOf[String] 就如同 Java String.class
obj.isInstanceOf[T] 就如同 Java obj instanceof T 判断 obj 是不是 T 类型。
obj.asInstanceOf[T] 就如同 Java (T)obj obj 强转成 T 类型。
最佳实践
类型检查和转换的最大价值在于:可以判断传入对象的类型,然后转成对应的子类对象,进行相
关操作,这里也体现出多态的特点。
object TypeConvertCase {
  def main(args: Array[String]): Unit = {
    val stu = new Student400
    val emp = new Emp400
    test(stu)
    test(emp)
  }

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

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

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

class Student400 extends Person400 {
  val stuId = 100

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

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

class Emp400 extends Person400 {
  val empId = 800

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

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

7.6.9 Scala 中超类的构造

回顾 -Java 中超类的构造
说明:
从代码可以看出:在 Java 中,创建子类对象时,子类的构造器总是去调用一个父类的构造器 ( 显式
或者隐式调用 )
class A {
public A() {
System.out.println("A()");
}
public A(String name) {
System.out.println("A(String name)" + name);
}
}
class B extends A{
public B() {
//这里会隐式调用 super(); 就是无参的父类构造器 A()
//super();
System.out.println("B()");
}
public B(String name) {
super(name);
System.out.println("B(String name)" + name);
}
}

7.6.10 Scala 中超类的构造

Scala 超类的构造说明
1) 类有一个主构器和任意数量的辅助构造器,而每个辅助构造器都必须先调用主构造器 ( 也可以是
间接调用 .)
object ScalaBaseConstrator {
  def main(args: Array[String]): Unit = {
    //分析一下他的执行流程
    //1.因为 scala 遵守先构建父类部分 extends Person300()
    //2.Person...
    //3.Emp.....(Emp300的构造器)
    //4.Emp 辅助构造器

    val emp30 = new Emp300()
    println("=================")

    val emp301 = new Emp300("jack")


    println("++++++++++++++++++++")
    //分析执行的顺序
    //1.Person...
    // 2.默认的名字
    //3.Emp ....
    // 4.Emp 辅助构造器~

    val smith = new Emp300("smith")


  }

}

class Person300(Pname:String){
  var name="zhangsan"
  println("Person.....")

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

//子类Emp继承Person
class Emp300() extends Person300(){
  println("Emp......")

  //辅助构造器
  def this(name:String){
    this  //必须调用主构造器
    this.name=name
    println("Emp 辅助构造器")
  }
}
2) 只有主构造器可以调用父类的构造器。辅助构造器不能直接调用父类的构造器。在 Scala 的构造器中,你不能调用 super(params)
object ScalaBaseConstrator {
  def main(args: Array[String]): Unit = {
    //分析一下他的执行流程
    //1.因为 scala 遵守先构建父类部分 extends Person300()
    //2.Person...
    //3.Emp.....(Emp300的构造器)
    //4.Emp 辅助构造器

//    val emp30 = new Emp300()
    println("=================")

    val emp301 = new Emp300("jack")


    println("++++++++++++++++++++")
    //分析执行的顺序
    //1.Person...
    // 2.默认的名字
    //3.Emp ....
    // 4.Emp 辅助构造器~

    val smith = new Emp300("smith")

    println("%%%%%%%%%%%%%%%%%%%%%%%%")
    val emp302 = new Emp300("make", 18)
    emp302.showInfo()


  }

}

class Person300(pName:String){
  var name=pName
  println("Person.....")

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

//子类Emp继承Person
class Emp300(eName:String,eAge:Int) extends Person300(eName){
  println("Emp......")

  //辅助构造器
  def this(name:String){
    this(name,10)  //必须调用主构造器
    this.name=name
    println("Emp 辅助构造器")
  }

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

7.6.11 覆写字段

基本介绍
Scala 中, 子类改写父类的字段,我们称为覆写 / 重写字段 。覆写字段需使用 override 修饰。
回顾:在 Java 只有方法的重写 没有属性 / 字段的重写 ,准确的讲,是 隐藏字段代替了重写
回顾 -Java 另一重要特性 : 动态绑定机制
动态绑定的机制:
//java 的动态绑定机制的小结
1. 如果调用的是方法,则 Jvm 机会将该方法和对象的内存地址绑定
2. 如果调用的是一个属性,则没有动态绑定机制,在哪里调用,就返回对应值
public class JavaDaynamicBind {
    public static void main(String[] args) {
        //将一个子类的对象地址,交给了一个 AA(父类的)引用
        //java 的动态绑定机制的小结
        //1.如果调用的是方法,则 Jvm 机会将该方法和对象的内存地址绑定
        //2.如果调用的是一个属性,则没有动态绑定机制,在哪里调用,就返回对应值
        AA obj = new BB();
        System.out.println(obj.sum()); //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 覆写字段快速入门

object ScalaFiledOverride {
  def main(args: Array[String]): Unit = {
    val obj1:AAA = new BBB
    val obj2:BBB = new BBB

    //obj1.age => obj1.age() //动态绑定机制
    //obj2.age => obj2.age()
    println("obj1.age="+obj1.age+"\tobj2.age="+obj2.age)

  }
}

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

class BBB extends AAA{
  override val age:Int=20  // 会生成 public age()相当于是方法的覆写
}

覆写字段的注意事项和细节
1) def 只能重写另一个 def( 即:方法只能重写另一个方法 )
2) val 只能重写另一个 val 属性 或 重写不带参数的 def 
案例 1 (val 只能重写另外一个 val 属性)上面这个代码
案例 2( 重写 不带参数的 def )
object ScalaFieldOverrideDetail02 {
def main(args: Array[String]): Unit = {
println("xxx")
val bbbbb = new BBBBB()
println(bbbbb.sal) // 0
val b2:AAAAA = new BBBBB()
println("b2.sal=" + b2.sal()) // 0
}
}
class AAAAA {
def sal(): Int = {
return 10
}}
class BBBBB extends AAAAA {
override val sal : Int = 0 //底层 public sal
}
3) var 只能重写另一个抽象的 var 属性

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


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

class Sub extends A03{
  //说明
  //1. 如果我们在子类中去重写父类的抽象属性,本质是实现了抽象方法
  //2. 因此这里我们可以写 override ,也可以不写
  var name:String = ""
//  override var age:Int=10
}
抽象属性:声明未初始化的变量就是抽象的属性 , 抽象属性在抽象类
var 重写抽象的 var 属性小结
1) 一个属性没有初始化,那么这个属性就是抽象属性
2) 抽象属性在编译成字节码文件时,属性并不会声明,但是会自动生成抽象方法,所以类必
须声明为抽象类
3) 如果是覆写一个父类的抽象属性,那么 override 关键字可省略 [ 原因:父类的抽象属性,
生成的是抽象方法,因此就不涉及到方法重写的概念,因此 override 可省略 ]

7.6.12 抽象类

基本介绍
Scala 中,通过 abstract 关键字标记不能被实例化的类。方法不用标记 abstract ,只要省掉方法体即可。抽象类可以拥有抽象字段,抽象字段/ 属性就是没有初始值的字段
说明 :抽象类的价值 更多是在于 设计 ,是设计者设计好后, 让子类继承并 实现抽象类 (即:实现抽象类的抽象方法)
快速入门案例:如何把 Animal 做成抽象类 , 包含一个抽象的方法 cry()
object AbstractDemo01 {
  def main(args: Array[String]): Unit = {
    println("hello")
  }
}

//抽象类
abstract class Animal{
  var name:String //抽象的字段
  var age:Int //抽象的字段
  var color:String = "black"  //普通的字段

  def cry() //抽象方法
}

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

1) 抽象类不能被实例
//默认情况下,一个抽象类是不能实例化的,但是你实例化时,动态的实现了抽象类的所有
//抽象方法,也可以先,如下
//使用的是匿名方法
val animal = new Animal03 {
override def sayHello(): Unit = {
println("say hello~~~~")
}
}
animal.sayHello()
2) 抽象类不一定要包含 abstract 方法。也就是说 , 抽象类可以没有 abstract 方法
3) 一旦类包含了抽象方法或者抽象属性 , 则这个类必须声明为 abstract
4) 抽象方法不能有主体,不允许使用 abstract 修饰
5) 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法和 抽象属性 ,除非它自己也声
明为 abstract
6) 抽象方法和抽象属性不能使用 private final 来修饰,因为这些关键字都是和重写 / 实现相违背的
7) 抽象类中可以有实现的方法 .
8) 子类重写抽象方法不需要 override ,写上也不会错 .

7.6.14 匿名子类

基本介绍
Java 一样,可以通过包含带有定义或重写的代码块的方式创建一个匿名的子类 .
public class NoNameDemo01 {
    public static void main(String[] args) {
        A2 a2 = new A2(){
            @Override
            public void cry() {
                System.out.println("cry.....");
            }
        };
        a2.cry();   //cry.....
    }
}

abstract class A2{
    public abstract void cry();
}
scala 匿名子类案例
object ScalaNoNameDemo02 {
  def main(args: Array[String]): Unit = {
    val monster = new Monster {
      override var name: String = _

      override def cry(): Unit = {
        println("cry...........")
      }
    }

    monster.cry()   //cry..........
  }
}

abstract class Monster{
  var name:String
  def cry()
}

7.6.15 继承层级

Scala 继承层级一览图
对上图的一个小结
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 可以是任何类

八、面向对象编程(高级特性)

8.1 静态属性和静态方法

8.1.1 静态属性-提出问题

说:有一群小孩在玩堆雪人 , 不时有新的小孩加入 , 请问如何知道现在共有多少人在玩 ? 请使用面向
对象的思想 ,编写程序解决。

8.1.2 基本介绍

Scala 中静态的概念 - 伴生对象
Scala 语言是完全面向对象 ( 万物皆对象 ) 的语言,所以并没有静态的操作 ( 即在 Scala 中没有静态的概念) 。但是为了能够和 Java 语言交互 ( 因为 Java 中有静态概念 ) ,就产生了一种特殊的对象来模拟类对象,我们称之为类的伴生对象。这个类的所有静态内容都可以放置在它的伴生对象中声明和调用

8.1.3 伴生对象的快速入门

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

 对快速入门案例的源码剖析

8.1.4 伴生对象的小结 

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

8.1.5 最佳实践-使用伴生对象完成小孩玩游戏

如果 , 设计一个 var total Int 表示总人数 , 我们在创建一个小孩时,就把 total 1, 并且 total 是所有对
象共享的就 ok ! ,我们使用伴生对象来解决
object ChildJoinGame {
  def main(args: Array[String]): Unit = {
    //创建三个小孩
    val child1 = new Child("孙悟空")
    val child2 = new Child("猪八戒")
    val child3 = new Child("沙和尚")

    Child.joinGame(child1)
    Child.joinGame(child2)
    Child.joinGame(child3)
    Child.showNum()

//    孙悟空 小孩加入了游戏
//    猪八戒 小孩加入了游戏
//    沙和尚 小孩加入了游戏
//    当前有3 小孩玩游戏

  }
}

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

8.1.6 伴生对象-apply 方法

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

object ApplyDemo01 {
  def main(args: Array[String]): Unit = {
    val list=List(1,2,4)
    println(list)

    val pig = new Pig("小雯")

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

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

//案例演示 apply 方法.
class Pig(pName:String){
  var name:String=pName
}

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

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

8.2 单例对象

在Scala设计模式中

8.3 接口

8.3.1 回顾 Java 接口

 8.3.2 Scala 接口的介绍

1) 从面向对象来看,接口并不属于面向对象的范畴, Scala 是纯面向对象的语言,在 Scala 中,没
有接口。
2) Scala 语言中,采用特质 trait (特征)来代替接口的概念,也就是说,多个类具有相同的特征(特征)时,就可以将这个特质(特征)独立出来,采用关键字 trait 声明。理解 trait 等价于 (interface + abstract class)
3) scala 继承特质 (trait) 的示意图

8.3.3 trait 的声明 

trait 特质名 {
        trait 体
}
1) trait 命名 一般首字母大写
Cloneable , Serializable
object T1 extends Serializable {
}
Serializable : 就是 scala 的一个特质。
scala 中, java 中的接口可以当做特质使用
object TraitDemo01 {
  def main(args: Array[String]): Unit = {

  }
}

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

object T1 extends Serializable {
}
object T2 extends Cloneable {
}

8.3.4 Scala trait 的使用

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

8.4 特质(trait)

8.4.1 特质的快速入门案例

8.4.2 代码完成 

object TraitDemo02 {
  def main(args: Array[String]): Unit = {
    val c = new C()
    val f = new F()
    c.getConnect()  // 连接 mysql 数据库...
    f.getConnect()  // 连接 oracle 数据库..
  }
}

//按照要求定义一个 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数据库...")
  }
}

8.4.3 特质 trait 的再说明

1) Scala 提供了特质(trait),特质可以同时拥有抽象方法和具体方法,一个类可以实现 / 继承多个
特质。
object TraitDemo03 {
  def main(args: Array[String]): Unit = {
    println("~~~~")
    //创建 sheep
    val sheep = new Sheep
    sheep.sayHi()
    sheep.sayHello()
//    小羊 say hi~~
//    say Hello~~
  }
}


//当一个 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~~")
  }
}
上面代码对应的底层的分析图

2) 特质中没有实现的方法就是抽象方法。类通过 extends 继承特质,通过 with 可以继承多个特质
3) 所有的 java 接口都可以当做 Scala 特质使用

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

1) 除了可以在类声明时继承特质以外,还可以在构建对象时混入特质,扩展目标类的功能
2) 此种方式也可以应用于对抽象类功能进行扩展
3) 动态混入是 Scala 特有的方式(java 没有动态混入),可在不修改类声明 / 定义的情况下,扩展
类的功能,非常的灵活,耦合性低 。
4) 动态混入可以在不影响原有的继承关系的基础上,给指定的类扩展功能。 [ 如何理解 ]
5) 同时要注意动态混入时,如果抽象类有抽象方法,如何混入
6) 案例演示
object MixInDemo01 {
  def main(args: Array[String]): Unit = {
    //在不修改类的定义基础,让他们可以使用 trait 方法
    val oracleDB = new OracleDB with Operate3 //ocp原则
    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) 动态混入

8.4.5 叠加特质

构建对象的 同时如果混入多个特质,称之为叠加特质,那么特质声明顺序从左到右,方法执行顺序从右到左。
叠加特质应用案例
目的:分析叠加特质时, 对象的构建顺序 和执行方法的顺
//看看混入多个特质的特点(叠加特质)
object AddTraits {
  def main(args: Array[String]): Unit = {
    //说明
    //1. 创建 MySQL4 实例时,动态的混入 DB4 和 File4
    //研究第一个问题,当我们创建一个动态混入对象时,其顺序是怎样的
    //总结一句话
    //Scala 在叠加特质的时候,和继承一样会先执行父特质的,然后从后面的特质开始执行(即从左到右)
    //1.Operate4...
    // 2.Data4
    //3.DB4
    //4.File4

    val mysql = new MySQL4 with DB4 with File4

    //研究第 2 个问题,当我们执行一个动态混入对象的方法,其执行顺序是怎样的
    //顺序是,
    // (1)从右到左开始执行
    // (2)当执行到 super 时,是指的左边的特质DB4
    // (3) 如果左边没有特质了,则 super 就是父特质
    //1. 向文件"
    //2. 向数据库
    //3. 插入数据 100
    mysql.insert(100)
  }
}

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

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

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

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

trait DB4 extends Data4 {
  println("DB4")

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

trait File4 extends Data4 {
  println("File4")

  override def insert(id: Int): Unit = {
    println("向文件")
    super.insert(id) //调用了 insert 方法(难点),这里 super 在动态混入时,不一定是父类
    println("---------------------")

    //如果我们希望直接调用Data4的insert方法,可以指定,如下
    super[Data4].insert(id)
  }
}
叠加特质注意事项和细节
1) 特质声明顺序从左到右。
2) Scala 在执行叠加对象的方法时,会首先从后面的特质 ( 从右向左 ) 开始执行
3) Scala 中特质中如果调用 super ,并不是表示调用父特质的方法,而是向前面(左边)继续
查找特质,如果找不到,才会去父特质查找
4) 如果想要调用具体特质的方法,可以指定: super[ 特质 ].xxx( ). 其中的泛型必须是该特质的
直接超类类
特质中重写抽象方发特例
object MixinDemo02 {
  def main(args: Array[String]): Unit = {
    val mysql = new MySQL5 with DB5 with File5
    //    将数据保存在文件中..
    //    数据在数据库里面...
    mysql.insert(666)

    //下面的混入方式错误
//    val mysql5 = new MySQL5 with File5
//    mysql5.insert(888)
  }
}

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

trait File5 extends Operate5{
  //说明
  //1.如果我们在子特质中重写/实现了一个父特质的抽象方法,但是同时调用super
  //2.这时我们的方法不是完全实现,因此需要声明为 abstract override
  //3.这时super.insert(id) 的调用就和动态混入顺序有密切关系
  abstract override def insert(id: Int): Unit = {
    println("将数据保存在文件中..")
    super.insert(id)
  }
}

trait DB5 extends Operate5{
  override def insert(id: Int): Unit = {//我们继承Operate5,并实现了Operate的insert
    println("数据在数据库里面...")
  }
}

class MySQL5{

}

8.4.6 当作富接口使用的特质

富接口:即该特质中既 有抽象方法 ,又有 非抽象方
trait Operate {
def insert( id : Int ) //抽象
def pageQuery(pageno:Int, pagesize:Int): Unit = { //实现
println("分页查询")
}
}

8.4.7 特质中的具体字段

特质中可以定义具体字段,如果初始化了就是具体字段,如果不初始化就是抽象字段。混入该特质
的类就具有了该字段,字段不是继承,而是直接加入类,成为自己的字段
object MixinPro {
  def main(args: Array[String]): Unit = {
    val mysql = new MySQL6 with DB6 {
      override var sal:Int =123
    }
  }
}

trait DB6 {
  var sal: Int //抽象字段
  var operType: String = "insert"

  def insert(): Unit = {
  }
}

class MySQL6 {}
反编译后的代码

8.4.8 特质中的抽象字段

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

8.4.9 特质构造顺序

object MixinSeq {
  def main(args: Array[String]): Unit = {
    //这时 FF 是这样 形式 class FF extends EE with CC with DD
    /*
    调用当前类的超类构造器
    第一个特质的父特质构造器
    第一个特质构造器
    第二个特质构造器的父特质构造器, 如果已经执行过,
    就不再执行
    第二个特质构造器
    .......重复 4,5 的步骤(如果有第 3 个,第 4 个特质)
    当前类构造器 [案例演示]
    */
    //构造顺序 1. E... //2. A... //3. B.... //4. C.... //5. D.... //6. F....

    //静态混入
    val ff = new FF()
    println(ff)

    /*
    先创建 new KK 对象,然后再混入其它特质
    调用当前类的超类构造器
    当前类构造器
    第一个特质构造器的父特质构造器
    第一个特质构造器. 第二个特质构造器的父特质构造器, 如果已经执行过,就不再执行
    第二个特质构造器
    .......重复 5,6 的步骤(如果有第 3 个,第 4 个特质)
    当前类构造器 [案例演示]
    */

    //构造顺序 1. E... //2. K.... //3. A... //4. B //5. C //6. D

    //动态混入
    val kk = new KK() with CC with DD
    println(kk)
  }
}

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...")
}

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

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

8.4.10 扩展类的特质

特质可以继承类,以用来拓展该特质的一些功能
//说明
//1.LoggedException 继承了 Exception
//2. LoggedException 特质就可以 Exception 功能
trait LoggedException extends Exception {
  def log(): Unit = {
    println(getMessage()) // 方法来自于 Exception 类
  }
}
所有混入该特质的类,会自动成为那个特质所继承的超类的子类
object ExtendTraitDemo01 {
  def main(args: Array[String]): Unit = {

  }
}


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


//因为 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 = "错误消息!"
}

8.4.11 自身类型

说明
自身类型:主要是为了解决特质的循环依赖问题,同时可以确保特质在不扩展某个类的情况下,依
然可以做到限制混入该特质的类的类型。
举例说明自身类型特质,以及如何使用自身类型特质
object SelfTypeDemo {
  def main(args: Array[String]): Unit = {

  }
}

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

//class Console extends Logger {} //错误 Console不是Exception 子类
class Console extends Exception with Logger {} //对的

8.5 嵌套类 //看源码,面试

8.5.1 Scala 嵌套类的使用 1

8.5.2 Scala 嵌套类的使用 2 

请编写程序,在内部类中访问外部类的属性。
  方式 1
内部类如果想要访问外部类的属性 ,可以通过外部类对象访问。
即:访问方式: 外部类名 .this. 属性
//外部类
class ScalaOuterClass {
  //定义两个属性
  var name = "scoot"
  private var sal = 30000.9

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

    }
  }
}
方式 2
内部类如果想要访问外部类的属性,也可以通过 外部类别名访问 ( 推荐 ) 。即:访问方式:外部类名
别名 . 属性名
//外部类
//内部类访问外部类的方式二:使用别名的方式
//1. 将外部类属性,写在别名后面
class ScalaOuterClass {
  myouter =>  //这里我们可以这里理解 外部类的别名 看做是外部类的一个实例
  class ScalaInnerClass { //成员内部类
    def info() = {
      // 访问方式:外部类名别名.属性名
      // 怎么理解 ScalaOuterClass.this 就相当于是 ScalaOuterClass 这个外部类的一个实例, 然后通过 ScalaOuterClass.this 实例对象去访问 name 属性
      // 只是这种写法比较特别,学习 java 的同学可能更容易理解 ScalaOuterClass.class 的写法.
      println("name~ = " + myouter.name
        + " sal· =" + myouter.sal)
    }
  }

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

8.5.3 类型投影

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

    //这里我们调用test()方法
    //在默认情况下啊,scala的内部类的实例和创建该内部类实例的外部对象关联
    inner1.test(inner1)
    inner2.test(inner2)
    //inner1.test(inner2) 错误 解决方法 ScalaOuterClass#ScalaInnerClass
    inner2.test(inner1)


//外部类
//内部类访问外部类的方式二:使用别名的方式
//1. 将外部类属性,写在别名后面
class ScalaOuterClass {
  myouter =>  //这里我们可以这里理解 外部类的别名 看做是外部类的一个实例
  class ScalaInnerClass { //成员内部类
    def info() = {
      // 访问方式:外部类名别名.属性名
      // 怎么理解 ScalaOuterClass.this 就相当于是 ScalaOuterClass 这个外部类的一个实例, 然后通过 ScalaOuterClass.this 实例对象去访问 name 属性
      // 只是这种写法比较特别,学习 java 的同学可能更容易理解 ScalaOuterClass.class 的写法.
      println("name~ = " + myouter.name
        + " sal· =" + myouter.sal)
    }

    //这里有一个方法,可以接受 ScalaInnerClass 实例
    //下面的 ScalaOuterClass#ScalaInnerClass 类型投影的作用就是屏蔽 外部对象对内部类对象的影响
    def test(ic:ScalaOuterClass#ScalaInnerClass): Unit ={
      System.out.println("使用了类型投影" + ic)
    }
  }

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值