Scala编程学习之八-第 8 章面向对象编程(高级)

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

8.1.1伴生对象的快速入门

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

    //使用伴生对象的方法/属性
    //伴生对象.属性 或 方法
    ScalaPerson.sayHi()
    println(ScalaPerson.sex)
  }
}

//伴生类说明
//1. 伴生类和伴生对象一一对应
//2. class ScalaPerson 底层会生成一个类 ScalaPerson
//3. 在底层生成类 ScalaPerson 会去调用 ScalaPerson$ 的方法。。。
class ScalaPerson {
  var name : String = _
}

//伴生对象
//1. 伴生对象和伴生类也是一一对应
//2. object ScalaPerson , 当在一个文件中,只有object ScalaPerson 会生成两个类ScalaPerson(空) 和 ScalaPerson$
//3. 当在一个文件中,有object ScalaPerson 和 class ScalaPerson,那么就生成一个 ScalaPerson$
object ScalaPerson {
  var sex : Boolean = true //

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

8.1.2伴生对象的小结
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.方法 来实现调用

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

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

object ChildGame {
  def main(args: Array[String]): Unit = {
    val child1 = new Child("蜘蛛精")
    val child2 = new Child("老鼠精")
    val child3 = new Child("黄鼠狼精")
    Child.joinGame(child1) //Child$.MODULE$
    Child.joinGame(child2)
    Child.joinGame(child3)

    Child.showInfo()
  }
}


//小孩类
class Child(cName:String) {
  var name :String = cName
}

object Child {
  var totalNum = 0
  def joinGame(child:Child): Unit = {
    println(child.name + " 加入游戏..")
    totalNum += 1
  }
  def showInfo(): Unit = {
    printf("当前有%d个小孩玩游戏\n" , totalNum)
  }
}

8.1.4伴生对象-apply方法

object ApplyDemo {
  def main(args: Array[String]): Unit = {
    //传统的创建... new ....

    val cat1 = Cat("波斯猫",2)
    println(cat1)
    val cat2 = Cat(10)
    println(cat2)

  }
}


class Cat(cName:String,cAge:Int) {
  var name:String = cName
  var age:Int = cAge

  override def toString: String = {
    s"猫猫的信息 name=$cName age=$age"
  }

  def this(age:Int) {
    this("默认小猫", age)
    //其它代码...
  }
}

object Cat {

  //apply可以重载..
  def apply(cName: String, cAge: Int): Cat =
    new Cat(cName, cAge) //调用了  class Cat的构造器

  def apply(cAge: Int): Cat = new Cat(cAge)
}

8.2单例模式
8.2.1什么是单例模式

单例模式是指:保证在整个的软件系统中,某个类只能存在一个对象实例。

8.2.2单例模式的应用场景

比如Hibernate的SessionFactory,它充当数据存储源的代理,并负责创建Session对象。SessionFactory并不是轻量级的,一般情况下,一个项目通常只需要一个SessionFactory就够,这是就会使用到单例模式。
Akka [ActorySystem 单例] AKKA

8.2.3单例模式的应用案例
单例模式-懒汉式

object TestSingleTon extends App {
  val singleTon = SingleTon.getInstance
  val singleTon2 = SingleTon.getInstance

  println(singleTon.hashCode() + " " + singleTon2.hashCode())
  println(singleTon == singleTon2) //true
}

//将SingleTon的构造方法私有化
//实现了SingleTon 类的单例模式
class SingleTon private() {}

object SingleTon {
  private var s: SingleTon = null

  //懒汉式的单例
  def getInstance = {
    if (s == null) {
      s = new SingleTon
    }
    s
  }
}


单例模式-饿汉式
package com.atguigu.chapter08

object TestSingleTon2 extends App {
  val singleTon = SingleTon2.getInstance
  val singleTon2 = SingleTon2.getInstance
  println(singleTon.hashCode() + " ~ " + singleTon2.hashCode())
  println(singleTon == singleTon2)
}

//将SingleTon2的构造方法私有化
class SingleTon2 private() {
  println("~~~")
}

object SingleTon2 {
  private val s: SingleTon2 = new SingleTon2

