scala之面象对象高级

1、伴生对象

1.1、基本介绍

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

1.2、示例

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.3、伴生对象结论

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

1.4、伴生对象-apply方法

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

    val pig = new Pig("小花")

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

    println("pig2.name=" + pig2.name) //小黑猪
    println("pig3.name=" + pig3.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("匿名猪猪")
}


2、scala接口-trait

2.1、基本介绍

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

2.2、声明

trait 特质名 {
	    trait体
}
object T1 extends Serializable {
}
Serializable: 就是scala的一个特质。
bject 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 {

}

2.3 trait的使用

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

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

2.4、示例

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

2.5、特质的注意事项

  • Scala提供了特质(trait),特质可以同时拥有抽象方法和具体方法,一个类可以实现/继承多个特质
  • 特质中没有实现的方法就是抽象方法。类通过extends继承特质,通过with可以继承多个特质
  • 所有的java接口都可以当做Scala特质使用
object TraitDemo03 {
  def main(args: Array[String]): Unit = {
    println("~~~~")
    //创建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~~")
  }
}

2.6、动态混入

  1. 除了可以在类声明时继承特质以外,还可以在构建对象时混入特质,扩展目标类的功能

  2. 此种方式也可以应用于对抽象类功能进行扩展

  3. 动态混入是Scala特有的方式(java没有动态混入),可在不修改类声明/定义的情况下,扩展类的功能,非常的灵活,耦合性低 。

  4. 动态混入可以在不影响原有的继承关系的基础上,给指定的类扩展功能。

  5. 同时要注意动态混入时,如果抽象类有抽象方法,如何混入

示例演示:

object MixInDemo01 {
  def main(args: Array[String]): Unit = {
    //在不修改 类的定义基础,让他们可以使用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()
}

2.7、叠加特质

①基本介绍

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

②示例
//看看混入多个特质的特点(叠加特质)
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
    println(mysql)

    //研究第2个问题,当我们执行一个动态混入对象的方法,其执行顺序是怎样的
    //顺序是,(1)从右到左开始执行 , (2)当执行到super时,是指的左边的特质 (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 { //特质,继承 Data4
  println("DB4")

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

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

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

class MySQL4  {} //普通类


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

2.8、当做富接口使用的特质

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

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

2.9、特质中的具体字段

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

object MixInPro {
  def main(args: Array[String]): Unit = {
    val mySQL = new MySQL6 with DB6 {
      override var sal = ""
    }
  }
}

trait DB6  {
  var sal:Int //抽象字段
  var opertype : String = "insert" //具体字段
  def insert(): Unit = {
  }
}
class MySQL6 {}

2.10、特质中的抽象字段

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

2.11、特质构造顺序

①基本介绍

特质也是有构造器的,构造器中的内容由“字段的初始化”和一些其他语句构成。

第一种特质构造顺序(声明类的同时混入特质)

  1. 调用当前类的超类构造器
  2. 第一个特质的父特质构造器
  3. 第一个特质构造器
  4. 第二个特质构造器的父特质构造器, 如果已经执行过,
    就不再执行
  5. 第二个特质构造器
  6. …重复4,5的步骤(如果有第3个,第4个特质)
  7. 当前类构造器

第2种特质构造顺序(在构建对象时,动态混入特质)

  1. 调用当前类的超类构造器
  2. 当前类构造器
  3. 第一个特质构造器的父特质构造器
  4. 第一个特质构造器.
  5. 第二个特质构造器的父特质构造器, 如果已经执行过,就不再执行
  6. 第二个特质构造器
  7. …重复5,6的步骤(如果有第3个,第4个特质)
  8. 当前类构造器
②分析两种情况
  • 第1种方式实际是构建类对象, 在混入特质时,该对象还没有创建
  • 第2种方式实际是构造匿名子类,可以理解成在混入特质时,对象已经创建
③代码演示
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 ff1 = new FF()

    println(ff1)

    //这时我们是动态混入
    /*
    先创建 new KK 对象,然后再混入其它特质

    调用当前类的超类构造器
当前类构造器
第一个特质构造器的父特质构造器
第一个特质构造器.
第二个特质构造器的父特质构造器, 如果已经执行过,就不再执行
第二个特质构造器
.......重复5,6的步骤(如果有第3个,第4个特质)
当前类构造器   [案例演示]

     */
    //1. E...
    //2. K....
    //3. A...
    //4. B
    //5. C
    //6. D
    println("=======================")
    val ff2 = new KK with CC with DD
    println(ff2)

  }
}

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

2.12、扩展类的特质

特质可以继承类,以用来拓展该特质的一些功能

trait LoggedException extends Exception{
  def log(): Unit ={
    println(getMessage()) // 方法来自于Exception类
  }
}

所有混入该特质的类,会自动成为那个特质所继承的超类的子类

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

​ 如果混入该特质的类,已经继承了另一个类(A类),则要求A类是特质超类的子类,否则就会出现了多继承现象,发生错误

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

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

2.13、自身类型

​ 自身类型:主要是为了解决特质的循环依赖问题,同时可以确保特质在不扩展某个类的情况下,依然可以做到限制混入该特质的类的类型。

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 {} //对吗? 错误
class Console extends Exception with Logger {}//对吗?

3、嵌套类

① Scala嵌套类的使用一

[外链图片转存失败(img-t3GP6ZkR-1566010798629)(D:/work/java/WorkSpace/MarkDown/scala/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1/assets/1542888488893.png)]

② scala嵌套类使用二

内部类如果想要访问外部类的属性,可以通过外部类对象访问。
即:访问方式:外部类名.this.属性名 
代码:

//外部类
//内部类访问外部类的属性的方法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
内部类如果想要访问外部类的属性,也可以通过外部类别名访问(推荐)。即:访问方式:外部类名别名.属性名 
代码如下:


import com.nuc.chapter08.innerclass

object ScalaInnerClassDemo {
  def main(args: Array[String]): Unit = {
    //测试1. 创建了两个外部类的实例
    val outer1 : ScalaOuterClass = new ScalaOuterClass();
    val outer2 : ScalaOuterClass = new ScalaOuterClass();

    //在scala中,创建成员内部类的语法是
    //对象.内部类  的方式创建, 这里语法可以看出在scala中,默认情况下内部类实例和外部对象关联
    val inner1 = new outer1.ScalaInnerClass
    val inner2 = new outer2.ScalaInnerClass

    //测试一下使用inner1 去调用 info()
    inner1.info()

    //创建静态内部类实例
    val staticInner= new ScalaOuterClass.ScalaStaticInnerClass()


  }
}

//外部类
//内部类访问外部类的属性的方法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() = {
      // 访问方式:外部类别名.属性名
      // 只是这种写法比较特别,学习java的同学可能更容易理解 ScalaOuterClass.class 的写法.
      println("name~ = " + myouter.name
        + " sal~ =" + myouter.sal)
    }
  }
  //定义两个属性
  var name = "jack"
  private var sal = 800.9
}

object ScalaOuterClass { //伴生对象
    class ScalaStaticInnerClass { //静态内部类
    }
}

4、类型投影

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

    def info() = {
      // 访问方式:外部类别名.属性名
      // 只是这种写法比较特别,学习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.9
}

发布了32 篇原创文章 · 获赞 39 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/yang735136055/article/details/99690365