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、伴生对象结论
Scala中伴生对象采用object关键字声明,伴生对象中声明的全是 "静态"内容,可以通过
伴生对象名称直接调用。
伴生对象对应的类称之为伴生类,
伴生对象的名称应该和伴生类名一致。
伴生对象中的属性和方法都可以通过伴生对象名(类名)直接调用访问
从语法角度来讲,
所谓的伴生对象其实就是类的静态方法和成员的集合
从技术角度来讲,scala还是没有生成静态的内容,只不过是将伴生对象生成了一个新的类,实现属性和方法的调用。
从底层原理看,伴生对象实现静态特性是依赖于 public static final MODULE$ 实现的。
伴生对象的声明应该和伴生类的声明在同一个源码文件中(
如果不在同一个文件中会运行错误!
),但是如果没有伴生类,也就没有所谓的伴生对象了,所以放在哪里就无所谓了。如果 class A 独立存在,那么A就是一个类, 如果 object A 独立存在,那么A就是一个"静态"性质的对象[即类对象], 在 object A中声明的属性和方法可以通过 A.属性 和 A.方法 来实现调用
当一个文件中,存在伴生类和伴生对象时,文件的图标会发生变化
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关键字连接
- 没有父类
class 类名 extends 特质1 with 特质2 with 特质3 …- 有父类
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、动态混入
除了可以在类声明时继承特质以外,还可以在构建对象时
混入特质
,扩展目标类的功能此种方式也可以应用于对抽象类功能进行扩展
动态混入是Scala特有的方式(
java没有动态混入
),可在不修改类声明/定义的情况下,扩展类的功能,非常的灵活,耦合性低 。动态混入可以在不影响原有的继承关系的基础上,给指定的类扩展功能。
同时要注意动态混入时,如果抽象类有抽象方法,如何混入
示例演示:
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 {} //普通类
③注意事项
- 特质声明顺序从左到右。
- Scala在执行叠加对象的方法时,会首先从后面的特质(从右向左)开始执行
- Scala中特质中如果调用super,并不是表示调用父特质的方法,而是向前面(左边)继续查找特质,如果找不到,才会去父特质查找
- 如果想要调用具体特质的方法,可以指定:
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、特质构造顺序
①基本介绍
特质也是有构造器的,构造器中的内容由“字段的初始化”和一些其他语句构成。
第一种特质构造顺序(声明类的同时混入特质)
- 调用当前类的超类构造器
- 第一个特质的父特质构造器
- 第一个特质构造器
- 第二个特质构造器的父特质构造器, 如果已经执行过,
就不再执行- 第二个特质构造器
- …重复4,5的步骤(如果有第3个,第4个特质)
- 当前类构造器
第2种特质构造顺序(在构建对象时,动态混入特质)
- 调用当前类的超类构造器
- 当前类构造器
- 第一个特质构造器的父特质构造器
- 第一个特质构造器.
- 第二个特质构造器的父特质构造器, 如果已经执行过,就不再执行
- 第二个特质构造器
- …重复5,6的步骤(如果有第3个,第4个特质)
- 当前类构造器
②分析两种情况
- 第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
}