  //饿汉式
  def getInstance = {
    s
  }
}

8.3接口
8.3.1回顾Java接口

声明接口
interface 接口名
实现接口
class 类名 implements 接口名1,接口2

1)在Java中, 一个类可以实现多个接口。
2)在Java中,接口之间支持多继承
3)接口中属性都是常量
4)接口中的方法都是抽象的 //java高级 [默认实现 【ssm】]

8.3.2Scala接口的介绍

1)从面向对象来看,接口并不属于面向对象的范畴,Scala是纯面向对象的语言,在Scala中,没有接口。
2)Scala语言中,采用trait(特质,特征)来代替接口的概念,也就是说,多个类具有相同的特征(特征)时,就可以将这个特质(特征)独立出来,采用关键字trait声明。可以这里理解trait (scala的特质时 抽象类+接口 组合 分析底层)

8.4特质(trait)
trait 的声明

trait 特质名 {
trait体
}

1)trait 命名 一般首字母大写.

Cloneable , Serializable

object T1 extends Serializable {
  
}

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

8.4.1Scala中trait 的使用

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

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

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

8.4.2特质的快速入门案例

可以把特质可以看作是对继承的一种补充
Scala的继承是单继承,也就是一个类最多只能有一个父类,这种单继承的机制可保证类
的纯洁性,比c++中的多继承机制简洁。但对子类功能的扩展有一定影响.所以
我们认为: Scala引入trait特征 第一可以替代Java的接口, 第二个也是对单继承机制
的一种补充 【案例演示+代码说明反编译】
代码如下:

object Trait02 {
  def main(args: Array[String]): Unit = {
    //使用
    val c = new C
    val e = new E
    //调用了C类实现的Trait1的geConnenct
    c.getConnect("root","12345")
    e.getConnect("scott","12345")

  }
}

//这是一个Trait1
trait Trait1 {
  //声明方法,抽象的.
  def getConnect(user: String, pwd: String): Unit

  //def test(n1:Int)
}

class A {}

class B extends A {}

class C extends A with Trait1 {
  override def getConnect(user: String, pwd: String): Unit = {
    println("连接到mysql数据库")
  }
}

class D {}

class E extends D with Trait1 {
  def getConnect(user: String, pwd: String): Unit = {
    println("e连接oracle")
  }
}

class F extends D {}

2)特质中没有实现的方法就是抽象方法。类通过extends继承特质,通过with可以继承多个特质

trait Trait05 {
  def sayHi
}

trait Trait04 {
  def say()
  def sayHello(): Unit = {
    println("Trait04 sayHello")
  }
}

class A05 extends Trait04 with Trait05 {
  override def say(): Unit = {
    println("A05 say()")
  }

  override def sayHi: Unit = {
    
  }
}
  1. 所有的java接口都可以当做Scala特质使用

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

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

    //创建OracleDB 实例,同时动态混入Operate3特质
    //就可以使用特质的方法,理解解耦接入.
    val oracleDB = new OracleDB with Operate3 {
      override def insert2(): Unit = {
        println("insert2")
      }
    }

    oracleDB.insert(100)//
    oracleDB.insert2()

    new MySQL3 with Operate3 {
      override def insert2(): Unit = {

      }
    }

    //如果我们要去实例化一个abstract 类,也可以,但是需要时候用匿名子类来构建
    //语句
    val mySQL = new MySQL3 {
      override def sayHi: Unit = {
        
      }
    }

  }
}


//特质
trait Operate3 {
  def insert(id: Int): Unit = {
    println("插入数据 = " + id)
  }
  def insert2()
}
//普通类
class OracleDB {
}
 
//抽象类
abstract class MySQL3 {
  def sayHi
}

