第 8 章 面向对象 (高级部分)

一、静态属性和静态方法

1、基本介绍

回顾下Java的静态概念

	public static 返回值类型  方法名(参数列表) {方法体}
	静态属性...

说明:

Java中静态方法并不是通过对象调用的,而是通过类对象调用的,所以静态操作并不是面向对象的。

2、Scala中静态的概念-伴生对象

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

伴生对象的示例代码:

package com.lj.scala.AccompanyObject

/**
  * @author Administrator
  * @create 2020-03-11 10:16
  */
object AmpObjTest01 {

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

        ScalaPeople.sayHello()  // 伴生对象直接调用伴生对象中的方法或者属性

    }

}

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

// 伴生对象
object ScalaPeople {
    var age: Int = _
    var sex: String = _

    def sayHello(): Unit = {
        println("Object ScalaPeople say Hello!!!")
    }
}
======================运行结果==============================
Object ScalaPeople say Hello!!!
======================运行结果==============================

伴生对象的小结

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

3、用伴生对象的方式计算小孩参加游戏的人数

示例代码:

package com.lj.scala.AccompanyObject

/**
  * @author Administrator
  * @create 2020-03-11 10:55
  */
object AmpObjTest02 {

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

        val c1 = new Child("白骨精")
        val c2 = new Child("豹子精")
        val c3 = new Child("蛇精")
        val c4 = new Child("孙悟空")

        Child.joinGame(c1)
        Child.joinGame(c2)
        Child.joinGame(c3)
        Child.joinGame(c4)

        Child.showNum()

        println("各自的任务如下:")

        Child.Work(c1)
        Child.Work(c2)
        Child.Work(c3)
        Child.Work(c4)

    }

}

class Child(inName: String) {
    var name = inName
}

object Child {

    var childs_total: Int = 0

    def joinGame(child: Child): Unit = {
        println(child.name + " 加入了校外的游戏活动!!!")
        childs_total += 1
    }

    def showNum(): Unit = {
        println(s"当前游戏的人数已经有$childs_total" + "人参加了!!!")
    }

    def Work(child: Child): Unit = {

        if (child.name == "孙悟空") {
            println(child.name + " 过来参加打妖怪的!!!")
        } else {
            println(child.name + " 过来参加抓唐僧吃肉的!!!")
        }

    }

}
======================运行结果==============================
白骨精 加入了校外的游戏活动!!!
豹子精 加入了校外的游戏活动!!!
蛇精 加入了校外的游戏活动!!!
孙悟空 加入了校外的游戏活动!!!
当前游戏的人数已经有4人参加了!!!
各自的任务如下:
白骨精 过来参加抓唐僧吃肉的!!!
豹子精 过来参加抓唐僧吃肉的!!!
蛇精 过来参加抓唐僧吃肉的!!!
孙悟空 过来参加打妖怪的!!!
======================运行结果==============================

4、伴生对象-apply方法

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

示例代码:

package com.lj.scala.AccompanyObject

/**
  * @author Administrator
  * @create 2020-03-11 11:14
  */
object AmpObjTest03 {

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

        // 通过“类名(参数)【Pig("宠物猪")】”创建对象。
        val pig01 = Pig("野猪")
        val pig02 = Pig()
        val pig03 = Pig("宠物猪")

        Pig.play(pig01)
        Pig.play(pig02)
        Pig.play(pig03)

    }

}

class Pig(inName: String) {

    var name = inName

}

object Pig {

    // 编写apply方法
    def apply(inName: String): Pig = new Pig(inName)

    def apply(): Pig = new Pig("识别不了的猪猪!")

    def play(pig: Pig): Unit = {
        printf("%s 在洗涮刷!!!\n", pig.name)
    }
}
======================运行结果==============================
野猪 在洗涮刷!!!
识别不了的猪猪! 在洗涮刷!!!
宠物猪 在洗涮刷!!!
======================运行结果==============================

二、接口

回顾Java接口

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

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

1、Scala接口的介绍

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

2、trait的声明

	trait 特质名 {
		trait体
	}

	trait 命名 一般首字母大写,例如:Cloneable , Serializable。
		object T1 extends Serializable {

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

3、Scala中trait的使用

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

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

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

4、特质的快速入门案例

可以把特质可以看作是对继承的一种补充

	Scala的继承是单继承,也就是一个类最多只能有一个父类,这种单继承的机制可保证类的纯洁
性,比c++中的多继承机制简洁。但对子类功能的扩展有一定影响.所以,Scala引入trait特征第一
可以替代Java的接口,  第二个也是对单继承机制的一种补充。

在这里插入图片描述
如上图,实现该功能的示例代码如下:

package com.lj.scala.AccompanyObject

/**
  * @author Administrator
  * @create 2020-03-11 11:48
  */
object AmpObjTest04 {

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

        val c1 = new C
        c1.getConn()
        val e1 = new E
        e1.getConn()

    }

}

