类
面向对象
抽象
定义一个类,实际上就是把一类事物的共有的属性和行为提取出来,形成一个物理模型。
面向对象编程的三大特征,封装,继承,多态
封装就是把抽象出的数据和对数据的操作封装在一起,数据被保护在内部,程序的其他部分只有通过授权的操作(成员方法),才能对数据进行操作
封装的理解和好处
隐藏实现细节
可以对数据进行验证,保证安全合理
如何体现封装
对类中的属性进行封装
通过成员方法,包实现封装
封装的步骤,将属性进行私有化,提供一个公共的get方法,用于对属性判断并赋值
def setXxx(参数名:类型):Unit={//加人数据验证的逻辑 属性=参数名}
定义类的属性
在scala中,类并不用声明为public scala源文件中可以包含多个类,所有这些类都具有公有可见性, 在scala中,类不用声明为public 如果没有定义构造器,类会有一个默认的无参构造器 var 修饰的变量,对外提供getter setter方法 val修饰的变量提供getter方法,没有setter方法 class Student { //用var修饰的变量,默认同时有公开的getter方法,和setter方法 //scala中声明一个属性,必须显示的初始化,然后根据初始化的数据的类型自动推断,属性类型可以省略 //_表示一个占位符,编译器会根据你变量的具体类型赋予相应的初始值 //使用占位符,类型必须指定 //类型 _对应的值 //Byte Short Int Long默认值为0 //Float Double默认值0.0 //String和应用类型,默认值null //Boolean 默认值false var name="tom" var nickName:String=_ //如果赋值为null,则一定要加类型,因为不加类型,那么该类型就是Null类型 var address=null //改为 var address:String=null //用val修饰的变量是只读属性的,只带getter方法但是没有setter方法 //相当于java中用final修饰的变量 //val修饰的变量不能使用占位符 val age =10 //类私有字段,有私有的getter方法和setter方法,只能在类内部使用 //其伴生对象也可以访问 private var hobby:String = "旅游" //对象私有字段,访问权限更加严格,Person类的方法只能访问到当前对象的字段 private[this] val cardInfo="123456" }
不同的兑现的属性是独立的,互不影响,一个队形对属性的更改,不影响另一个
自定义get和set方法
//自己手动创建的getter和setter方法需要遵循的原则 // 将成员私有化 // 字段属性名以“_”作为前缀,如定义:_x // getter方法定义为 def x=_x // setter方法定义时,方法名为属性名去掉前缀并加上后缀,后缀是“x_=” class Point{ private var _x = 0 private var _y = 0 private val bound = 100 def x = _x def x_ =(newValue:Int):Unit={ if (newValue < bound) _x = newValue else printWarning } def y = _y def y_ = (newValue:Int):Unit = { if (newValue < bound) _y = newValue else printWarning } private def printWarning = println("WARNING: Out of bounds") } object Print{ def main(args:Array[String]):Unit ={ val point1=new Point point1.x=99 point1.y=101//points the warning } }
Bean属性
javaBean规范把java属性定义为一堆getFoo和setFoo方法,类似与java,当你将scala字段标注为@BeanProperty时,getter和setter方法会自动生成
import scala.beans.BeauProperty object exam1 { def main(args:Array[String]):Unit = { val per:Person = new Person per.name = "zhangsan" per.setName("lisi") //BeanProperty生成的setName方法 println(per.getName) //BeanPeoperty生成的setName方法 } } class Person { @BeanProperty var name:String = _ }
以上的Person中由@BeanProperty生成了四个方法
1 name:String 2 naem_=(newValue:String):Unit 3 getName():String 4 setName(newValue:String):Unit
定义类的构造方法
Scala构造器作用是完成堆新对象的初始化,构造器没有返回值
scala中构造分为主构造器,和辅助构造器
Scala类的构造器包括,主构造器和辅助构造器
scala构造器的基本语法
class 类名(形参列表) {//主构造器 //类体 def this(形参列表){//辅助构造器} def this(形参列表){//辅助构造器可以有多个} //辅助构造器的 函数的名称this,可以有多个,编译器通过不同的参数来区分
主构造器的参数直接放置于类名之后
定义类 //private var name:String //私有字段,私有的getter和setter方法 //var name:String //私有字段公有的gettersetter方法 //private val name:String //私有有字段 私有的getter方法 //val name:String //私有字段,公有的getter方法 class ClassConstuctor(var name:String,private var price:Double){ def myPrintln = println(name+","+price) } 执行 val classConstructor = new ClassConstuctor("《傲慢与偏见》",20.5) classConstructor.myPrintln
主构造器会执行类定义中的所有语句
定义类: class ClassConstructor2(val name:String = "",val price:Double=0){ println(name+","+price) } 执行 val classConstructor2=new ClassConstructor2("aa",20) val classConstructor2_2=new ClassConstructor2()
如果不带val和var的参数至少被一个方法使用,改参数将自动升级为字段,这时name和price就变成了类的不可变字段,而且这两个字段是对象私有的,这类似于private[this] val 字段的效果
否则改参数将不被保存为字段,即实例化该对象时传入的参数值,不会被保留在实例化后的对象之中
class ClassConstructor(name:String,price:Double){ def main(args:Array[String]):Unit={ val calssConstructor = new ClassConstrucrtor("傲慢与偏见",20.5) } }
主构造器参数 | 生成的字段/方法 |
---|---|
name:String | 对象私有字段,如果没有方法使用name,则没有该字段 |
private val/var name:String | 私有字段,私有的getter和setter方法 |
var name:String | 私有字段,公有的getter和setter方法 |
@BeanProperty val/var name:String | 私有字段,公有的scala版和java版的getter和setter方法 |
可以通过private设置私有的主构造器
//私有的主构造器无法调用,需要定义辅助构造器创建对应的对象 class ClassConstructor private(var name:String,private var price:double){ def myPrintln= new ClassConstructor(name+","+price) } object TestClassConstructor{ def main(args:Array[String]):Unit = { val classConstructor=new ClassConstructor("傲慢与偏见",20.5) classConstructor.myPrintln } }
辅助构造器名称为this,可以通过不同的参数进行分区,每一个辅助构造器,都必须以主构造器或者已经定义的辅助构造器的调用开始,即需要放在辅助构造器的第一行
class Person{ private var name="" private var age=0 //辅助构造器的声明不能和主构造器一致,会发生错误(即构造器名重复) def this(name:String){ this() this.name=name } def this(name:String,age:Int){ this(name) this.age=age } def description = name+"is"+age+"year old" }
"this.name"表示访问当前对象的name字段,可以使用别名替代this
class Person{ self=> //self等价于this:需要出现在类的第一句 private var name="" private var age=0 def this(name:String){ this() self.name=name } def this(name:String,age:Int){ this(name) self.age=age } def description = name + "is" +age+"years old" }
创建对象和访问属性
创建对象语法:
val | var 对象名[:类型]=new 类型()
如果我们不希望改变队形的引用(即:内存地址),应该声明为val 性质的,否则声明为var,scala设计者推荐使用val,因为一般来说,在程序中,我们只是该变对象属性的值,而不是改变对象的引用
访问属性的语法:
对象名.属性名
object Test1{ val name:String="zhangsan" def main(args:Array[String]):Unit = { //调用空参构造器,可以加()也可以不加 val student=new Student() student.name="lisi //变量中使用val修饰的变量不能更改 //student.age=50 println(student.name) println(student.age) println(Test1.name) } }
对象
单例对象
在scala中没有静态方法和静态字段,但是可以使用object这个语法结构来达到同样的目的
1.scala类似与java中的工具类,可以用来存放工具函数和常量
2.高效共享单个不可变的实例
3.单例模式
单例对象虽然类似与java中的工具类,但它不是,还是一个对象,可以把单例对象看做一个贴在对象上的标签
单例对象的使用
object Logger{ def info(message:String):Unit=println(s"INFO: $message") } class Test{ //调用单例对象中定义的方法 Logger.info("Created projects") //Prints "INFO: Create projects" val logObject=Logger logObject.info("Create projects") }
import scala.collection.mutable.ArrayBuffer object SingletonDemo{ def main(agrs:Array[String]):Unit = { val s=SessionFactory println(s.getSession) println(s.getSession.size) println(s.removeSession) println(s.getSession.size) } } object SessionFactory{ println("SessionFactory被执行") var i=5//计数器 //存放Session对象的数组 val sessions =new ArrayBuffer[Session]() //向sessions里添加Session对象,最多添加5个Session对象 while(i>0){ println("while被执行") sessions.append(new Session) i -=1 } //获取Session对象 def getSession = sessions //删除Session对象 def removeSession{ val session = sessions(0) sessions.remove(0) println("session对象被移除"+session) } }
类和单例对象的区别是,单例对象不能带参数,单例对象不能用new关键字实例化,所以没有机会将他传递给实例化的参数
单例对象在第一次访问的时候才会初始化
当单例对象与某个类同名是,他被称为类的伴生对象,类和伴生对象必须定义在一个源文件里,类称为该单例对象的伴生类,类和他的伴生对象可以相互访问其私有成员.
不与伴生对象共享名称的单例对象被称为独立对象,可以作为相关功能的工具类,锁着scala应用的程序的入口点
伴生对象
在scala的类中,与类名相同并且用object修饰的对象叫做伴生对象,雷和伴生对象之间可以相互访问私有的方法和属性,他们必须存在于同一个源文件中
class AccountInfo{ //类的伴生对象的功能特性并不在类的作用域, //所以不能直接使用newUniqueNumber()调用的伴生对象的方法 var id = AccountInfo.newUniqueNumber() } object AccountInfo{ private var lastNumber = 0 private def newUniqueNumber() = { lastNumber +=1; lastNumber } def main(args:Array[String]){ //相当于java中的静态方法 println(AccountInfo.newUniqueNumber()) } }
class CompanionObject { var id = 0 //字段 private val name = "小明"//私有字段 private [this] var age = 18 //对象私有字段 def getAge = age def getAdress = CompanionObject.adress } object CompanionObject{ private val adress = "beiqijai" def main(args:Array[String]):Unit = { val co =new CompanionObject co.id = 1 println(co.id) println(co.name) pirntln(co.getAge) println(co.ageAdress) } }
应用程序对象
scala程序都必须从一个对象的main方法开始,可以通过扩展app特质不写main方法
object Hello extends App{ println("hello, world") }
同
object Hello{ def main(args:Array[String]):Unit = { println("hello world") } }
apply方法
apply方法一般都声明在伴生对象中,可以用来实例化伴生类的对象
class Man private(val sex:String,name:String){ def describe = { println("Sex:" + sex + "name:" + name) } } object Man { def apply(name: String){ new Man("男",name) } }
测试
val man1 = Man("Nick") val man2 = Man("Tom") man1.decribe man2.decribe
也可以用来实现单例模式,我们只需要对上述例子稍加改进
class Man private(val sex: String,name: String){ def describe = { println("Sex: " + sex + "name: " + name) } } objcet Man { var instance: Man = null def apply(name: String) = { if (instance == null){ instance = new Man("男",name) } instance } }
测试
val man1 = Man("Nick") val man2 = Man("Tome")
继承
继承
scala中,让子类继承父类,和java一样,也是使用extends关键字
scala继承给编程带来的便利性
提高了代码的复用性,
提高了代码的扩展性和维护性
继承就代表,子类可以从父类继承父类的属性和方法(子类继承了所有的属性,只是私有的属性不能直接访问,需要通过公共的方法访问) ;然后子类可以在自己内部放入父类所没有,子类特有的属性和方法,使用继承可以有效的代码复用
子类可以覆盖父类的属性和方法,但是如果父类用final修饰则该类是无法被继承的,属性和方法可以用final修饰,属性和方法是无法被覆盖的
只有主构造器可以调用父类的构造器,辅助构造器不能直接调用父类的构造器
class person { private var name = "leo" def getName = name } class Students extends person{ private var score = "100" def getScore = score } object testg{ def main(args:Array[String]):Unit = { val s = new Students println(s.getName) println(s.getScore) //如果上面的方法不加private修饰可以直接访问 //pringln(s.name) //println(s.score) } }
Override和super
scala中,如果子类要重写覆盖一个父类中的非抽象方法,则必须使用override关键字
override关键字可以帮我们尽早的发现代码里的错误,比如,override 修饰的父类方法的方法名拼写错了,比如要覆盖父类方法的参数,我们写错了等等
此外,在子类覆盖父类方法之后,如果我们在子类中就是要调用父类被覆盖的方法呢,那就可以使用super关键字,显示的指定要调用父类的方法
class person { private var name = "zhangsan" def getName = name } class Stu extends person { private var score = 100 //利用override重写父类的方法,通过supper来调用父类的getName方法 override def getName: String = sorce + " " + super.getName } objcet testi{ def main(args Array[Stering]):Unit = { val s = new Stu println(s.getName) } }
覆盖变量override
class person{ val name = "Person" def age = 0 } class Stus extends person { override val name: String = "leo" override def age: Int = 30 } object test{ def main(args Array[String]):Unit = { val per = new person println(per.name) println(per.age) val s = new Stus println(s.name) println(s.age) } }
类型检查和转换
islnstanceOf和asInstanceOf
使用isInstanceOf判断对象是否指定类的对象,如果是的话,则可以使用功能asInstanceOf将对象转换为指定类型
用asInstanceOf方法也可以将引用转换为子类的引用
注意,如果对象是null,则isInstanceOf一定放回false,asInstanceOf一定返回null
注意,如果没有用isInstanceOf先判断队形是否为指定类的实例,就直接用asInstanceOf转换,则可能会抛出异常,classOf获取对象的类名
class person{ val name = "Person" def age = 0 } class Stus extends person{ override val name: String = "leo" override def age: Int = 30 } object testf{ def main(args:Array[String]):Unit = { //子类返回父类的类型 var per: person = new Stus //先创建一个IOS的空类 var stus :Stus = null //接下来进行转换,首先转换前要进行类型的判断 if (per.isInstanceOf[Stus]){ stus = per.asInstanceOf[Stus] println(stus) pirntln(classOf(per)) } } class Point(xPosition:Int,yPosition:Int){ var x: Int = xPosition var y: Int = yPosttion println("Point init") def move(x:Int,y:Int):Unit = { this.x += x this.y += y println("x轴坐标为:" + this.x) pirntln("y轴坐标为:" + this.y) } } }
受保护的字段
protected[this]:在当前子类对象中访问父类的成员,无法通过其他的子类对象访问父类成员
private[this]:相对于protect来说更加严格一些,仅仅可以被同一个类的同一个对象访问
calss Person{ protected val salary = 100.00 def getSalary = salary override def toString = {"hello person" + salary} protected[this] var address = "beijing" } class Employee extends Person{ private val name = "Garry" def getName = name override def getSalary = 300 def getNewSalary = salary override def toString = {"hello,employee=> " + super.toString + name} def makeAddressDifference(emp:Employee):Unit = { println("my address => " + address + ",you address => " +emp.address) } }
超类的构造
只有主构造器可以调用超类的构造器,辅助构造器永远你都不可能直接调用超类的构造器
如果是父类中接收的参数,在子类接收时,就不要用任何的val,或var来修饰了,否则会认为是子类要覆盖父类的field
class Person (val name:String,val age:Int) calss Employee(name:String,age:Int,val address:String) extends Person(name,age){ def this(name:String){ this(name,0,"") } def this(age:Int){ this("garry",age,"") } }
匿名子类
spark源码中大量使用了匿名子类,匿名子类一般都是在运行时期定义的类,可以定义一个类的无名子类,并直接创建其对象,然后将对象的引用赋值一个变量
通过公包含带有定义或重写的代码块的方式创建一个匿名子类
class Person(val name:String){ def greeting = "Glad to see you" + name } val person = new Person("Garry"){ val age = 10 override def greeting = "Greet4ings,Earthing! My name is . " + name } println(person.greeting,person.age) //也可以用这个类型作为参数类型的定义 def greetingMeet (p: Person { val age:Int;def greeting:String}){ println(p.name + " says:" + p.greeting) }
抽象类
抽象类的基本语法
//在scala中,通过abstract关键字标记不能被实例化的类,方法不用标记为abstract,只要省略掉方法体即可,抽象类可以拥有抽象字段,抽象字段/属性就是没有初始值的字段 abstract class Person(){ //抽象类 var name: String //抽象字段没有初始化 def printName //抽象方法,没有方法体 } //抽象类的价值更多是在与设计,是设计者设计好后,让子类继承并实现抽象类(即:实现抽象类的抽象方法)
抽象类的定义和使用
定义一个抽象类
abstract class Animal{ //声明一个有值的字段 val age = 3 //声明一个没有值的字段 val name: String //声明一个有方法体的方法 def climb: String = { "I can climb" } //声明一个没有方法体的方法 def run:String }
如果某个类至少存在一个抽象方法或一个抽象字段,则该类必须声明为abstract
abstract class Person{ //没有初值,抽象字段 var name: String //没有方法体,抽象方法 def id: Int } class Employ extends Person{ var name: String = "Fired" //实现,不需要overide关键字 def id = name.hashCode }
注意
抽象类不能被实例
抽象类不一定要包含abstract方法,也就是说抽象类可以没有abstract方法
一旦类包含了抽象方法或者抽象属性,则这个类必须声明为abstract抽象方法,不能有主体,不允许使用abstract修饰
如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法和抽象属性,除非他自己也声明为abstract类,抽象方法和抽象属性不能使用private ,final来修饰, 因为这些关键字都是和重写/实现相违背的,
抽象类中可以有实现的方法,
子类重写抽象放啊发不需要override 写上也不会出错,
特质
特质用于在类之间共享程序接口和字段,他们类似与java8的接口,类和对象可以扩展特质,但是特质不能被实例化,因此特质没有参数
特质的定义处了使用关键字trait之外和类的定义并无差异
在trait中可以定义抽象方法,就与抽象类中的抽象方法一样,只要不给出方法的具体实现即可
类可以使用extends关键字继承trait 注意这里不是implement 而是extends 在scala中没有implement的概念,无论继承类还是trait,都是extends
没有父类 class 类名 extends 特质1 with 特质2 with 特质3 有父类 class 类名 extends 父类 with 特质1 with 特质2 with 特质3
类继承trait后,必须实现其中的抽象方法,实现时,不需要使用override关键字
scala不支持对类进行多继承,但是支持多继承trait使用with关键字即可
trait Flyable{ //声明一个有值的字段 /* scala中的trait可以定义具体变量,此时继承trait的类就自动获得trait中定义的变量, 但是这种获得变量的方式与继承class是不同的,如果是继承的class获取的变量,实际是定义在类中的,相当于我们获得到的是父类的引用地址,而继承trait获取的变量,就直接被添加到类中 */ val distance: Int = 1000 //声明一个没有值的字段 //定义抽象字段的时候如果我们继承了这个接口,那么必须要重写实现这个字段,并且重新赋值, val hight: Int //声明一个有方法体的方法 def fly: String = { "I can fly" } //声明一个没有方法体的方法 def fight: String }
特质用来在类之间进行接口或者属性的共享,类和对象都可以继承特质,特质不能被实例化,因此也没有参数,
一旦特质被定义了,就可以使用extends或with在类中混入特质
作为接口使用特质
特质的定义:
trait Logger{ //这是抽象方法,特质中未被实现的方法默认是抽象的,不需要abstract关键字修饰 def log(msg:String) }
子类对特质的实现:
class ConsoleLogger extends Logger{ //重写抽象方法,不需要override def log(msg: String){println(msg)} }
带有具体实现的特质
trait ConsoleLogger { def log(msg:String){ println(msg) } }
特质的使用
class Account{ protected var balance = 0.0 } class SavingsAccount extends Account with ConsoleLogger { def withdraw(amount: Double){ if (amount > balance) log("余额不足") else balance -= amount } } object Test{ def main(args:Array[String]):Unit = { val obj = new SavingsAccount() obj.withdraw(100) } }
Trait继承class
//在scala中,trait也可以继承自class,此时这个class就会成为所有继承该trait的类的父类 class MyUitl{ def pringMsg(msg:String)=println(msg) } trait MyTrait extends MyUitl{ def log(msg:String) = printMsg("log : " + msg) } class Demos(val name:String) extends Mytrait { def sayHello:Unit{ log(name + " 0000") printMsg(name + " 111") } } object testz{ def main(args:Array[String]):Unit= { val a = new Demos("A") a.sayHello } }
动态混入
在构造具体对象的时候,混入特质
class Account{ val Account: String = "" } class SavingAccount extends Account { } trait Logged{ def log(msg:String){} } trait ConsoleLogger extends Logged{ override def log(msg:String) = println("msg=> " + msg) } //可以在构造对象的时候加入这些特质,即动态混入 val savingAccount = new SavingAccount with ConsoleLogger //当我们在savingAccount对象上调用log方法时ConsoleLogger特质的Log方法就会被执行 savingAccount.log("You will get logs....")
使用特质扩展单例对象
abstract class PersonAccount(val accountName:String){ def unRegisterPersonAccount() : Unit def reRegisterPersonAccount() : Unit } trait Logger{ def log(msg:String) : Unit = { println(msg) } } object PersonAccountImpl extends PersonAccount("AccountName") with Logger{ override def unRegisterPersonAccount() : Uint = { log("===start to logger===") } override def reRegisterPersonAccount() : Unit = { log("====start to logger====") } } //调用 PersonAccountImpl.reRegisterPersonAccount()
App特质
object ObjectOps extends App { if (args.length > 0 ) println("hello ,"+args[0]) else println("hello world") }
枚举
1.java中的枚举是enum关键字,scala提供的是Enumerate助手类
2.使用Enumerate定义枚举
定义一个扩展Enumerate类的对象并以Value方法调用初始化枚举中的所有可选值
每一个枚举值包括ID和name,如果不指定ID值,则ID在前一个枚举值的基础上+1,从0开始,缺省name字段为字段名
3.枚举的引用 : 用PersonClothesColor.Red PersonClothesColor.Yellow等来引用枚举值
4.枚举的访问
value返回所有的枚举值
按ID name访问
object PersonClotherColor extends Enumeration{ val Black = Value(1,"black") val Red,Yellow,Green = Value } for(c <- PersonClotherColor.values){ println(c.id + ":" + c) } println(PersonClothesColor(0)) println(PersonClotherColor.withName("Yellow"))
包和包对象
包可以包含类, 对象和特质,但是不能包含变量或者方法的定义,应使用包对象解决这个问题
package com.lee{ package object module_1{//对应包com.lee.module_1每个包都可以有自己的包对象 val msg = "hello" } package module_1{...} //与包对象同名的包可以直接使用包对象中定义的变量和方法 }
import让包和包对象的成员可以直接通过名称访问
//易于访问fruit import com.lee.Fruit //易于访问com.lee的所有成员 import com.lee._ //易于访问com.lee.Fruit的所有成员 import com.lee.Fruit._ //只引用Apple与Orange 并且Apple重命名为McIntosh import com.lee.Fruits.{Apple=>McIntosh,Orange}
样例类
使用case修饰符的类称为样例类(case class) ,case 修饰符可以让scala编译器自动为类添加一些便捷的设定
自动生成类的getter和setter访问器
构造器中的参数默认获得"val"前缀 只生成getter
可手动显式对参数指定的"var"前缀 同时生成getter&setter
自动生成类的伴生对象(单例对象)
在单例对象中,实现apply()方法另外实现了一些其他的方法: toString(),equals(),copy(),hashCode(),Unapply()等,特别的nuapply()也称为提取器
case class User(name:String,age:Int)
使用普通类模仿样例类:
class User(val name:String,val age:Int){ override def toString:String = { (name,age).toString() } override def equals(obj:Any):Boolean = super.equals(obj) } object User{ def apply(name:String,age: Int):User = new User(name,age) /* 提取器的第一个功能,完成对象参数的提取 def unapply(arg: User):Option[(String,Int)] = { if (arg!=null) Some((arg.name,arg.age)) else None } */ //提取器的第二个功能,测试一个对象 def unapply(arg:User):Boolean = if(arg.age>10) true else false }