七、Scala面向对象编程(高级) 淩亂°似流年 2024-04-17 05:59 18阅读 0赞 = **目录** 一、静态属性和静态方法 1、基本介绍 2、伴生对象 (1)实例 (2)伴生对象的小结 (3)伴生对象-apply方法 三、接口 1、Java接口 2、Scala接口(特质) A、基本介绍 B、特质入门实例 C、带有特质的对象,动态混入 E、叠加特质 F、在特质中重写抽象方法特例 G、富接口特质、特质中的具体字段和抽象字段 H、特质构造顺序 I、扩展类的特质 J、自身类型 四、嵌套类 A、Java内部类的简单回顾 (2)Java内部类的分类 B、Scala嵌套类的使用 C、类型投影 -------------------- # 一、静态属性和静态方法 # ## 1、基本介绍 ## **(1)回顾Java的静态概念** Java中静态方法并**不是通过对象调用的,而是通过类对象调用的**,**所以静态操作并不是面向对象的。** public static 返回值类型 方法名(参数列表) {方法体} 静态属性... **(2)Scala中静态的概念-伴生对象** Scala中静态的概念-伴生对象 Scala语言是完全面向对象(**万物皆对象**)的语言,所以并没有静态的操作(即在Scala中没有静态的概念)。但是为了能够和Java语言交互(因为Java中有静态概念),就产生了一种特殊的对象来**模拟类对象**,我们称之为类的**伴生对象**。这个类的所有静态内容都可以**放置在它的伴生对象**中声明和调用 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3MyOTQ4NzgzMDQ_size_16_color_FFFFFF_t_70][] ## 2、伴生对象 ## ### **(1)实例** ### //伴生类 //class ScalaPerson 编译后生成ScalaPerson 类 ScalaPerson.class //object ScalaPerson 编译后生成ScalaPerson$ 类 ScalaPerson$.class class ScalaPerson{ var name:String = _ } //伴生对象 object ScalaPerson { var sex:Boolean = true def sayHi():Unit={ println("object ScalaPerson") } } ### **(2)伴生对象的小结** ### * Scala中伴生对象采用object关键字声明,**伴生对象中声明的全是 "静态"内容**,可以**通过伴生对象名称直接调用**。 * 伴生对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致。 * 伴生对象中的属性和方法都**可以通过伴生对象名(类名)直接调用访问** * 从语法角度来讲,所谓的伴生对象其实就是**类的静态方法和成员的集合** * 从技术角度来讲,**scala还是没有生成静态的内容,**只不过是将伴生对象**生成了一个新的类,实现属性和方法的调用。** * 从底层原理看,伴生对象实现静态特性是依赖于 **public static final MODULE$** 实现的。 * **伴生对象的声明应该和伴生类的声明在同一个源码文件中**(如果不在同一个文件中会运行错误!),**但是如果没有伴生类,也就没有所谓的伴生对象了**,所以放在哪里就无所谓了。 * 如果 class A 独立存在,那么A就是一个类, **如果 object A 独立存在,那么A就是一个"静态"性质的对象**\[即类对象\], 在 object A中声明的属性和方法可以通过 A.属性 和 A.方法 来实现调用 * 当一个文件中,存在伴生类和伴生对象时,文件的图标会发生变化 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3MyOTQ4NzgzMDQ_size_16_color_FFFFFF_t_70 1][] ### (3)伴生对象-apply方法 ### object ApplyTest { def main(args: Array[String]): Unit = { val list = List(1,2,3) val pig = new Pig("小花") // 使用apply方法来创建对象 val pig2 = Pig("小黑猪")//触发def apply(pName: String): Pig = new Pig(pName)方法 val pig3 = Pig()//触发def apply(): Pig = new Pig("匿名猪猪") println(s"pig2 = ${pig2.name}") println(s"pig3 = ${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("匿名猪猪") } 二、单例对象 (在scala设计模式中学习) # 三、接口 # ## 1、Java接口 ## **(1)声明接口** interface 接口名 **(2)实现接口** class 类名 implements 接口名1,接口2 **(3)注意点** * 在Java中, 一个类可以实现多个接口。 * 在Java中,接口之间支持多继承 * 接口中属性都是常量 * 接口中的方法都是抽象的 ## 2、Scala接口(特质) ## ### **A、基本介绍** ### 从面向对象来看,接口并不属于面向对象的范畴,**Scala是纯面向对象的语言**,在Scala中,没有接口。 Scala语言中,**采用特质trait(特征)来代替接口的概念,**也就是说,多个类具有相同的特征(特征)时,就可以将这个特质(特征)独立出来,采用关键字trait声明。 理解trait 等价于(interface + abstract class) ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3MyOTQ4NzgzMDQ_size_16_color_FFFFFF_t_70 2][] **(1)trait 的声明** trait 命名 一般首字母大写. 在scala中,java中的接口可以当做特质使用 trait 特质名 { trait体 } //trait Serializable extends Any with java.io.Serializable //scala中,java的接口都可以当做trait来使用 object T1 extends Serializable{ } **(2)trait 的使用** 一个类具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素,所以在使用时,也采用了**extends**关键字,**如果有多个特质或存在父类,那么需要采用with关键字连接。** //假如定义的类没有父类时 class 类名 extends 特质1 with 特质2 with 特质3 .. //假如定义的类有父类时 class 类名 extends 父类 with 特质1 with 特质2 with 特质3 ### **B、特质入门实例** ### * ***可以把特质可以看作是对继承的一种补充*** * Scala的继承是单继承,也就是一个类最多只能有一个父类,这种单继承的机制可保证类的纯洁性,比c++中的多继承机制简洁。但对子类功能的扩展有一定影响.所以我们认为: ***Scala引入trait特征 第一可以替代Java的接口, 第二个也是对单继承机制的一种补充*** * Scala提供了特质(trait) ,**特质可以同时拥有抽 象方法和具体方法,一 个类可以实现/继承多个 特质,具体方法可以不被实现特质的类重写,也可以被实现特质的类重写。**和Java中的接口不太一样的是特质中的方法并不一定是抽象的, * 特质中没有实现的方法就是抽象方法。**类通过extends继承特质,通过with可以继承多个特质** * **所有的java接口都可以当做Scala特质使用** ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3MyOTQ4NzgzMDQ_size_16_color_FFFFFF_t_70 3][] object TraitTest { def main(args: Array[String]): Unit = { val c = new C val e = new E c.getConnect() e.getConnect() c.sayOk() e.sayOk() } } /**类之间的关系,使用trait给C和E增加方法(如数据库连接方法)*/ class A{} class B extends A{} class C extends A with T { override def getConnect(): Unit = { println("连接mysql...") } } class D{} class E extends D with T { override def getConnect(): Unit ={ println("连接mongo...") } //重写具体方法 override def sayOk(): Unit = { println("mongo say ok") } } class F extends D{} /** 定义trait*/ trait T{ // 定义一个规范 def getConnect() // 具体方法 def sayOk():Unit={ println("okkkk") } } ### C、带有特质的对象,动态混入 ### **(1)基本介绍** * 除了可以在类声明时继承特质以外,还可以在构建对象时混入特质,扩展目标类的功能 * 此种方式也可以应用于对抽象类功能进行扩展 * 动态混入**是Scala特有的方式(java没有动态混入),**可在不修改类声明/定义的情况下,扩展类的功能,非常的灵活,**耦合性低。** * 动态混入可以在不影响原有的继承关系的基础上,给指定的类扩展功能。 object TraitTest { def main(args: Array[String]): Unit = { // 普通类的动态混入 var oracle = new OracleDB with T oracle.insert(111) oracle.update(222) // 抽象类的动态混入 val mysql = new MysqlDB with T mysql.insert(333) } } class OracleDB extends T2 { } abstract class MysqlDB { } trait T { def insert(id: Int): Unit = { println("插入数据 = " + id) } } trait T2{ def update(id:Int): Unit ={ println(s"更新数据 = $id") } } **(2)如果抽象类中有抽象的方法,如何动态混入特质?** object TraitTest { def main(args: Array[String]): Unit = { // 具有抽象方法的抽象类,如何动态混入特质 val mysql = new MysqlDB with T { override def sayOk(): Unit = { println("具有抽象方法的抽象类,如何动态混入特质") } } mysql.insert(111) mysql.sayOk() } } abstract class MysqlDB{ def sayOk() } trait T { def insert(id: Int): Unit = { println("插入数据 = " + id) } } ### E、叠加特质 ### 构建对象的同时**如果混入多个特质,称之为叠加特质**, ***那么特质声明顺序从左到右,方法执行顺序从右到左。*** **(1)叠加特质时,对象的构建顺序,和执行方法的顺序分析** object TraitTest { def main(args: Array[String]): Unit = { // 混入多个特质的特点 // 1、当动态混入多个特质时,顺序是怎么样子的 // Scala在叠加特质的时候,会首先从后面的特质开始执行(从右向左) /** T_1 ... * data_T ... * DB_T ... * File_T ... */ val mysql = new Mysql with DB_T with File_T println("======================") /*** File_T 插入数据 = 111 * DB_T 插入数据 = 111 */ // 2、当执行一个动态混入对象的方法,其执行顺序是,从右向左开始执行(栈) // 所以先输出 File_T 插入数据 = 111 // 当执行到 super 时,是指的左边的特质 DB_T,若没有调用super方法,则不会像后执行(即执行完毕) // 若执行super时,左边没有特质了,super指的当前特质的父类 mysql.insert(111) } } /**类*/ class Mysql{} /**多个特质*/ trait T_1 { println("T_1 ...") def insert(id: Int): Unit = { println("T_1 插入数据 = " + id) } } trait Data_T extends T_1{ println("data_T ...") override def insert(id: Int): Unit = { println("data_T 插入数据 = " + id) } } trait DB_T extends Data_T{ println("DB_T ...") override def insert(id: Int): Unit = { println("DB_T 插入数据 = " + id) super.insert(id) } } trait File_T extends Data_T{ println("File_T ...") override def insert(id: Int): Unit = { println("File_T 插入数据 = " + id) //调用了insert方法,这里的super和混入特质的顺序有关系。不一定指的是父特质data_T super.insert(id) } } **(2)叠加特质注意事项和细节** * 特质声明顺序从左到右。 * Scala在执行叠加对象的方法时,会首先从后面的特质(从右向左)开始执行 * Scala中特质中如果调用super,并不是表示调用父特质的方法,而是向前面(左边)继续查找特质,如果找不到,才会去父特质查找 * 如果想要调用具体特质的方法,可以指定:super\[特质\].xxx(…).其中的泛型必须是该特质的直接超类类型 ### F、在特质中重写抽象方法特例 ### **(1)下述代码运行时会报错** trait T_1 { def insert(id: Int) } trait Data_T extends T_1{ def insert(id: Int): Unit = { println("data_T 插入数据 = " + id) super.insert(id) } } **(2)原因:**没有完全的实现insert,同时还没有声明 abstract overrid **(3)解决方法** * a、去掉 super() * b、调用父特质的抽象方法,那么在实际使用时,没有方法的具体实现,**无法编译通过**,为了避免这种情况的发生。***可重写抽象方法,***这样在使用时,**就必须考虑动态混入的顺序问题。** object TraitTest { def main(args: Array[String]): Unit = { // 尝试去掉Data_T中方法insert中调用的super.insert(id),看会发现什么 val mysql = new Mysql with DB_T with Data_T mysql.insert(666) //下面代码会报错,和动态混入顺序有关,执行到Data_T时中的方法 super.insert(id)会取找到父类的抽象方法 val mysql2 = new Mysql with Data_T with DB_T } } /**类*/ class Mysql{} /**特质*/ trait T_1 { def insert(id: Int) } trait Data_T extends T_1{ //若 // 1、在子特质中重写父特质的方法 // 2、同时又调用了super方法, // 则必须使用 abstract override 修饰 // 作用:这时super.insert(id)的调用和动态混入的顺序有关系(参考上面内容) abstract override def insert(id: Int): Unit = { println("data_T 插入数据 = " + id) super.insert(id) } } //实现T_1中的抽象方法 trait DB_T extends T_1{ def insert(id: Int): Unit = { println("DB_T 插入数据 = " + id) } } **(4)如何理解abstract override** 可以这里理解,当我们给某个方法增加了abstract override 后,就是明确的告诉编译器,该方法确实是重写了父特质的抽象方法,但是重写后,该方法仍然是一个抽象方法(因为没有完全的实现,**需要其它特质继续实现\[通过混入顺序\]**) ### G、富接口特质、特质中的具体字段和抽象字段 ### **富接口:**即该特质中***既有抽象方法,又有非抽象方法*** **具体字段**:特质中可以定义**具体字段**,如果初始化了就是具体字段,如果不初始化就是抽象字段。**混入该特质的类就具有了该字段**,**字段不是继承,而是直接加入类,成为自己的字段,可以通过编译后的代码查看。** **抽象字段:**特质中未被初始化的字段在具体的子类中必须被重写。 object TraitTest { def main(args: Array[String]): Unit = { val mysql = new Mysql with T { override var age = 0 } println(mysql.age) } } class Mysql{} /**特质*/ trait T { var name :String = "" var age :Int def insert():Unit={ } } ### H、特质构造顺序 ### 特质也是有构造器的,构造器中的内容由“字段的初始化” 和一些其他语句构成。具体实现请参考“特质叠加” **(1)第一种特质构造顺序(声明类的同时混入特质)** * 调用当前类的超类构造器 * 第一个特质的父特质构造器 * 第一个特质构造器 * 第二个特质构造器的父特质构造器, 如果已经执行过, 就不再执行 * 第二个特质构造器 * .......重复4,5的步骤(如果有第3个,第4个特质) * 当前类构造器 **第2种特质构造顺序(在构建对象时,动态混入特质)** * 调用当前类的超类构造器 * 当前类构造器 * 第一个特质构造器的父特质构造器 * 第一个特质构造器. * 第二个特质构造器的父特质构造器, 如果已经执行过,就不再执行 * 第二个特质构造器 * .......重复5,6的步骤(如果有第3个,第4个特质) * 当前类构造器 object TraitTest { def main(args: Array[String]): Unit = { // 声明类时混入特质 val ff1 = new FF()//构造顺序 E...A...B....C....D....F.... println(ff1) println("=======================") // 动态混入特质 val ff2 = new KK() with CC with DD//构造顺序 E...K....A...B....C....D.... 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 { println("F....") } class KK extends EE { println("K....") } **(3)分析两种方式对构造顺序的影响** * 第1种方式实际是构建类对象, 在混入特质时,该对象还没有创建。 * 第2种方式实际是构造匿名子类,可以理解成在混入特质时,对象已经创建了。 ### I、扩展类的特质 ### 特质可以继承类,以用来拓展该类的一些功能 所有混入该特质的类,会自动成为那个特质所继承的超类的子类 trait LoggedException extends Exception{ def log(): Unit ={ println(getMessage) // 方法来自于Exception类 } } //UnhappyException 就是Exception的子类. class UnhappyException extends LoggedException{ // 已经是Exception的子类了,所以可以重写方法 override def getMessage = "错误消息!" } **如果混入该特质的类,已经继承了另一个类(A类),则要求A类是特质超类的子类,**否则就会出现了多继承现象,发生错误。 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3MyOTQ4NzgzMDQ_size_16_color_FFFFFF_t_70 4][] ### J、自身类型 ### **自身类型:**主要是为了**解决特质的循环依赖问题**,同时可以确保特质在不扩展某个类的情况下,依然可以做到**限制混入该特质的类的类型。** //class Console extends Logger {} //error,因为不是Exception或其子类 class Console extends Exception with Logger//正确, //Logger就是自身类型特质,当这里做了自身类型后 //等价于trait Logger extends Exception,主要是为了给编译器说明,要求混入该特质的对象也必须是Exception的子类 trait Logger { // 明确告诉编译器,我就是Exception,如果没有这句话,下面的getMessage不能调用 this: Exception => def log(): Unit ={ // 既然我就是Exception, 那么就可以调用其中的方法 println(getMessage) } } # 四、嵌套类 # 在Scala中,**你几乎可以在任何语法结构中内嵌任何语法结构。**如在类中可以再定义一个类,这样的类是嵌套类,其他语法结构也是一样。嵌套类类似于Java中的内部类。 ## A、Java内部类的简单回顾 ## 在Java中,一个类的内部又完整的嵌套了另一个完整的类结构。被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类。内部类**最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系** **(1)基本语法** class Outer{ 外部类 class Inner{ 内部类 } } class Other{ 外部其他类 } ## (2)Java内部类的分类 ## 从定义在外部类的成员位置上来看, * 1) 成员内部类(没用static修饰) * 2) 和静态内部类(使用static修饰), 定义在外部类局部位置上(比如方法内)来看: * 分为局部内部类(有类名) * 匿名内部类(没有类名) ## B、Scala嵌套类的使用 ## **(1)定义Scala 的成员内部类和静态内部类,并创建相应的对象实例。** object TraitTest { def main(args: Array[String]): Unit = { val outer1 : ScalaOuterClass = new ScalaOuterClass() val outer2 : ScalaOuterClass = new ScalaOuterClass() // Scala创建内部类的方式和Java不一样,将new关键字放置在前,使用 对象.内部类 的方式创建 // 对象.内部类 的方式创建,这里的语法可以看出在scala中,默认情况下,内部类实例和外部对象关联 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)在内部类中访问外部类的属性。** a、内部类如果想要访问外部类的属性,可以通过外部类对象访问。 即:访问方式:外部类名.this.属性名 object TraitTest { def main(args: Array[String]): Unit = { //调用成员内部类的方法 val outer = new ScalaOuterClass val inner = new outer.ScalaInnerClass inner.info() } } class ScalaOuterClass { var name : String = "scott" private var sal : Double = 1.2 class ScalaInnerClass { //成员内部类 def info() = { // 访问方式:外部类名.this.属性名 // 怎么理解 ScalaOuterClass.this 就相当于是 ScalaOuterClass 这个外部类的一个实例, // 然后通过 ScalaOuterClass.this 实例对象去访问 name 属性 // 只是这种写法比较特别,学习java的同学可能更容易理解 ScalaOuterClass.class 的写法. println("name = " + ScalaOuterClass.this.name + " age =" + ScalaOuterClass.this.sal) } } } object ScalaOuterClass { //伴生对象 class ScalaStaticInnerClass { //静态内部类 } } **b、内部类如果想要访问外部类的属性,也可以通过外部类别名访问(推荐)。 即:访问方式:外部类名别名.属性名** object TraitTest { def main(args: Array[String]): Unit = { //调用成员内部类的方法 val outer = new ScalaOuterClass val inner = new outer.ScalaInnerClass inner.info() } } class ScalaOuterClass { myOuter => //这样写,你可以理解成这样写,myOuter就是代表外部类的一个对象. // 当给外部指定别名时,需要将外部类的属性放到别名后. var name : String = "scott" private var sal : Double = 1.2 class ScalaInnerClass { //成员内部类 def info() = { println("name = " + ScalaOuterClass.this.name + ", age =" + ScalaOuterClass.this.sal) println("-----------------------------------") println("name = " + myOuter.name + ", age =" + myOuter.sal) } } } object ScalaOuterClass { //伴生对象 class ScalaStaticInnerClass { //静态内部类 } } ## C、类型投影 ## **类型投影是指:**在方法声明上,如果使用 外部类\#内部类 的方式,表示忽略内部类的对象关系,等同于Java中内部类的语法操作,我们将这种方式称之为 类型投影(即:忽略对象的创建方式,只考虑类型) object ShadowTest { def main(args: Array[String]): Unit = { val outer1 : ScalaOuterClass = new ScalaOuterClass() val outer2 : ScalaOuterClass = new ScalaOuterClass() val inner1 = new outer1.ScalaInnerClass() val inner2 = new outer2.ScalaInnerClass() inner1.test(inner1) // ok, 因为 需要outer1.ScalanInner // 默认情况下,scala的内部类实例是和创建该内部类实例的外部类对象关联的(即一一对应的) inner1.test(inner2) // error, 需要outer1.ScalanInnerouter2.ScalanInner,使用类型投影后就不会报错 } } class ScalaOuterClass { myOuter => class ScalaInnerClass { //成员内部类 // 下面这个方法可以接收又外部类派生出来的任何内部类 ScalaInnerClass 实例 // 下面的 ScalaOuterClass#ScalaInnerClass 类型投影的作用就是屏蔽外部对象对内部类对象的影响 def test(ic: ScalaOuterClass#ScalaInnerClass): Unit = { System.out.println("使用类型投影 :"+ic) } } } [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3MyOTQ4NzgzMDQ_size_16_color_FFFFFF_t_70]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/15/67e4c4fdd22842cc8cda9913f18f1cdc.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3MyOTQ4NzgzMDQ_size_16_color_FFFFFF_t_70 1]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/15/f600bc1d7f854ec3895f7202508e2ea1.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3MyOTQ4NzgzMDQ_size_16_color_FFFFFF_t_70 2]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/15/42ba95859bfe4f1a8f894ba27a492595.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3MyOTQ4NzgzMDQ_size_16_color_FFFFFF_t_70 3]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/15/a263fa07c4bc44069ecf0b6c0c0a323a.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3MyOTQ4NzgzMDQ_size_16_color_FFFFFF_t_70 4]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/15/5c937617f23a44558d8144ab075c8f8d.png
还没有评论,来说两句吧...