// 定义一个Trait
trait getCEConnTrait {

    def getConn(): Unit = {

        println("获取对应的数据库连接!!!")

    }

}


class A {
    // to do
}

class B extends A {
    // to do
}

class C extends A with getCEConnTrait {

    override def getConn(): Unit = {
        println("获取到C数据库的连接!")
    }

}

class D {
    // to do
}

class F extends D {
    // to do
}

class E extends D with getCEConnTrait {

    override def getConn(): Unit = {
        println("获取到E数据库的连接!")
    }

}
======================运行结果==============================
获取到C数据库的连接!
获取到E数据库的连接!
======================运行结果==============================

5、特质trait 的再说明

1. Scala提供了特质(trait),特质可以同时拥有抽象方法和具体方法,一个类可以实现/继承多
个特质;
2. 特质中没有实现的方法就是抽象方法。类通过extends继承特质,通过with可以继承多个特质;
3. 所有的java接口都可以当做Scala特质使用。

以上说明的示例代码:

package com.lj.scala.AccompanyObject

/**
  * @author Administrator
  * @create 2020-03-11 14:28
  */
object AmpObjTest05 {

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

        val dog = new Dog
        dog.eat("素类、肉类!")
        dog.run()
        dog.Jiao()

    }

}

/**
  * 创建一个特质(trait): AnimalTrait
  * 特质可以同时拥有“抽象方法”和“具体方法”
  */

trait AnimalTrait {

    // 抽象方法
    def run()

    // 具体实现方法
    def eat(message: String): Unit = {
        println("这个动物吃:" + message)
    }

}

trait AnimalTrait02 {

    // 抽象方法
    def Jiao()

}

// 类通过extends继承特质,通过with可以继承多个特质
class Dog extends AnimalTrait with AnimalTrait02 {

    // 实现Trait中的抽象方法:run()和JIAO()
    override def run(): Unit = {
        println("狼狗一般跑的都比较快!")
    }

    override def Jiao(): Unit = {
        println("狼狗见到陌生人就汪汪叫!")
    }
}
======================运行结果==============================
这个动物吃:素类、肉类!
狼狗一般跑的都比较快!
狼狗见到陌生人就汪汪叫!
======================运行结果==============================

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

1. 除了可以在类声明时继承特质以外,还可以在构建对象时混入特质,扩展目标类的功能;
2. 此种方式也可以应用于对抽象类功能进行扩展;
3. 动态混入是Scala特有的方式(java没有动态混入),可在不修改类声明/定义的情况下,扩展类的
功能,非常的灵活,耦合性低 ;
4. 动态混入可以在不影响原有的继承关系的基础上,给指定的类扩展功能。

示例代码:

package com.lj.scala.AccompanyObject

/**
  * @author Administrator
  * @create 2020-03-11 14:53
  */
object AmpObjTest06 {

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

        // 具体类构建对象china时混入特质Country,扩展目标类China的功能,
        // 同时需要实现特质中的抽象方法。
        // 同时也不会修改类声明/定义。
        val china = new China with Country {
            override def Stronger(name: String): Unit = {
                println(name + "非常强大,让百姓免受战火之苦!")
            }
        }
        china.area("中国", "960万平方公里")
        china.historyInfo("中国")

        // 抽象类构建对象american时混入特质Country,扩展目标类American的功能,
        // 同时需要实现抽象类中的抽象方法和特质中的抽象方法。
        // 同时也不会修改类声明/定义。
        val american = new American with Country {
            override def say(): Unit = {
                println("特朗普特别喜欢在推特上跟网友瞎砍!")
            }

            override def Stronger(name: String): Unit = {
                println(name + "特别蛮横,欺负弱小国家!遭到世界人民的谴责与愤怒!")
            }
        }
        american.Stronger("美国")
        american.say()

        // 动态混入可以在不影响原有的继承关系的基础上,给指定的类扩展功能。
        val egypt = new Egypt
        egypt.historyInfo("埃及")

    }

}


trait Country {

