一.面向对象之类
1.构造器
主构造器:Scala中的每个类都有一个主构造器,主构造器的参数直接放置类名后面,与类交织在一起.
注意:主构造器会执行类中定义的所有语句
package com.legendlee.oop.constructor
class Student(var n: String, var a: Int) {
var name = n
var age = a
a = a + 1
var sex: String = _ //表示占位符
println("主构造器默认会执行类中定义的所有语句")
//辅助构造器
def this(n: String, a: Int, sex: String) {
//第一行必须调用主构造器的代码
this(n, a)
println("执行辅助构造器")
this.sex = sex
}
//定义一个方法
def study(s:String): Unit ={
println(this.age+"岁的"+this.name+"在学习"+s)
}
}
object Demo01 {
def main(args: Array[String]): Unit = {
//创建类的一个对象
//调用主构造器
var stu=new Student("ss",23)
stu.study("linux")
//调用辅助构造器
var stu2=new Student("legendlee",25,"男")
stu2.study("scala")
}
}
运行结果:
注意:占位符_只能将变量声明为var时才能使用
2.访问权限
构造器的访问权限:
package com.legendlee.oop.privilege
//构造器的访问权限
class Student private (var n: String, var a: Int) {//私有主构造器
var name = n
var age = a
var sex:String=_//_表示占位符
//私有化辅助构造器
private def this( name:String,age:Int,sex:String){
this(name,age)
this.sex=sex
}
}
object Demo01 {
def main(args: Array[String]): Unit = {
var stu =new Student("ss",23)
println(stu)
}
}
表明在Demo01中无法访问到Student类中的构造器
说明:在主构造器或辅构造器修饰为private后,访问权限仅限:本类和伴生对象中使用
类中变量的访问权限:
当把类中的成员变量设为私有时,其他类里无法访问
解决方案:
1.在Student类中添加setter getter方法
2.使用伴生对象
package com.legendlee.oop.privilege
//类中变量的访问权限
class Student{
//将类中的成员变量私有化
private var name:String =_
private var age:Int=_
//定义一个方法
def study(c:String): Unit ={
println(this.age+"岁的"+this.name+"在学习"+c)
}
//提供set方法
def setName(name:String): Unit ={
this.name=name
}
//get
def getName(): String ={
name
}
//提供set方法
def setAge(age:Int): Unit ={
this.age=age
}
//get
def getAge(): Int ={
age
}
}
object Demo02 {
def main(args: Array[String]): Unit = {
var stu =new Student()
stu.setName("legendlee")
stu.setAge(25)
println(stu.getName())
stu.study("scala")
}
}
运行结果:
二.面向对象编程之对象
1.scala中的Object
object相当于class的单个实例(单例模式),通常在里面放一些静态的field或者method
在Scala中没有静态方法和静态字段,但是可以使用object这个语法结构来达到同样的目的.
单例模式的特点就是对象只有一个。放在Object中相当于静态的,不需要去实例化,可以直接访问
package com.legendlee.oop.object1
class Session{}//伴生类
//伴生对象 伴生对象名要和伴生类名保持一致
object Session{//只要是书写在Object里面的就是静态的
val session=new Session()
//获得session对象
def getSession(): Session ={
session
}
}
object Demo01 {
def main(args: Array[String]): Unit = {
//相当于单例模式
var s=Session.getSession()//通过类名直接调用方法
var s2=Session.session//通过类名直接调用对象
//打印s s2
println(s)
println(s2)
}
}
运行结果:
2.scala中的伴生对象
首先,需要明确一个问题,什么是伴生对象?
伴生对象的解释:
如果有一个class,还有一个与class同名的object,那么就称这个object是class的伴生对象,class是object的伴生类
要求: 伴生类和伴生对象必须存放在一个.scala文件中
特点: 伴生类和伴生对象的最大特点是,可以相互访问(可以访问私有成员)
可以理解为:
随着类的加载而自动出现的一个对象(单例)
关键字:Object
必须和类名保持一致
好处:可以在伴生对象中直接访问伴生类中的私有成员
package com.legendlee.oop.object1
class Person private(n: String) {
//全部都定义为私有的
private var name: String = n
private var age: Int = _
private def this(name: String, age: Int) {
this(name)
this.age = age
}
}
//伴生对象
object Person {
val person=new Person("ss")
person.name="珊珊"
person.age=23
def getName(): String ={
this.person.name
}
}
object Demo02 {
def main(args: Array[String]): Unit = {
println(Person.getName())
}
}
运行结果:
总结:伴生对象里面可以访问到伴生类中的私有的成员信息
3.scala中的apply方法
1.apply方法的引入
var arr = Array(10,20,30)
var list = ListBuffer(10,20,30)
在使用Array(10,20,30)和ListBuffer(10,20,30)时,其实使用的并不是数组或集合类本身,而是使用Array类和ListBuffer类的object伴生对象
package com.legendlee.oop.object1
class Person private(n: String) {
//全部都定义为私有的
private var name: String = n
private var age: Int = _
private def this(name: String, age: Int) {
this(name)
this.age = age
}
}
//伴生对象
object Person {
//创建apply方法
def apply(name:String): Person ={
new Person(name)
}
}
object Demo03 {
def main(args: Array[String]): Unit = {
val p=Person("ss")
}
}
当我鼠标点击Person的时候会出现apply方法:
说明不需要再重新new一个对象,其直接调用了伴生对象中的apply方法.
apply方法也可以重载
package com.legendlee.oop.object1
class Person private(n: String) {
//全部都定义为私有的
var name: String = n
var age: Int = _
private def this(name: String, age: Int) {
this(name)
this.age = age
}
}
//伴生对象
object Person {
//创建apply方法
def apply(name:String): Person ={
new Person(name)
}
def apply(name:String,age:Int): Person ={
new Person(name,age)
}
}
object Demo03 {
def main(args: Array[String]): Unit = {
val p=Person("ss",23)
println(p.name,p.age)
}
}
根据传参的个数和类型系统自动调用对应的apply方法。
三.面向对象之继承
1.继承的概念
继承是面向对象的概念,用于代码的可重用性。被扩展的类称为超类或父类, 扩展的类称为派生类或子类。
Scala 中,让子类继承父类,与 Java 一样,也是使用 extends 关键字;
继承就代表,子类可继承父类的field和method,然后子类还可以在自己的内部实现父类没有的,子类特有的 field 和method,使用继承可以有效复用代码
在Scala中的继承:
1、private修饰的field和method不可以被子类继承,只能在类的内部使用
2、使用final修饰符时,修饰类:类不能被继承、修饰field和method:不能被覆写
package com.legendlee.extendsDemo
//父类
class Person {
final def say() = {
println("打招呼")
}
}
//子类
class Student extends Person {
def show() = {
say()
}
}
object Demo01 {
def main(args: Array[String]): Unit = {
//实例化子类对象
val stu = new Student()
stu.show()
}
}
运行结果:
子类可以直接使用父类中的方法。
2.scala中的override和super关键字
override的使用场景:
1、Scala中,如果子类要覆盖父类中的一个非抽象方法,必须要使用 override 关键字
2、子类可以覆盖父类的val修饰的field,只要在子类中使用 override 关键字即可
(注意:针对父类中的var修饰的field,子类不能覆写)
package com.legendlee.extendsDemo
class Phone{
val core:String="android0011"
def call():Unit={
println("打电话.....")
}
}
class NewPhone extends Phone{
//内核升级 针对父类中的val变量,需要使用override覆写
override val core:String ="android1100"
//打电话功能升级 针对父类中的非抽象方法,子类在重写时必须添加:override关键字
override def call()={
println("视频...")
super.call()
}
}
object ExtendsDemo2 {
def main(args: Array[String]): Unit = {
val p= new NewPhone()
println(p.core)
p.call
}
}
super关键字的使用场景:
super.父类中的方法
在子类覆盖父类方法后,如果在子类中要调用父类中被覆盖的方法,则必须要使用super关键字(显示的指出要调用的父类方法)
3.scala中的protected
在Scala中同样提供了protected关键字来修饰field和method,方便子类去继承父类中的field和method
private修饰的成员变量:本类可访问 ,伴生对象可以访问
private[this]修饰的成员变量:仅限本类访问
private修饰的成员变量:本类可访问 ,伴生对象可以访问
private[this]修饰的成员变量:仅限本类访问
protected修饰的成员变量:本类、本类的伴生对象、子类、子类的伴生对象
protected[this]修饰的成员变量:本类、子类
完整代码如下:
package com.legendlee.extendsDemo
class Person{
private val id1:String ="9527"
private[this] val id2:String ="8888"
//private修饰的成员变量:本类可访问 ,伴生对象可以访问
//private[this]修饰的成员变量:仅限本类访问
protected[this] var name:String ="张三"
//protected修饰的成员变量:本类、本类的伴生对象、子类、子类的伴生对象
//protected[this]修饰的成员变量:本类、子类
protected var age:Int=_
protected def sayHello(): Unit ={
println(name+"打招呼...")
}
}
object Person{
var p=new Person
p.id1
}
class Student extends Person{
def study()={
println(""+this.name)
}
}
object Student extends App{ //学生类的伴生对象
val stu = new Student()
stu.age=22
stu.study()
}
4.调用父类中的constructor
package com.legendlee.extendsDemo
class Person {
println("Person类主构造器....")
var name: String = _
var age: Int = _
//重载的辅助构造器
def this(name: String) = {
this() //调用主构造器
this.name = name
println(s"Person($name)类辅助构造器....")
}
def this(name: String, age: Int) = {
this() //调用主构造器
this.name = name
this.age = age
println("Person(String,Int)类辅助构造器....")
}
}
//创建子类继承父类
class Student extends Person{
println("Student类的主构造器....")
//子类的辅助构造器
def this(name:String)={
this()
this.name = name
println("Student(String)类的辅助构造器....")
}
//子类的辅助构造器
def this(name:String,age:Int)={
this()
this.name = name
println("Student(String,Int)类的辅助构造器....")
}
}
object ExtendsDemo extends App{
val stu = new Student("张三",18)
}
分析下代码的执行过程:
1.先调用Person类的主构造器
2.再调用Student类的主构造器
3.再调用Student类的辅助构造器
运行结果如下:
5.scala中的抽象类
1.抽象类的概念
如果在父类中,有某些方法无法立即实现,而需要依赖不同的子类来覆盖,重写实现不同的方法。此时,可以将父类中的这些方法编写成只含有方法签名,不含方法体的形式,这种形式就叫做抽象方法。
一个类中,如果含有一个抽象方法或抽象field,就必须使用abstract将类声明为抽象类,该类是不可以被实例化的。
在子类中覆盖抽象类的抽象方法时,可以不加override关键字。
package com.legendlee.extendsDemo
abstract class Person {
//抽象内容
var name: String //抽象字段
def hello(): String //抽象方法
//非抽象内容
var age: Int = _
def sayHello(msg: String) = {
println("你好," + msg)
}
}
//子类继承抽象类,必须重写所有的抽象内容
class Student extends Person {
override var name = "老唐"
override def hello(): String = {
name
}
}
object AbstractDemo1 {
def main(args: Array[String]): Unit = {
//实例化学生类
val stu1 = new Student()
stu1.name = "老李"
stu1.sayHello(stu1.hello())
}
}
运行结果:
6.Scala中的isInstanceOf和asInstanceOf
在多态中,父类型的引用指向子类型的对象。
//判断person对象是不是属于Student类
if(person.isInstanceOf[Student]){
//把person对象转为Student实例
var stu:Student=person.asInstanceOf[Student]
}
四.面向对象编程之Trait
1.trait是什么?
trait相当于java中的接口,
与java中接口的不同为:它除了定义抽象方法外,还可以定义属性和方法的实现。
scala中没有implement的概念,无论是继承还是trait,都是用extends关键字。然后必须实现所有的抽象方法,实现时可以不用override关键字。
案例一:HelloTrait
//定义一个trait
trait HelloTrait {
def sayHello(msg:String)
}
//子类继承Trait,并重写抽象方法
class TraitChild extends HelloTrait{ //trait的单一继承
def sayHello(msg: String): Unit = {
println("你好,"+msg)
}
}
object TraitChild{
def main(args: Array[String]): Unit = {
var tc = new TraitChild()
tc.sayHello("ss")
}
}
输出结果:
Scala不支持对类进行多继承,但是支持多重继承 trait,使用 with 关键字即可
案例二:Trait多重继承
//定义第一个trait
trait TraitOne{
def getDate():String
}
//定义第二个trait
trait TraitTwo{
def printDate(data:String)
}
//当继承多个Trait时,使用关键字:with
class TraitChildClass extends TraitOne with TraitTwo{
override def getDate(): String = {
"测试数据"
}
override def printDate(data: String): Unit = {
println(data)
}
}
object TraitTest1 extends App{
val tc = new TraitChildClass()
tc.printDate( tc.getDate() )
}
执行结果:
2.在trait中书写的定义
在trait中除了可以写抽象方法外,还可以写抽象字段(变量没有初始化值),具体方法,具体字段。
案例:
package com.legendlee.traitDemo
trait TraitPerson{
//抽象方法
def study(): Unit
//具体方法
def eat(food:String): Unit ={
println(food)
}
//抽象字段
var name:String
//具体字段
var age:Int=_
}
//创建一个子类继承Trait
class Person extends TraitPerson{
override def study(): Unit = {
println(this.age+"岁的"+this.name+"在学习")
}
override var name: String = _
}
object Demo01 {
def main(args: Array[String]): Unit = {
var person =new Person()
person.name="ss"
person.age=23
person.study()
}
}
运行结果:
3.Trait的高级应用
3.1在实例对象指定混入某个trait
Trait高级知识: 创建对象实例时,trait加入到对象实例中
可在创建类的对象时,为该对象指定混入某个trait,且只有混入了trait的对象才具有trait中的方法,而其他该类的对象则没有
格式: val 引用变量 = new 类名() with Trait
在创建对象时,使用with关键字指定混入某个trait
//定义第一个trait
trait TraitOne{
def getDate():String
}
//定义第二个trait
trait TraitTwo{
def printDate(data:String)
}
//当继承多个Trait时,使用关键字:with
class TraitChildClass extends TraitOne with TraitTwo{
override def getDate(): String = {
"测试数据"
}
override def printDate(data: String): Unit = {
println(data)
}
}
object TraitTest1 extends App{
val tc = new TraitChildClass()
tc.printDate( tc.getDate() )
}
运行结果为:
3.2、Trait调用链
问题:当类继承多个Trait时,而这些Trait中都共有同一个方法,那么子类调用时到底执行哪个Trait中的方法?
//验证数据
trait ValidTrait{
def handler(data:String): Unit ={
println("最终的数据验证:"+data)
}
}
//数据签名验证
trait SignatureValidTrait extends ValidTrait{
override def handler(data:String):Unit={
println("数据签名验证:"+data)
//调用链: 多个Trait具有相同的方法
super.handler(data) //会执行ValidTrait中的handler方法
}
}
//数据的合法性验证
trait DataValidTrait extends ValidTrait{
override def handler(data:String):Unit={
println("数据合法性验证:"+data)
super.handler(data)
}
}
class Request() extends DataValidTrait with SignatureValidTrait{
def getParamter(data:String): Unit ={
handler(data)
}
}
object ValidTest extends App{
val r = new Request()
r.getParamter("测试数据")
}
运行的结果为:
Trait的调用链机制:
当多个Trait中共同都具有一个相同的方法时,子类在继承了这些Trait后,当子类对象调用这个共同的方法时:
先执行extends后面最右边的Trait中的方法,依次向左边Trait中的方法顺序执行下去(使用了:Super.共同方法() )
3.3、混合使用trait的具体方法和抽象方法
在trait中,可以混合使用具体方法和抽象方法
案例:
trait ValidTrait {
//抽象方法
def getData: String
//非抽象方法
def valid(data: String): Boolean = {
data.equals(getData) //在非抽象方法中使用抽象方法
}
}
class ValidClass extends ValidTrait {
override def getData = {
"数据"
}
}
object ValidTest extends App {
val vc = new ValidClass()
if (vc.valid("数据")){
println("合法数据")
}else{
println("非法数据")
}
}
运行结果:
3.4、Trait的构造机制
3.4.1、构造机制
在Scala中,trait也是有构造的,即在trait中,不包含在任何方法中的代码
说明:Trait中只有无参构造(主构造器)
//父类
class Father{
println("Father类的主构造器.....")
}
//父Trait
trait TraitParent{
println("TraitParent构造器执行了一次......")
}
trait TraitOne extends TraitParent{
println("TraitOne构造器.....")
}
trait TraitTwo extends TraitParent{
println("TraitTwo构造器.....")
}
//子类继承了父类,同时又继承了两个Trait (Trait也继承父Trait)
class TraitChildClass extends Father with TraitTwo with TraitOne {
println("TraitChildClass主构造器.......")
}
object ConstructorDemo extends App{
val tc = new TraitChildClass()
}
运行结果:
子类继承了父类的同时,也继承了多个Trait(Trait也有继承)时,构造机制:
1、父类的构造器
2、多个Trait从左向右依次执行(构造Trait时,先构造父Trait)
注意:如果多个Trait继承同一个父Trait,则父Trait只会构造一次
3、子类的构造器
说明:trait的构造器的调用顺序和trait调用链的调用顺序相反
3.4.2、Trait中的field初始化
在Scala中,Trait是没有带参数构造器的(也是Trait和class的区别)
如果想要对Trait中的field进行初始化,怎么解决呢?
答案: Trait的高级特性:提前定义
首先,看一段代码
trait MyTrait{
//抽象字段
val msg:String
//主构造器代码
println(s"消息的长度是:${msg.length}")
}
class MyTraitChild extends MyTrait{
override val msg = "测试数据"
}
object TraitFieldDemo1 extends App{
val my = new MyTraitChild()
}
其中在trait中定义了一个抽象的字段,在其子trait实例化了这个抽象字段。
运行结果:
为什么会出现这个问题?
分析错误原因: 结合前面学习的Trait构造机制
先去执行MyTrait中的构造代码,而在MyTrait的构造代码中需要使用“抽象filed”,但该field还未初始值。“null.length”造成空指针异常。
解决方案:
1.修改实例化的代码,使用混入方案
trait MyTrait{
//抽象字段
val msg:String
//主构造器代码
println(s"消息的长度是:${msg.length}")
}
class MyTraitChild extends MyTrait{
override val msg = "测试数据"
}
object TraitFieldDemo1 extends App{
//修改实例化的代码,使用混入方案
val my = new { override val msg = "测试数据"} with MyTraitChild()
}
运行结果:
2.不会破坏实例化代码,修改extends后满的代码。
trait MyTrait{
//抽象字段
val msg:String
//主构造器代码
println(s"消息的长度是:${msg.length}")
}
//相当于提前初始化
class MyTraitChild extends { override val msg = "测试数据"} with MyTrait{
}
object TraitFieldDemo1 extends App{
val my = new MyTraitChild()
}
运行结果:
3.5、Trait继承class
当Trait继承了某个class后,Trait就继承了该class中的方法,当子类在继承Trait后,也同时继承了class中的方法。
class Person {
var name: String = _
var age: Int = _
def sayHello(): Unit = {
println("打招呼....")
}
}
//trait继承Person后,就可以直接使用Person中的成员
trait TraitPerson extends Person {
def eat(food: String): Unit = {
println(this.name + "吃" + food) //可以直接使用父类中的成员
}
}
class Student extends TraitPerson {
}
object Test extends App {
val stu = new Student()
stu.name = "张三"
stu.age = 22
stu.sayHello()
stu.eat("食物")
}
运行结果: