一、静态属性和静态方法
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
每天进步一点点,也许某一天你也会变得那么渺小!!!