    // 具体实现方法
    def area(country_name: String, size: String): Unit = {
        println(country_name + "的面积是:" + size)
    }

    def army(): Unit = {
        // to do
    }

    def Stronger(name: String)

}

class China {

    def historyInfo(name: String): Unit = {
        println(name + "是四大文明古国之一!")
    }

}

abstract class American {

    def say()


}

class Egypt extends China {
    // to do
}
======================运行结果==============================
中国的面积是:960万平方公里
中国是四大文明古国之一!
美国特别蛮横,欺负弱小国家!遭到世界人民的谴责与愤怒!
特朗普特别喜欢在推特上跟网友瞎砍!
埃及是四大文明古国之一!
======================运行结果==============================

在Scala中创建对象共有几种方式?

1. new 对象
2. apply 创建
3. 匿名子类方式
4. 动态混入

7、叠加特质

基本介绍

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

分析叠加特质时,对象的构建顺序,和执行方法的顺序示例代码:

package com.lj.scala.AccompanyObject

/**
  * @author Administrator
  * @create 2020-03-11 15:30
  */
object AmpObjTest07 {

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

        /**
          * 说明:
          * 创建对象mySQL01时,特质的声明顺序从左到右,即从DB01特质开始执行,待DB01执行完以后,
          * 再执行下一个特质最后到File01e特质
          * 即声明顺序:1. 第一先执行DB01特质,根据继承关系可以知道特质声明顺序:DB01 --> Data01 --> Operate01,
          *             先声明的先放入栈(先进后出的原则)中,然后再执行对应的特质。
          *           2. 第二执行File01特质,根据继承关系可以知道特质声明顺序:File01 --> Data01(由于已经在第一
          *             次声明的时候执行过了),所以此时特质声明顺序只有:File01,然后执行。
          *
          * Scala在执行叠加对象的方法时,会首先从后面的特质(从右向左)开始执行,特质中如果调用super,并不是表示调用父
          * 特质的方法,而是向前面(左边)继续查找特质,如果找不到,才会去父特质查找
          * 即方法执行顺序:1. 先执行特质File01中的insert方法,由于方法中调用了super关键字,所以执行到这里时会去找
          *                 特质File01左边的特质DB01,找到对应的insert()方法(否则就去父类中特质查找对应的方法),
          *                 特质DB01中的insert()方法执行之后,又遇到super关键字,所以此时会去找DB01左边的特质,此时
          *                 发现没有,所以会查找父特质中的insert()方法,然后执行完就结束了。
          */
        println("输出信息如下:")
        val mySQL01 = new MySQL01 with DB01 with File01
        println("------------------华丽的分隔符-------------------------")
        println("输出信息如下:")
        mySQL01.insert(10001)

    }

}

trait Operate01 {

    println("Operate01执行......")

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

}

// 特质也可以继承特质,同样也需要实现特质中的抽象方法
// 特质Data01继承Operate01
trait Data01 extends Operate01 {

    println("Data01执行......")

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

}

// 特质DB01继承Data01
trait DB01 extends Data01 {

    println("DB01执行......")

    override def insert(id: Int): Unit = {

        println("向数据库DB01插入数据:" + id)
        super.insert(id)

    }

}

// 特质File01 extends Data01
trait File01 extends Data01 {

    println("File01执行......")

    override def insert(id: Int): Unit = {
        println("向文件File01写入数据:" + id)
        super.insert(id)  // 调用了insert方法(难点),这里super动态混入时不一定是父类
        /**
          * 说明:
          * 如果想要调用具体特质的方法,可以指定:super[特质].xxx(…).其中的泛型必须
          * 是该特质的直接超类类型,例如:super[Data01].insert(id)
          * 此时如果执行到super关键字,就不会先找左边的特质了,而是直接找到当前指定
          * 泛型[特质],即当前特质的父特质。
          */
    }

}

class MySQL01{
    // to do
}


======================运行结果==============================
输出信息如下:
Operate01执行......
Data01执行......
DB01执行......
File01执行......
------------------华丽的分隔符-------------------------
输出信息如下:
向文件File01写入数据:10001
向数据库DB01插入数据:10001
Data01插入数据:10001
======================运行结果==============================

叠加特质注意事项和细节

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

在特质中重写抽象方法特例

	trait Operate {
	  def insert(id : Int)
	}
	trait File extends Operate {
	  def insert( id : Int ): Unit = {
	    println("将数据保存到文件中..")
	    super.insert(id)
	  }
	}
运行代码,并小结问题 (错误,原因就是没有完全的实现insert,同时你还没有声明abstract overrid)

解决问题

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

			trait Operate02 {
				def insert(id : Int)
			}
			
			trait File02 extends Operate02 {
				abstract override  def insert( id : Int ): Unit = {
					println("将数据保存到文件中..")
					super.insert(id)
				}
			}

理解 abstract override 的小技巧分享:

	可以这里理解,当我们给某个方法增加了abstract override 后,就是明确的告诉编译器,该方法确
实是重写了父特质的抽象方法,但是重写后,该方法仍然是一个抽象方法(因为没有完全的实现,需
要其它特质继续实现[可以通过混入顺序实现])

示例代码:

package com.lj.scala.AccompanyObject

/**
  * @author Administrator
  * @create 2020-03-11 17:34
  */
object AmpObjTest08 {

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

        val mySQL02 = new MySQL02 with DB02 with File02  // ok
        mySQL02.insert(10001)

        // val mySQL02_01 = new MySQL02 with File02  // error  直接就报错
        val mySQL02_02 = new MySQL02 with DB02  // ok
        mySQL02_02.insert(10002)
        // val mySQL02_03 = new MySQL02 with File02 with DB02   // error
        // mySQL02_03.insert(10003)

    }

}


trait Operate02 {
    def insert(id: Int)
}

trait File02 extends Operate02 {

    /**
      * @param id
      * 说明:
      * 1. 如果在子特质中重写/实现了一个父特质的抽象方法,但同时调用super关键字,
      * 这时的方法不是完全实现,因此需要声明为“abstract override”
      * 2. 这时super.insert(id)的调用就和动态混入顺序有密切关系。
      */

    abstract override def insert(id: Int): Unit = {
        println("将数据文件(File02)保存到文件中:" + id)
        super.insert(id)
    }
}

trait DB02 extends Operate02 {  // 继承了Operate02,并实现了Operate02的insert(),其中override可以省略!
    def insert(id: Int): Unit = {
        println("将数据(DB)保存到数据库中:" + id)
    }
}

class MySQL02 {
    // to do
}
======================运行结果==============================
将数据文件(File02)保存到文件中:10001
将数据(DB)保存到数据库中:10001
将数据(DB)保存到数据库中:10002
======================运行结果==============================

8、当作富接口使用的特质

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

9、特质中的具体字段

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

示例代码:

package com.lj.scala.AccompanyObject

/**
  * @author Administrator
  * @create 2020-03-11 17:58
  */
object AmpObjTest09 {

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

        val p3 = new People03 with Operate03 {
            override var age: Int = _  // 重写字段属性
        }
        
        // p3对象就拥有了age这个字段属性
        p3.age = 12
        print(p3.age)  // 12
    }

}


trait Operate03 {
    var name: String =_
    var age: Int  // 特质中未被初始化的字段在具体的子类中必须被重写。
}

class People03 {
    // to do
}

9、特质构造顺序

介绍

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

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

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

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

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

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

第一种方式实际是构建类对象, 在混入特质时,该对象还没有创建。
第二种方式实际是构造匿名子类,可以理解成在混入特质时,对象已经创建了。

示例代码:

package com.lj.scala.AccompanyObject

/**
  * @author Administrator
  * @create 2020-03-11 18:20
  */
object AmpObjTest10 {

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

        /**
          * 直接构建类: 声明类的同时混入特质(特质构造顺序)
          *     1. 调用当前类的超类构造器;
          * 	2. 第一个特质的父特质构造器;
          * 	3. 第一个特质构造器;
          * 	4. 第二个特质构造器的父特质构造器, 如果已经执行过,就不再执行
          * 	5. 第二个特质构造器;
          * 	6. .......重复4,5的步骤(如果有第3个,第4个特质);
          * 	7. 当前类构造器。
          * 特点:实际是构建类对象,在混入特质时,该对象还没有创建,最后(执行第7步骤)才会创建。
          */
        val F = new FF
        println("------------------华丽分隔符-------------------")
        /**
          * 在构建对象时,动态混入特质(特质构造顺序)
          *     1. 调用当前类的超类构造器;
          * 	2. 当前类构造器;
          * 	3. 第一个特质构造器的父特质构造器;
          * 	4. 第一个特质构造器.;
          * 	5. 第二个特质构造器的父特质构造器, 如果已经执行过,就不再执行;
          * 	6. 第二个特质构造器;
          * 	7. .......重复5,6的步骤(如果有第3个,第4个特质);
          * 	8. 当前类构造器
          * 特点:实际是构造匿名子类,可以理解成在混入特质时,对象已经创建了(即执行第2步骤的时候就创建了)。
          */
        val K = new KK with CC with DD

    }

}

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

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

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

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

