Scala面向对象程序编程
1 Scala类(重点)
1.1类定义
class HELLOWORLD(){
//属性
private val value1 = "HELLO"
var value2 = "WORLD"
//方法
def add()={
println(value1+value2)
}
def plus(m:Char)=value2+m
}
类成员主要包括字段(val跟var)、方法与函数(def),但Scala禁止使用同样的名称命名字段和方法,即既声明一个value字段,又声明一个value方法是不允许的。
类成员可见性有两种,private(私有)跟public(公有),private需要声明,public无需额外声明。
类声明后利用new声明对象 val one= new HELLOWORLD
实例的操作
val a=one.value1
val b=one.value2
one.value2=one.plus(H)
one.add()
调用无参方法时可以不带()。
one.add
若类中声明无参方法时不带(),实际调用也不可带()。
...def add{ println(value1+value2) }...
one.add()
1.2 getter、setter
Scala对每个类中的字段都提供getter和setter方法。
对于公有字段来说,其getter和setter方法同样是共有的,对于私有字段来说,则是私有的。
var声明的字段带有getter和setter方法(读写)。
val声明的字段自带有getter方法(只读)。
对于字段value1,其getter形式为value1,并没有setter方法。
对于字段value2,其getter和setter方法形式为value2和value2_=
实际使用时,在类定义外,getter和setter方法使用是一致的,形如one.value2。
getter方法与setter方法的意义在于控制类中私有对象的数据。
在类中可以通过重定义getter和setter方法获取、有限制的修改私有字段。
class HELLOWORLD{
private var privatevalue1 = "HELLO"
var value2 = "WORLD"
def add() {
println(value1+privatevalue2)
}
def plus(m:Char)=value2+m
def value1 = privatevalue1
def value1_ = (newvalue1:String) {
//若新的字段比原私有字段长,则更改,否则保持私有字段不变
if(newvalue1.length>privatevalue1.length)
privatevalue1=newvalue1
}
}
1.3主构造器(primary constructor)
每个类都有主构造器,且与类定义交织在一起。
1.主构造器的参数直接放置在类名之后。
class HELLOWORLD (val value1:String,var value2:String){...}
主构造器的参数被编译成字段,并在构造对象时初始化传入。
一个类若没有显式定义主构造器自动拥有一个无参主构造器。
2.若类中有直接执行的语句(非定义的方法、函数),每次构造对象时皆会执行一次,不论什么样的构造器类型。
如:
class HELLOWORLD (val value1:String,var value2:String) {
println("HELLOWORLD IS CREATED")
val value3=value1+value2
}
val two = new HELLOWORLD("WELCOME","HOME")
3.主构造器的参数一般有四种:
value:String 生成对象私有字段,对象中没有方法使用value,则没有该字段。
private val/var value:String 私有字段,私有的getter/setter方法。
val/var value:String 私有字段,公有的getter/setter方法。
@BeanProperty val/var value:String 私有字段,共有的Scala和JavaBean的getter/setter方法。
4.主构造器私有化
class HELLOWORD private(主构造器){ 类成员 }
主构造器私有,只能通过辅助构造器构造对象。
1.4辅助构造器(auxiliary constructor)
Scala类能有任意多的辅助构造器
辅助构造器的名称为this,在类中定义
辅助构造器必须以一个主构造器或其他已定义的辅助构造器调用开始
class HELLOWORLD{
private var value1=" "
private var value2=" "
def this(m:String){
this() //调用主构造器
this.value1=m
}
def this(m:String,n:String){
this(m) //调用已定义的辅助构造器
this.value2=n
}
}
1.5嵌套类(了解)
Scala允许任何语法结构中镶嵌任何语法结构,因此能在类中定义类。
class HELLOWORLD{
class HI{....}
}
对于同一个外部类,不同实例下的内部类是不同的。
形如val three = new HELLOWORLD与val four = new HELLOWORLD
three.HI与four.HI是两个不同的类
内部类中可以调用外部类的成员,利用外部类.this或指针实现。
class HELLOWORLD{
pointto => var value2= " "
class HI{
val value3=HELLOWORLD.this.value2
var value4=pointto.value2
}
}
eg:
import scala.collection.mutable.ArrayBuffer;
class NetWork{
class Member(val name:String){
val contacts=new ArrayBuffer[Member]
}
private val members=new ArrayBuffer[Member];
def join(name:String)={
val m=new Member(name);
member += m;
m
}
}
考虑有如下两个网络:
val chatter=new NetWork;
val myFace=bew NetWork;
val fred=chatter.join("Fred");
val wilma=chatter.join("Wilma");
fred.contacts += wilma; // OK
val barney=myFace.join("Barney");
//不可以这样做——————不能将一个myFace.Member添加到chatter.Member元素缓冲中。
fred.contacts += barney;
2 Scala对象(重点)
2.1单例对象
scala没有静态方法或静态字段,要想实现类似于这种功能,可以借助于单例对象。
object语法定义了某个类的单个实例。
对象的构造器在该对象第一次被使用时调用。
object语法结构与class大致相同,除了object不能提供构造器参数。
通常使用单例对象的环境:
作为存放工具函数或常量的地方;
共享单个不可变实例 ;
利用单个实例协调某个服务。
2.2伴生对象(重点)
既有实例方法又有静态方法的类,借助于伴生对象。
当一个单例对象存在同名类的时候,称为伴生对象。
class HELLOWORLD{...}
object HELLOWORLD{...}
类和其伴生对象可以互相访问私有属性,但必须存在同一个源文件中
类的伴生对象可以被访问,但并不在作用域中,如;
class HELLOWORLD{...}
object HELLOWORLD{ def NOW{...} }
HELLOWORLD 类必须通过HELLOWORLD.NOW调用伴生对象中的NOW方法,而不能直接用NOW来调用。
2.3样例类case class
对需要伴生对象的类使用case关键字进行了简单封装。
样例类的格式:case class 类名(args1:T1,args2:T2){}
注意:
1.主构造器的()无参时不可以省略
2.参数列表中参数默认是被val关键字修饰
3.被case定义的类,自动提供了22个方法。经常用到的toString hashcode equals copy apply unapply
4.apply 构建对象该方法可以通过统一对象构建原则构建对;
unapply 模式匹配时,将对象匹配绑定成属性。
5.样例类中所具有的功能,我们都能在伴生类,伴生对象中自己实现。
2.4扩展类或者特质的对象
一个object可以扩展类以及一个或多个特质,其结果是一个扩展了指定类以及特质的类的对象,同时拥有在对象定义中给出的所有特性。
eg:一个有用的场景就是给出可被共享的缺省对象。
举例来说,考虑在程序中引入一个可撤销动作的类;
abstract class UndoableAction(val description:String){
def undo():Unit;
def redo():Unit;
}
默认情况下,可以是"什么都不做",当然,对于这个行为我们只需要一个实例即可。
object DoNothingAction extends UndoableAction("Do nothing"){
override def undo(){}
override def redo(){}
}
DoNothingAction对象可以被所有需要这个缺省行为的地方共用。
val actions=Map(“open”->DoNothingAction,“save”->DoNothingAction)//打开和保存功能尚未实现。
2.5 apply方法
我们通常会定义和使用对象的apply方法。当遇到如下表达式时,apply方法就会被调用:
Object(参数1,参数2,…,参数N)
通常,这样一个apply方法,返回的是伴生类的对象。
对于嵌套表达式,省去new关键字会方便好多。
eg:
val arrs=Array(Array(1,7),Array(2.9));
需要构造有参数需求的伴生对象时,可定义并使用apply方法。
class HELLOWORLD(var m:String,n:Char){...}
object HELLOWORLD{
def apply(n:Char)=new HELLOWORLD(" ",n)
}
val hi=HELLOWORLD('j')
3 Scala继承(理解)
3.1抽象类
1.不能被实例的类叫做抽象类。
2.抽象类的某个或某几个成员没有被完整定义,这些没有被完整定义的成员称为抽象方法或抽象字段。
3.用abstract保留字标记抽象类。
4.只要类中有任意一个抽象成员,必须使用abstract标记。
5.重写抽象方法、抽象字段不需要使用override保留字。
eg:
abstract class year{
val name:Array[String] //抽象的val,带有一个抽象的getter方法
var num:Int //抽象的var,带有抽象的getter/setter方法
def sign //没有方法体/函数体,是一个抽象方法
}
abstract class Father() {
//抽象属性
val name:String
//具体属性
var age=0
//抽象方法
[abstract] def methodName():ReturnType
//具体方法
def test(args:T1):Int={}
}
实现类:
class Son extends Father{
//子类重写 父类中的抽象方法
[override] def methodName():ReturnType={}
//子类重写 父类中的抽象属性
[override] val name:String="tom"
//子类重写 父类中的具体方法
override def test(args:T1):Int={}
override var age=10
}
3.2重写
1.在Scala中重写一个非抽象方法必须使用override修饰符。
class week extends month{ override def firstday = {...} }
override保留字实际使用类似与private,声明这个保留字后的定义、声明是对超类的重写,因此,其也可以写在类定义的参数中。
class week(override val lastday:String) extends month{...}
2.子类的重写或修改Scala会检查其超类,但是,超类的修改并不会检查其子类。
3.重写包括字段和方法,但参数不同的方法可以不重写。
class month{
def secondday(m:String)={...}
}
class week extends month{
def secondday ={...}
}
4.在Scala中调用超类的方法和Java一样,使用super关键字。
5.只有主构造器可以调用超类的主构造器。
class Employee(name:String,age:Int,val salary:Double) extends Person(name:String,age:Int)
6.重写规则:
重写 def
用val :利用val能重写超类用没有参数的方法(getter)。
用def:子类的方法与超类方法重名。
重写val
用val:子类的一个私有字段与超类的字段重名,getter方法重写超类的getter方法。
重写var
用var:且当超类的var是抽象的才能被重写,否则超类的var都会被继承。
eg:
abstract class month{
val one = 25 //可在子类中用val重写
var two = 15 //不可在子类中用var重写,因为不是抽象的
var three:Int
def firstday = //可在子类中用val/def重写
def lastday(m:Char)={} //可在子类中用def重写
}
3.3构造顺序(难点)
1.子类构造器的运行在超类构造器运行之后。
2.在超类的构造器中调用的成员被子类重写后,返回值可能不正确。
class month{
val num = 31
val days = new Array[Int](num)
}
class week extends month{
override val num = 7
}
val a=new week
构造week对象前先执行month的构造器,num被初始化为31,month为初始化days 数组,调用num,但num被子类week重写了,但因为week构造器还没被调用,此时 num的值未被初始化,因而返回0,days被设置为长度为0的数组,month构造器运行 完毕,执行week构造器,num被初始化为7。
3.解决方法:
3.1将超类的val声明为final
3.2将超类的val声明为lazy
lazy val days = new Array[Int](num)
或者
lazy val num = 31
override lazy val num = 7
3.3在子类中使用提前定义语法
提前定义:在超类的构造器运行之前初始化子类的字段。
把需要提前定义的语句块放在extends与超类之间,并后接with保留字。
class week extends {override val num =7} with month{...}
提前定义中=右侧若需调用类中B成员时,除非B成员已在调用前提前定义。
class week extends{
override val num=7
override val num2=num+1 //允许,num已被提前定义
override val num4=num2+num3 //不允许,num3没有在之前提前定义
}with month{...}
4 Scala特质(重点)
包含:
抽象方法/属性
具体方法/属性
4.1多重继承
class A extends Father with T1 with T2
Scala不支持多重继承,取而代之的是特质。
一个子类只能拥有一个超类,一个超类能拥有多个子类。
即:class week extends month,year是不合法的。
为什么?
若一个子类继承自不同的超类,不同的超类中同名成员子类不知如何处理多重继承产生菱形继承问题。
解决多重继承可能导致的问题消耗的资源远比多重继承产生的价值高。
Scala使用特质达到类似多重继承的效果。
一个类可以扩展自一个或多个特质,一个特质可以被多个类扩展。
特质能限制被什么样的类所扩展。
4.2特质使用
特质是Scala里代码复用的基础单元,封装了方法和字段的定义。
特质的定义使用保留字trait,具体语法与类定义相似,除了不能拥有构造参数。
trait reset{
def reset(m:Int,n:Int)=if(m>=n) 1
}
一旦特质被定义了,就可以混入到类中。
class week extends reset {...}
当要混入多个特质时,利用with保留字。
class week extends reset with B with C {...}
特质 混入 类/对象/特质/实例(对象)。
除了在类定义中混入特质以外,还可以在特质定义中混入特质:
trait reseting extends reset{...}
在对象构造时混入特质。
val five = new week with reseting
总结:
1.在特质中重写特质中的抽象方法时,可以不用加override关键字。
2.多个特质重写同一个特质的同一个抽象方法时,后期再使用过程中必须加上override关键字。
3.在特质中重写特质中的抽象方法时,如果使用super调用父类的方法时,那么必须添加abstract和override关键字。
trait Logger{
def log(msg:String)
}
trait TimestampLogger extends Logger{
abstract override def log(msg:String){
super.log(new java.util.Date()+" "+msg )
}
}
4.3特质构造
1.特质的构造是有顺序的,从左到右被构造。
2.构造器按如下顺序构造:
超类
父特质
第一个特质
第二个特质(父特质不重复构造)
类
eg:
class SavingsAccount extends Account with FileLogger with ShortLogger{}
构造器按照如下的顺序执行:
1.Account
2.Logger
3.FileLogger
4.ShortLogger
5.SavingsAccount
3.线性化描述
说明:构造器的顺序是类的线性化的反向。
描述:线性化描述某个类型的所有超类型的一种技术规格。
规则定义:如果 C extends C1 with C2 with … with Cn,则lin© = C >> lin(cn)>> … >> lin(c2)>>lin(c1)
">>“是指"串接并去掉重复项,右侧胜出”
lin(SavingsAccount)
=SavingsAccount >> lin(ShortLogger)>> lin(FileLogger)>>lin(Account)
=SavingsAccount >> (ShortLogger >> Logger) >> (FileLogger >> Logger) >> lin(Account)
=SavingsAccount >> ShortLogger >> FileLogger >> Logger >> Account
省去了位于任何线性化末端的类型:ScalaObject,AnyRef,Any
线性化给出了在特质中super被解析的顺序。举例来说,ShortLogger中调用super会执行FileLogger的方法,而FileLogger中调用super执行Logger的方法。
4.4特质应用(难点)
1.特质的一个主要应用方面在于:富接口(根据类已有的方法自动为类添加方法,利用特质实现富接口)。
构造一个具有少量抽象方法和大量基于抽象方法的具体方法的特质.那么只要把特质混入类中,通过类重写抽象方法后,类便自动获得大量具体方法。
trait Logger{
def log(msg:String)
def warn(msg:String) {
log(“server”+msg)
}
def server(msg:String) {
log("server"+msg)
}
}
class Week extends Logger{
def log(msg:String){
println(msg)
}
server("HI")
}
2.特质的另一个应用方面在于:为类提供可堆叠的改变(super保留字)。
2.1当为类添加多个互相调用的特质时,从最后一个开始进行处理 。
2.2在类中super.foo()这样的方法调用是静态绑定的,明确是调用它的父类的foo()方法。
2.3在特质中写下了super.foo()时,它的调用是动态绑定的。调用的实现将在每一次特质被混入到具体类的时候才被决定。因此,特质混入的次序的不同其执行效果也就不同。
eg:
abstract class IntQueue {
def get(): Int;
def put(x: Int);
}
class BasicIntQueue extends IntQueue {
private val buf = new ArrayBuffer[Int]
def get() = buf.remove(0)
def put(x: Int) {
buf += x
}
}
trait Incrementing extends IntQueue {
abstract override def put(x: Int) {
super.put(x + 1)
}
}
trait Doubling extends IntQueue {
abstract override def put(x: Int) {
super.put(2 * x)
}
}
object TestClient extends App {
val queue1 = (new BasicIntQueue with Incrementing with Doubling)
queue1.put(2) //Doubling.put(2*2)->Incrementing.put(4+1)
println(queue1.get()) //result is 5
val queue2 = (new BasicIntQueue with Doubling with Incrementing)
queue2.put(2) //Incrementing.put(2+1)->Doubling.put(2*3)
println(queue2.get()) //result is 6
}
4.5特质的提前定义(难点)
特质的使用基本上与类一致,只是不能通过构造传参。
思考:构建一个文件日志生成特质,但文件名称根据混入的实例不同而不同。
trait FileLogger {
val fileName:String;
val out=new PrintStream(fileName);
def log(info:String)={
out.println(info);
out.flush();
}
}
接下来构建一个包含文件日志记录的实例。
val sa=new SavingAccount with FileLogger{
override val fileName="SavingAccountLog.txt"
}
但此时运行报错空指针异常(原因是构造器的执行顺序)。
解决办法:
1.懒值
lazy val out=new PrintStream(fileName);
2.提前定义
val sa=new {override val fileName="SavingAccountLog.txt"} with SavingAccount with FileLogger
4.6扩展类的特质(难点)
特质也可以扩展自类,那么该类自动变成所有混入该特质的超类。
trait LoggedException extends Exception
class HappyException extends LoggedException{} //编译正确,运行正确
class UnHappyException extends JFrame with LoggedException //编译正确,运行报错。
原因:类中显示父类必须与超类存在子父类之间的关系。
如何在编译阶段给出提示:
1.自身类型
作用:使用自身类型代替显示继承某一父类
格式:
this:类型 =>
eg:
trait LoggedException {
this:Exception =>
def log()
def warn():String
}
解释:后期只有Exception以及Exception的子类才可以混入该特质。
2.结构类型
作用:使用结构类型代替显示继承某一父类,用具有的方法来代替具体的某一类。
格式:
this:{ 方法 } =>
eg:
trait LoggedException {
this:{
def getMessage():String
def log(info:String){
println("log:"+info)
}
} =>
}