在Scala中创建对象共有几种方式
1)new 对象
2)applay 方法,创建对象
3)动态混入
4)匿名子类创建对象

8.4.5叠加特质

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

叠加特质应用案例
目的:分析叠加特质时,对象的构建顺序,和执行方法的顺序

object MuTraitDemo {
  def main(args: Array[String]): Unit = {
    //1. Operate4...
    //2. Data4
    //3. DB4
    //4. File4

    //val mysql = new MySQL4 with DB4 with File4 //栈的机制
    val mysql = new MySQL4 with File4 with DB4
    //分析问题1: 动态混入时,构建对象实例的顺序是什么?
    //构建实例时,顺序是从左到右 -》

    //分析问题2:动态混入创建对象,在执行方法时,顺序
    //是 从右到左执行
    // 1. 向文件
    // 2. 向数据库
    // 3. 插入数据 = 100
    println("-----------------------")
    mysql.insert(100)



    //val mysql = new MySQL4 with File4 with DB4


  }
}

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

  def insert(id: Int)
}

trait Data4 extends Operate4 {
  println("Data4")

  override def insert(id: Int): Unit = {
    println("插入数据 = " + id)
  }
}

trait DB4 extends Data4 {
  println("DB4")

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

trait File4 extends Data4 {
  println("File4")

  override def insert(id: Int): Unit = {
    println("向文件")
    //理论上应该是调用其父特质的insert
    //这里的 super 含义: Scala中特质中如果调用super,并不是表示调用父特质的方法,而是向前面(左边)继续查找特质,如果找不到,才会去父特质查找
    super.insert(id)
    //如果我们就是希望去让super指向自己的直接父特质,可以如下操作
    //这里的Data4必须是File4 直接父特质
    //super[Data4].insert(id)
  }
}
//普通的类
class MySQL4 {}

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

8.5在特质中重写抽象方法特例
8.5.1提出问题和应用案例

object OverrideAbstractMethod {
  def main(args: Array[String]): Unit = {
    println("aaa~~")
    val mysql5 = new MySQL5 with DB5  with File5 //ok!!!
    //val mysql5_ = new MySQL5 with File5  with  DB5//error!!!
    mysql5.insert(999) //1.

  }
}

trait Operate5 {
  def insert(id: Int)
}

trait DB5 extends  Operate5 {
  def insert( id : Int ): Unit = {
    println("将数据保存到数据库中.." + id)
  }
}

trait File5 extends Operate5 {

  //这里的问题1. 重写了父特质的方法,但是没有没有完全完成,因为调用了父特质的抽象方法
  //解决方法.
  //(1)abstract  override 表示告诉编译器,该方法重写,但是仍然是抽象,但是这里的super.insert()
  //(2)不是执行父特质,而是在动态混入时,指向前面的某个特质的super.insert
  //(3)我们在混入 File5 要注意混入的顺序。
  abstract  override  def insert(id: Int): Unit = {
    println("将数据保存到文件中..")
    super.insert(id)
  }
}
class MySQL5 {}



8.5.2解决问题重写抽象方法

方式1 : 去掉 super()... 

方式2:  调用父特质的抽象方法,那么在实际使用时,没有方法的具体实现,无法编译通过,为了避免这种情况的发生。可重写抽象方法,这样在使用时,就必须考虑动态混入的顺序问题。

重写抽象方法时需要考虑混入特质的顺序问题和完整性问题 
看4个案例,并判断结果。
var mysql2 = new MySQL5  with DB5 //  ok
mysql2.insert(100)
var mysql3 = new MySQL5  with File5 // error
mysql2.insert(100)

var mysql4 = new MySQL5 with File5 with DB5// error 
mysql4.insert(100)
var mysql4 = new MySQL5 with DB5 with File5// ok
mysql4.insert(100)

8.5.3当作富接口使用的特质

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

8.5.4特质中的具体字段

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

object MixInFiledDemo {
  def main(args: Array[String]): Unit = {
    var mysql = new MySQL6 with DB6
    //通过反编译,可以看到 opertype,是加入到 mysql对象
    //从这里我们可以看到混入和继承不是一回事
    /*
      MySQL6 mysql = new MySQL6() { private String opertype;

      public String opertype() { return this.opertype; }
      @TraitSetter
      public void opertype_$eq(String x$1) { this.opertype = x$1; }
      public void insert() { DB6.class.insert(this); }

    };
     */
    println(mysql.opertype)

  }
}

//特质
trait Operate6 {
  var opertype: String //抽象字段

  def insert() //抽象方法
}

trait DB6 extends Operate6 {
  var opertype: String = "insert" //字段

  def insert(): Unit = { //实现
  }
}

class MySQL6 {}

8.5.5特质构造顺序
介绍
特质也是有构造器的,构造器中的内容由“字段的初始化”和一些其他语句构成。具体实现请参考“特质叠加”

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

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 TraitCreateSeq {
  def main(args: Array[String]): Unit = {
    //分析
    //1.E...
    //2. A...
    //3. B....
    // 4. C....
    // 5. D....
    // 6. F....
    val ff = new FF

    //1. E...
    //2. K....
    //3. A
    //4. B
    //5. C
    //6. D
    println("第2种构建顺序...")
    val ff2 = 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...")
}

class FF extends EE with CC with DD {
  println("F....")
}

class KK extends EE {
  println("K....")
}

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

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

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

trait LoggedException extends Exception{
  def log(): Unit ={
    println(getMessage()) // 方法来自于Exception类
  }
}
//UnhappyException 就是Exception的子类.
class UnhappyException extends LoggedException{
    // 已经是Exception的子类了,所以可以重写方法
    override def getMessage = "错误消息!"
}

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

//扩展类特质
object TraitExtendsClass {
  def main(args: Array[String]): Unit = {
      println("hello")
  }
}


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

//ok
//IndexOutOfBoundsException 和  LoggedException 都是Exception
class UnhappyException2 extends IndexOutOfBoundsException with LoggedException


class CCC 
class UnhappyException3 extends CCC with LoggedException

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

应用案例
举例说明自身类型特质,以及如何使用自身类型特质
代码

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

  }
}

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

//因为 Logger 使用自身类型,要求继承(混入)Logger 必须是Exception子类
// class AAA extends Logger [错误]
class BBB extends Exception with Logger //ok

8.6嵌套类
8.6.1基本介绍
在Scala中,你几乎可以在任何语法结构中内嵌任何语法结构。如在类中可以再定义一个类,这样的类是嵌套类,其他语法结构也是一样。
嵌套类类似于Java中的内部类。

8.6.2Java内部类的简单回顾

在Java中,一个类的内部又完整的嵌套了另一个完整的类结构。被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类。内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系 【完整详细的回顾看我以前授课视频】

8.6.3Java内部类基本语法

class Outer{	//外部类
        class Inner{	//内部类

      	  }
	}
	class Other{	//外部其他类
	}

8.6.4Java内部类的分类

从定义在外部类的成员位置上来看,

  1. 成员内部类(没用static修饰)
  2. 和静态内部类(使用static修饰),

定义在外部类局部位置上(比如方法内)来看:
分为局部内部类(有类名)
匿名内部类(没有类名)

这里我们就回顾一下成员内部类和静态内部类。

8.6.5Java内部类回顾案例

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);
        inner2.test(inner2);

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

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

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

内部类的四个作用

8.6.6Scala嵌套类的使用1

1)请编写程序,定义Scala 的成员内部类和静态内部类,并创建相应的对象实例。
2)代码如下:

