Scala核心编程(八)面向对象编程(高级特性)

发布时间:2026/5/28 23:36:32

Scala核心编程(八)面向对象编程(高级特性) 一、静态属性和静态方法1.1 提出问题有一类经典问题一群小孩在玩堆雪人不时有新的小孩加入如何使用面向对象的思想编写程序知道现在共有多少人在玩核心需求需要一个所有对象共享的计数变量。1.2 回顾Java的静态概念在Java中我们使用static关键字publicstatic返回值类型 方法名(参数列表){方法体}// 静态属性...说明Java中静态方法并不是通过对象调用的而是通过类对象调用的所以静态操作并不是面向对象的。1.3 Scala中的伴生对象Scala语言是完全面向对象万物皆对象的语言所以并没有静态的操作即在Scala中没有静态的概念。但是为了和Java语言交互产生了一种特殊的对象来模拟类对象称之为类的伴生对象。这个类的所有静态内容都可以放置在它的伴生对象中声明和调用。1.4 伴生对象的快速入门classScalaPerson{varname:String_// 伴生类}objectScalaPerson{varsex:Booleantrue// 伴生对象}// 调用println(ScalaPerson.sex)1.5 伴生对象小结Scala中伴生对象采用object关键字声明伴生对象中声明的全是**静态内容可以通过伴生对象名称**直接调用。伴生对象对应的类称之为伴生类伴生对象的名称应该和伴生类名一致。伴生对象中的属性和方法都可以通过伴生对象名(类名)直接调用访问。从语法角度来讲伴生对象其实就是类的静态方法和成员的集合。从技术角度来讲Scala还是没有生成静态的内容只不过是将伴生对象生成了一个新的类实现属性和方法的调用。从底层原理看伴生对象实现静态特性是依赖于public static final MODULE$实现的。伴生对象的声明应该和伴生类的声明在同一个源码文件中如果不在同一个文件中会运行错误。如果class A独立存在那么A就是一个类如果object A独立存在那么A就是一个**静态性质的对象**即类对象。当一个文件中存在伴生类和伴生对象时文件的图标会发生变化。1.6 伴生对象解决小孩游戏问题classChild{varname:String_}objectChild{vartotal:Int0defjoin(child:Child):Unit{total1println(s${child.name}加入了游戏)}}1.7 伴生对象-apply方法在伴生对象中定义apply方法可以实现**类名(参数)**方式来创建对象实例classCat(cName:String){varname:StringcName}objectCat{// apply方法是当我们创建对象时可以通过Cat()调用defapply():Cat{println(apply被调用)newCat(xx)}defapply(name:String):Cat{println(apply被调用)newCat(name)}}// 使用valcat1Cat()// 调用无参applyvalcat2Cat(Tom)// 调用有参apply1.8 课堂练习题目将以下Java题用Scala完成。在Frock类中声明私有的静态属性currentNum初始值为100000作为衣服出厂的序列号起始值。声明公有的静态方法getNextNum作为生成上衣唯一序列号的方法。每调用一次将currentNum增加100并作为返回值。在TestFrock类的main方法中分两次调用getNextNum方法获取序列号并打印输出。在Frock类中声明serialNumber序列号属性并提供对应的get方法。在Frock类的构造器中通过调用getNextNum方法为Frock对象获取唯一序列号赋给serialNumber属性。在TestFrock类的main方法中分别创建三个Frock对象并打印三个对象的序列号验证是否为按100递增。二、特质trait2.1 回顾Java接口// 声明接口interface接口名// 实现接口class类名implements接口名1,接口2Java接口特点一个类可以实现多个接口接口之间支持多继承接口中属性都是常量接口中的方法都是抽象的2.2 Scala接口的介绍从面向对象来看接口并不属于面向对象的范畴Scala是纯面向对象的语言在Scala中没有接口。Scala语言中采用**特质trait特征**来代替接口的概念。也就是说多个类具有相同的特征时就可以将这个特质独立出来采用关键字trait声明。理解trait等价于 (interface abstract class)2.3 trait的声明trait特质名{// trait体}trait命名一般首字母大写例如Cloneable,SerializableobjectT1extendsSerializable{// Serializable就是Scala的一个特质// 在Scala中Java中的接口可以当做特质使用}2.4 Scala中trait的使用一个类具有某种特质就意味着这个类满足了这个特质的所有要素。使用时采用extends关键字如果有多个特质或存在父类需要采用with关键字连接。没有父类class类名extends特质1with特质2with特质3..有父类class类名extends父类with特质1with特质2with特质32.5 特质的快速入门案例可以把特质看作是对继承的一种补充。Scala的继承是单继承也就是一个类最多只能有一个父类。引入trait特征第一可以替代Java的接口第二也是对单继承机制的一种补充traittrait1{// 声明方法抽象的defgetConnect(user:String,pwd:String):Unit}classA{}classBextendsA{}classCextendsAwithtrait1{overridedefgetConnect(user:String,pwd:String):Unit{println(c连接mysql)}}classD{}classEextendsDwithtrait1{defgetConnect(user:String,pwd:String):Unit{println(e连接oracle)}}classFextendsD{}2.6 特质trait的再说明Scala提供了特质trait特质可以同时拥有抽象方法和具体方法一个类可以实现/继承多个特质。特质中没有实现的方法就是抽象方法。类通过extends继承特质通过with可以继承多个特质。所有的Java接口都可以当做Scala特质使用。traitLogger{deflog(msg:String)// 抽象方法}classConsoleextendsLoggerwithCloneablewithSerializable{deflog(msg:String){println(msg)}}2.7 带有具体实现的特质和Java中的接口不太一样的是特质中的方法并不一定是抽象的也可以有非抽象方法即实现了的方法。traitOperate{definsert(id:Int):Unit{println(保存数据id)}}traitDBextendsOperate{overridedefinsert(id:Int):Unit{print(向数据库中)super.insert(id)}}classMySQLextendsDB{}2.8 带有特质的对象动态混入除了可以在类声明时继承特质以外还可以在构建对象时混入特质扩展目标类的功能。此种方式也可以应用于对抽象类功能进行扩展。动态混入是Scala特有的方式Java没有动态混入可在不修改类声明/定义的情况下扩展类的功能非常的灵活耦合性低。动态混入可以在不影响原有的继承关系的基础上给指定的类扩展功能。traitOperate3{definsert(id:Int):Unit{println(插入数据 id)}}classOracleDB{}abstractclassMySQL3{}// 动态混入varoraclenewOracleDBwithOperate3 oracle.insert(999)valmysqlnewMySQL3withOperate3 mysql.insert(4)思考如果抽象类中有抽象的方法如何动态混入特质2.9 创建对象的几种方式在Scala中创建对象共有几种方式new对象apply创建匿名子类方式动态混入2.10 叠加特质基本介绍构建对象的同时如果混入多个特质称之为叠加特质。特质声明顺序从左到右方法执行顺序从右到左。traitOperate4{println(Operate4...)definsert(id:Int)}traitData4extendsOperate4{println(Data4)overridedefinsert(id:Int):Unit{println(插入数据 id)}}traitDB4extendsData4{println(DB4)overridedefinsert(id:Int):Unit{print(向数据库)super.insert(id)}}traitFile4extendsData4{println(File4)overridedefinsert(id:Int):Unit{print(向文件)super.insert(id)}}classMySQL4{}valmysqlnewMySQL4withDB4withFile4 mysql.insert(888)叠加特质注意事项和细节特质声明顺序从左到右。Scala在执行叠加对象的方法时会首先从**后面的特质从右向左**开始执行。Scala中特质中如果调用super并不是表示调用父特质的方法而是向前面左边继续查找特质如果找不到才会去父特质查找。如果想要调用具体特质的方法可以指定super[特质].xxx(...)其中的泛型必须是该特质的直接超类类型。traitFile4extendsData4{println(File4)overridedefinsert(id:Int):Unit{print(向文件)super[Data4].insert(id)// 指定调用Data4的insert}}2.11 在特质中重写抽象方法提出问题traitOperate5{definsert(id:Int)}traitFile5extendsOperate5{definsert(id:Int):Unit{println(将数据保存到文件中..)super.insert(id)// 报错}}运行代码会出错原因就是没有完全的实现insert同时还没有声明abstract override。解决问题方式1去掉super()…方式2调用父特质的抽象方法那么在实际使用时没有方法的具体实现无法编译通过。为了避免这种情况的发生可重写抽象方法这样在使用时就必须考虑动态混入的顺序问题。traitOperate5{definsert(id:Int)}traitFile5extendsOperate5{abstractoverridedefinsert(id:Int):Unit{println(将数据保存到文件中..)super.insert(id)}}traitDB5extendsOperate5{definsert(id:Int):Unit{println(将数据保存到数据库中..)}}classMySQL5{}valmysql5newMySQL5withDB5withFile5理解 abstract override 的小技巧当我们给某个方法增加了abstract override后就是明确地告诉编译器该方法确实是重写了父特质的抽象方法但是重写后该方法仍然是一个抽象方法因为没有完全的实现需要其它特质继续实现[通过混入顺序]。重写抽象方法时需要考虑混入特质的顺序问题和完整性问题varmysql2newMySQL5withDB5// okmysql2.insert(100)varmysql3newMySQL5withFile5// errorvarmysql4newMySQL5withFile5withDB5// errorvarmysql4newMySQL5withDB5withFile5// ok2.12 当作富接口使用的特质富接口即该特质中既有抽象方法又有非抽象方法。traitOperate{definsert(id:Int)// 抽象defpageQuery(pageno:Int,pagesize:Int):Unit{// 实现println(分页查询)}}2.13 特质中的具体字段特质中可以定义具体字段如果初始化了就是具体字段如果不初始化就是抽象字段。混入该特质的类就具有了该字段字段不是继承而是直接加入类成为自己的字段。traitOperate6{varopertype:String// 抽象字段}traitDB6extendsOperate6{varopertype:Stringinsert// 具体字段definsert():Unit{}}classMySQL6{}varmysqlnewMySQL6withDB6 println(mysql.opertype)// insert2.14 特质中的抽象字段特质中未被初始化的字段在具体的子类中必须被重写。2.15 特质构造顺序特质也是有构造器的构造器中的内容由**“字段的初始化”**和一些其他语句构成。第一种特质构造顺序声明类的同时混入特质调用当前类的超类构造器第一个特质的父特质构造器第一个特质构造器第二个特质构造器的父特质构造器如果已经执行过就不再执行第二个特质构造器…重复4,5的步骤如果有第3个第4个特质当前类构造器traitAA{println(A...)}traitBBextendsAA{println(B....)}traitCCextendsBB{println(C....)}traitDDextendsBB{println(D....)}classEE{println(E...)}classFFextendsEEwithCCwithDD{println(F....)}第二种特质构造顺序在构建对象时动态混入特质调用当前类的超类构造器当前类构造器第一个特质构造器的父特质构造器第一个特质构造器第二个特质构造器的父特质构造器如果已经执行过就不再执行第二个特质构造器…重复5,6的步骤当前类构造器分析两种方式对构造顺序的影响第1种方式实际是构建类对象在混入特质时该对象还没有创建。第2种方式实际是构造匿名子类可以理解成在混入特质时对象已经创建了。2.16 扩展类的特质特质可以继承类以用来拓展该类的一些功能。traitLoggedExceptionextendsException{deflog():Unit{println(getMessage())// 方法来自于Exception类}}所有混入该特质的类会自动成为那个特质所继承的超类的子类。// UnhappyException 就是Exception的子类classUnhappyExceptionextendsLoggedException{// 已经是Exception的子类了所以可以重写方法overridedefgetMessage错误消息}注意如果混入该特质的类已经继承了另一个类A类则要求A类是特质超类的子类否则就会出现多继承现象发生错误。// 正确因为 IndexOutOfBoundsException 是 LoggedException特质的超类Exception的子类classUnhappyException2extendsIndexOutOfBoundsExceptionwithLoggedException{overridedefgetMessage错误信息}classCCC{}// 错误因为 CCC 不是 LoggedException特质的超类Exception的子类classUnhappyException3extendsCCCwithLoggedException{// 编译错误overridedefgetMessage错误信息}2.17 自身类型说明自身类型主要是为了解决特质的循环依赖问题同时可以确保特质在不扩展某个类的情况下依然可以做到限制混入该特质的类的类型。// Logger就是自身类型特质traitLogger{// 明确告诉编译器我就是Exception// 如果没有这句话下面的getMessage不能调用this:Exceptiondeflog():Unit{// 既然我就是Exception那么就可以调用其中的方法println(getMessage)}}// 对吗classConsoleextendsLogger{}// 错误// 对吗classConsoleextendsExceptionwithLogger{}// 正确三、嵌套类3.1 基本介绍在Scala中你几乎可以在任何语法结构中内嵌任何语法结构。如在类中可以再定义一个类这样的类是嵌套类其他语法结构也是一样。嵌套类类似于Java中的内部类。面试题Java中类共有五大成员属性方法内部类构造器代码块3.2 Java内部类的简单回顾在Java中一个类的内部又完整的嵌套了另一个完整的类结构。被嵌套的类称为内部类inner class嵌套其他类的类称为外部类。内部类最大的特点就是可以直接访问私有属性并且可以体现类与类之间的包含关系。classOuter{// 外部类classInner{// 内部类}}classOther{// 外部其他类}3.3 Java内部类的分类从定义在外部类的成员位置上来看成员内部类没用static修饰静态内部类使用static修饰定义在外部类局部位置上比如方法内来看局部内部类有类名匿名内部类没有类名3.4 Scala嵌套类的使用定义成员内部类和静态内部类classScalaOuterClass{classScalaInnerClass{// 成员内部类}}objectScalaOuterClass{// 伴生对象classScalaStaticInnerClass{// 静态内部类}}创建对象valouter1:ScalaOuterClassnewScalaOuterClass()valouter2:ScalaOuterClassnewScalaOuterClass()// Scala创建内部类的方式和Java不一样将new关键字放置在前// 使用 对象.内部类 的方式创建valinner1newouter1.ScalaInnerClass()valinner2newouter2.ScalaInnerClass()// 创建静态内部类对象valstaticInnernewScalaOuterClass.ScalaStaticInnerClass()3.5 在内部类中访问外部类的属性方式1通过外部类对象访问访问方式外部类名.this.属性名classScalaOuterClass{varname:Stringscottprivatevarsal:Double1.2classScalaInnerClass{// 成员内部类definfo(){// 访问方式外部类名.this.属性名println(name ScalaOuterClass.this.name age ScalaOuterClass.this.sal)}}}ScalaOuterClass.this相当于是ScalaOuterClass这个外部类的一个实例。方式2通过外部类别名访问推荐访问方式外部类名别名.属性名classScalaOuterClass{myOuter// 这样写myOuter就是代表外部类的一个对象classScalaInnerClass{// 成员内部类definfo(){println(name ScalaOuterClass.this.name age ScalaOuterClass.this.sal)println(name myOuter.name age myOuter.sal)}}// 当给外部指定别名时需要将外部类的属性放到别名后varname:Stringscottprivatevarsal:Double1.2}外部类名.this等价于外部类名别名3.6 类型投影问题引出classScalaOuterClass3{myOuterclassScalaInnerClass3{// 成员内部类deftest(ic:ScalaInnerClass3):Unit{System.out.println(ic)}}}valouter1:ScalaOuterClass3newScalaOuterClass3()valouter2:ScalaOuterClass3newScalaOuterClass3()valinner1newouter1.ScalaInnerClass3()valinner2newouter2.ScalaInnerClass3()inner1.test(inner1)// ok因为需要outer1.ScalaInnerinner1.test(inner2)// error需要outer1.ScalaInner而不是outer2.ScalaInner分析Java中的内部类从属于外部类因此在Java中inner.test(inner2)就可以因为是按类型来匹配的。Scala中内部类从属于外部类的对象所以外部类的对象不一样创建出来的内部类也不一样无法互换使用。解决方式 - 使用类型投影类型投影是指在方法声明上如果使用外部类#内部类的方式表示忽略内部类的对象关系等同于Java中内部类的语法操作。即忽略对象的创建方式只考虑类型。classScalaOuterClass3{myOuterclassScalaInnerClass3{// 使用类型投影deftest(ic:ScalaOuterClass3#ScalaInnerClass3):Unit{System.out.println(ic)}}}总结本章深入讲解了Scala面向对象编程的三大高级特性特性核心要点伴生对象用object模拟静态实现apply工厂方法特质(trait)替代接口支持具体实现、动态混入、叠加特性嵌套类类型投影解决内部类类型绑定问题掌握这些特性你就能写出更加优雅、灵活的Scala代码

相关新闻