class EE {
    println("EE...")
}

// 声明类的同时混入特质
class FF extends EE with CC with DD {
    println("FF....")
}

class KK extends EE {
    println("KK....")
}
======================运行结果==============================
EE...
AA...
BB....
CC....
DD....
FF....
------------------华丽分隔符-------------------
EE...
KK....
AA...
BB....
CC....
DD....
======================运行结果==============================

10、扩展类的特质

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

		// 特质LoggedException继承了Exception类,扩展了改特质的功能(即增加了log方法等。)
		trait LoggedException extends Exception{
			def log(): Unit ={
				println(getMessage()) // 方法来自于Exception类
			}
		}
		
2. 所有混入该特质的类,会自动成为那个特质所继承的超类的子类;

		trait LoggedException extends Exception{
			def log(): Unit ={
				println(getMessage()) // 方法来自于Exception类
			}
		}
		
		// 由于特质LoggedException继承了Exception类,所以此时UnhappyException类继承特质
		// LoggedException,因此此时的UnhappyException就自动成为了Exception的子类。
		class UnhappyException extends LoggedException{
			// UnhappyException已经是Exception的子类了,所以可以重写方法
			override def getMessage = "错误消息!"
		}

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

		trait LoggedException extends Exception{
			def log(): Unit ={
				println(getMessage()) // 方法来自于Exception类
			}
		}
		// 因为IdexOutOfBoundsException类是特质LoggedException超类Exception的子类,所以如下
		// 特质的混入不会出错。
		class UnhappyException extends IdexOutOfBoundsException with LoggedException {
			// 已经是Exception的子类了,所以可以重写方法
			override def getMessage = "错误消息!"
		}
		
		class AA {}
		// 因为AA类不是特质LoggedException超类Exception的子类,所以如下特质混入会出错。
		class UnhappyException extends AA with LoggedException {
			// 已经是Exception的子类了,所以可以重写方法
			override def getMessage = "错误消息!"
		}

11、自身类型

说明

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

示例代码:

package com.lj.scala.AccompanyObject

/**
  * @author Administrator
  * @create 2020-03-11 19:20
  */
object AmpObjTest11 {

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

        val logInfo = new LogInfo
        logInfo.log("这段英语翻译有误!")

    }

}

class Exception {

    def getMessage(): Unit = {
        println("Exception info ......")
    }

}

trait Logger {

    // 明确告诉编译器,我就是Exception,如果没有这句话,下面的getMessage不能调用
    this: Exception =>

    def log(info: String): Unit = {
        // 既然我就是Exception, 那么就可以调用其中的方法
        println("调用方法getMessage:")
        getMessage()
        println("Logger打印信息:" + info)
    }
}

// 自身类型的使用, 这种用法是√
class LogInfo extends Exception with Logger {
    // to do
}

/*
// 自身类型的使用, 这种用法是×
//
class LogInfo extends Logger {
    // to do
}
 */
======================运行结果==============================
调用方法getMessage:
Exception info ......
Logger打印信息:这段英语翻译有误!
======================运行结果==============================

三、嵌套类

基本介绍

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

Java内部类的简单回顾

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

Java内部类基本语法

		class Outer{	//外部类
			class Inner{	//内部类
		
			}
		}
		class Other{	//外部其他类
		
		}

Java内部类的分类

从定义在外部类的成员位置上来看,
	1. 成员内部类(没用static修饰)
	2. 静态内部类(使用static修饰),

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

示例代码:

package com.lj.scala.AccompanyObject;

/**
 * @author Administrator
 * @create 2020-03-11 20:03
 */
public class Test {

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

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

class OuterClass { //外部类

    class InnerClass { //成员内部类

        public void test( InnerClass ic ) {
            System.out.println(ic);
        }

    }

    static class StaticInnerClass { //静态内部类

    }

}

Scala嵌套类的使用

1. 定义Scala 的成员内部类和静态内部类,并创建相应的对象实例

示例代码:

package com.lj.scala.AccompanyObject

/**
  * @author Administrator
  * @create 2020-03-11 20:08
  */
object AmpObjTest12 {

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

        val outer1 : ScalaOuterClass = new ScalaOuterClass();
        val outer2 : ScalaOuterClass = new ScalaOuterClass();

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

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

    }

}

class ScalaOuterClass {  //伴生类

    class ScalaInnerClass { //成员内部类
    }

}

object ScalaOuterClass {  //伴生对象

    class ScalaStaticInnerClass { //静态内部类
    }

}

2. 在内部类中访问外部类的属性

《方式一》

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

《方式2》

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

示例代码:

package com.lj.scala.AccompanyObject

/**
  * @author Administrator
  * @create 2020-03-11 20:08
  */
object AmpObjTest12 {

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

        val outer1 : ScalaOuterClass = new ScalaOuterClass();
        val outer2 : ScalaOuterClass = new ScalaOuterClass();

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

        outer1.name = "Jack"
        outer1.setSal(8800.59)
        inner1.info()

        outer2.name = "Tom"
        outer2.setSal(25888.29)
        inner2.info2()

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

    }

}

class ScalaOuterClass {  //伴生类

    myOuter =>  //这样写,可以理解成myOuter就是代表外部类的一个对象。

    // 当给外部指定别名时,需要将外部类的属性放到别名后。
    var name:String = _
    private var sal: Double = _

    class ScalaInnerClass { //成员内部类

        // 内部类访问外部类的属性《方式一》
        def info(): Unit = {
            // 访问方式:外部类名.this.属性名
            // 怎么理解 ScalaOuterClass.this 就相当于是 ScalaOuterClass 这个外部类的一个实例,
            // 然后通过 ScalaOuterClass.this 实例对象去访问 name 属性
            println("不是别名方式:name = " + ScalaOuterClass.this.name + " sal =" + ScalaOuterClass.this.sal)
        }

        def info2(): Unit = {
            println("别名方式:name = " + myOuter.name + " sal =" + myOuter.sal)
        }

    }

    def setSal(inSal: Double): Unit = {
        this.sal = inSal
    }

}

object ScalaOuterClass {  //伴生对象

    class ScalaStaticInnerClass { //静态内部类
    }

}
======================运行结果==============================
不是别名方式:name = Jack sal =8800.59
别名方式:name = Tom sal =25888.29
======================运行结果==============================

3、类型投影

解决方式-使用类型投影

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

示例代码:

package com.lj.scala.AccompanyObject

/**
  * @author Administrator
  * @create 2020-03-11 20:37
  */
object AmpObjTest13 {

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

        val outer1 : ScalaOuterClass01 = new ScalaOuterClass01();
        val outer2 : ScalaOuterClass01 = new ScalaOuterClass01();
        val inner1 = new outer1.ScalaInnerClass01()
        val inner2 = new outer2.ScalaInnerClass01()

        /**
          * 说明下面调用test的正确和错误的原因:
          * 1. Java中的内部类从属于外部类,因此在java中inner.test(inner2)就可以,因为是按类型来匹配的。
          * 2. Scala中内部类从属于外部类的对象,所以外部类的对象不一样,创建出来的内部类也不一样,无法互换使用;
          * 3. 比如你使用ideal看一下在inner1.test()的形参上,它提示的类型是outer1.ScalaOuterClass,而不是ScalaOuterClass。
          */
        inner1.test(inner1) // ok, 因为需要outer1.ScalaInnerClass01
        // inner1.test(inner2) // error, 需要outer1.ScalanInner01 而不是 outer2.ScalaInnerClass01
        inner2.test(inner2)  // ok  因为需要outer2.ScalaInnerClass01
        
        // 解决使用类型投影:
        inner1.test2(inner1)   // ok
        inner1.test2(inner2)   // ok

    }

}

class ScalaOuterClass01 {
    myOuter =>
    class ScalaInnerClass01 { //成员内部类

        def test(ic: ScalaInnerClass01): Unit = {
            println(ic)
        }

        /**
          * 解决使用类型投影:
          * 类型投影是指:在方法声明上,如果使用“外部类#内部类”的方式,表示忽略内部类的对象关系,
          * 等同于Java中内部类的语法操作,我们将这种方式称之为类型投影(即:忽略对象的创建方式,只考
          * 虑类型)。
          */
        def test2(ic: ScalaOuterClass01#ScalaInnerClass01): Unit = {
            println(ic)
        }

    }
}

学习来自:北京尚学堂韩顺平老师—尚硅谷大数据技术之Scala
每天进步一点点,也许某一天你也会变得那么渺小!!!

发布了24 篇原创文章 · 获赞 6 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/XuanAlex/article/details/104793152