object ScalaInnerClass {
  def main(args: Array[String]): Unit = {
    //创建两个外部类对象
    val outer1: ScalaOuterClass = new ScalaOuterClass()
    val outer2: ScalaOuterClass = new ScalaOuterClass()

    //创建内部类对象
    val innerClass1 = new outer1.ScalaInnerClass
    val innerClass2 = new outer2.ScalaInnerClass
    //观察innerClass1 类型是 outer1.ScalaInnerClass
    //innerClass2 类型是 outer2.ScalaInnerClass
    println(innerClass1)
    println(innerClass2)

    //调用内部类的info方法,来访问外部类的属性(方法)
    innerClass1.info()
    innerClass2.info()

    //创建一个静态内部类的对象实例
    //创建静态内部类对象
    val staticInner = new ScalaOuterClass.ScalaStaticInnerClass()
    println(staticInner)


  }
}


//外部类
class ScalaOuterClass {

  var name : String = "scott"
  private var sal : Double = 1.2

  private def sayHi(): Unit = {
    println("say hi~~~")
  }


  class ScalaInnerClass { //成员内部类

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

  }

}


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

}

8.6.7Scala嵌套类的使用2

请编写程序,在内部类中访问外部类的属性和方法两种方法。
方式1
内部类如果想要访问外部类的属性,可以通过外部类对象访问。
即:访问方式:外部类名.this.属性名
方式2
内部类如果想要访问外部类的属性,也可以通过外部类别名访问(推荐)。
即:访问方式:外部类名别名.属性名 【外部类名.this 等价 外部类名别名】
代码如下:

object ScalaInnerClass2 {
  def main(args: Array[String]): Unit = {
    //创建两个外部类对象
    val outer1: ScalaOuterClass2 = new ScalaOuterClass2()
    val outer2: ScalaOuterClass2 = new ScalaOuterClass2()

    //创建内部类对象
    val innerClass1 = new outer1.ScalaInnerClass2
    val innerClass2 = new outer2.ScalaInnerClass2
    //观察innerClass1 类型是 outer1.ScalaInnerClass
    //innerClass2 类型是 outer2.ScalaInnerClass
    println(innerClass1)
    println(innerClass2)

    //调用内部类的info方法,来访问外部类的属性(方法)
    innerClass1.info()
    innerClass2.info()

    //创建一个静态内部类的对象实例
    //创建静态内部类对象
    val staticInner = new ScalaOuterClass.ScalaStaticInnerClass()
    println(staticInner)


  }
}


//外部类
//内部类如果想要访问外部类的属性,也可以通过外部类别名访问(推荐)。
即:访问方式:外部类名别名.属性名
class ScalaOuterClass2 {

  myouter =>
  class ScalaInnerClass2 { //成员内部类

    def info() = {
      //使用别名的方式来访问外部类的属性和方法,相当于myouter是一个外部类的实例ScalaOuterClass2.this
      //这时需要将外部类的属性和方法的定义/声明放在别名后
      println("name = " + myouter.name
        + " age =" + myouter.sal)
      //可以访问方法
      myouter.sayHi
    }

  }
  var name: String = "scott"
  private var sal: Double = 1.2

  private def sayHi(): Unit = {
    println("say hi~~~哈哈~~~")
  }

}


object ScalaOuterClass2 { //伴生对象
class ScalaStaticInnerClass2 { //静态内部类
}

}

8.6.8类型投影
先看一段代码,引出类型投影

class ScalaOuterClass3 {
  myOuter =>
  class ScalaInnerClass3 { //成员内部类
    def test(ic: ScalaInnerClass3): Unit = { //如果这样写,不同的外部对象创建的内部实例,是有匹配问题
      System.out.println(ic)
    }
  }
}

解决方式-使用类型投影

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

class ScalaOuterClass3 {
  myOuter =>
  class ScalaInnerClass3 { //成员内部类
    def test(ic: ScalaOuterClass3#ScalaInnerClass3): Unit = { //如果这样写,不同的外部对象创建的内部实例,是有匹配问题
      System.out.println(ic)
    }
  }
}

猜你喜欢

转载自blog.csdn.net/smsmtiger/article/details/84983983