天天看点

疯狂Kotlin讲义学习笔记08章:面向对象(中)--扩展,匿名函数,宏替换,final,抽象类,密封类,接口

1、扩展方法

实际就是继承=>派生的意思

被继承需要使用open关键字修饰

使用类名.方法名的方式为父类扩展方法,这样父类和子类都可以共享该方法

open class Base{
    fun test(){
        println("这是基类的方法")
    }
}
class Sub:Base(){
    fun ext(){
        println("这是子类的方法")
    }
}
//为基类扩展一个方法
fun Base.info(){
    println("扩展出来的方法")
}

fun main(args:Array<String>){
    var base=Base()
    base.test()
    var son=Sub()
    son.ext()

    base.info()
    son.info()

}
           

打印

这是基类的方法

这是子类的方法

扩展出来的方法

扩展出来的方法

2、扩展的实现机制

扩展实际就是调用了定义的扩展方法

成员方法执行动态解析(由运行时类型决定);扩展方法执行静态解析(由编译类型决定)

3、为可空类型扩展方法

允许对可空类型扩展方法,由于可空类型允许接收null值,这样使得null值也可调用该扩展方法

fun Any?.equals(other:Any?):Boolean{
    if(this==null){
        return if (other==null) true else false
    }
    return other.equals(other)
}

fun main(args:Array<String>){
    var a=null
    println(a.equals(null))
    println(a.equals("kotlin"))

}
           

打印:

true

false

4、扩展属性

扩展属性实际上市通过添加getter和setter方法实现的,扩展的属性只能是计算属性

  • 扩展属性不能有初始值
  • 不能用field关键字显示访问幕后字段
  • 扩展只读属性必须提供getter方法,扩展读写属性必须提供getter和setter方法
class User(var firstname:String, var lastname:String){}
//为User扩展属性
var User.fullname:String
    get()="${firstname}.${lastname}"
    set(value){
        var tokens=value.split(".")
        firstname=tokens[0]
        lastname=tokens[1]
    }

fun main(args:Array<String>){
    var user=User("悟空","孙")
    println(user.fullname)

    user.fullname="八戒.猪"
    println(user.firstname)
    println(user.lastname)

}
           

打印:

悟空.孙

八戒

5、以成员方式定义扩展

以类成员的方式定义扩展–就向为类定义方法、属性哪样定义扩展

class A{
    fun bar()= println("A的bar方法")
}
class B{
    fun baz()= println("B的baz方法")
    //以成员方法为A扩展foo()方法
    fun A.foo(){
        //在该扩展方法里面,既可以调用类A的成员,也可调用类B的成员
        bar()
        baz()
    }

    fun test(target:A) {
        //调用A对象的成员方法
        target.bar()
        //调用A对象的扩展方法
        target.foo()
    }
}
fun main(args:Array<String>){
    var b=B()
    b.test(A())

}
           

打印:

A的bar方法

A的bar方法

B的baz方法

程序在扩展方法中调用两个类都包含方法的时候,系统总是优先调用被扩展类的方法,为了让系统调用扩展定义所在类的方法,必须使用带标签的this进行限定:[email protected]

class Tiger{
    fun foo(){
        println("Tiger类的foo()方法")
    }
}

class Bear{
    fun foo(){
        println("Bear类的foo()方法")
    }
    //以成员方式为Tiger类扩展test方法
    fun Tiger.test(){
        //同名函数调用时,优先调用被扩展类的test()方法
        foo()
        //使用带标签的this指定Bear的foo()方法
        [email protected]()
    }
    fun info(tiger: Tiger){
        tiger.test()
    }
}
fun main(args:Array<String>){
    var b=Bear()
    b.info(Tiger())

}
           

打印:

Tiger类的foo()方法

Bear类的foo()方法

6、带接收者的匿名函数

kotlin制程为类扩展匿名函数,该扩展函数所属的类也是该函数的接收者。这种匿名函数称之为“带接收者的匿名函数”

val factorial=fun Int.():Int{
    //该匿名函数的接收者是Int对象
    //因此在该匿名函数中,this代表调用该匿名函数的Int对象
    if (this<0){
        return -1
    }else if (this==1){
        return 1
    }else{
        var result=1
        for (i in 1..this){
            result*=i
        }
        return  result
    }
}

fun main(args:Array<String>){
    println(6.factorial())

}
           

打印

720

与普通函数想类似的是,带接收者的匿名函数也有自身的类型,即带接收者的函数类型。如上例factorial变量的类型为:Int.()->Int

class HTML{
    fun body(){
        println("<body></body>")
    }
    fun head(){
        println("<head></head")
    }


}
//定义一个类型为HTML.()->Int的形参(带接收者的匿名函数)
//这样在函数中HTML对象就增加了一个init方法
fun html(init:HTML.()->Unit){
    println("<html>")
    val html=HTML()//创建接收者对象
    html.init()
    println("</html>")
}

fun main(args:Array<String>){
    //调用html函数,需要传入HTML.()->Unit类型的参数
    //此时系统可推断出接收者的类型,故可以用lambda表达式代替匿名函数

    html{
        //lambda表达式中的this就是该方法的调用者
        head()
        body()
    }
}
           

打印:

<html>
<head></head
<body></body>
</html>
           

7、何时使用扩展

两个作用

  • 扩展可动态地为已有的类添加方法或属性
  • 扩展能以更好的形式组织一些工具方法

8、可执行“宏替换”的常量

kotlin的final修饰符不能修饰局部变量,因此open自然也不能修饰局部变量

kotlin不允许使用final定义“宏变量”

“宏替换”的常量除使用const修饰之外,还必须满足如下条件

  • 位于顶层或者是对象表达式的成员
  • 初始值为基本类型值或者字符串字面值
  • 没有自定义的getter方法
const val MAX_AGE=100

fun main(args:Array<String>){
    println(MAX_AGE)
}
           

打印:

100

9、final属性

final表示该属性不能被重写,为所有的属性自动添加final修饰

10、final方法

final表示该方法不能被重写,为所有的方法自动添加final修饰

11、final类

用final修饰的类不可以有子类,为所有类自动添加final修饰

12、不可变类(immutable)

不可变类的意思是创建该类的实例后,该实例的属性值是不可改变的

如果需要创建自定义的不可变类,可遵循如下规则

  • 提供带参的构造器,用于根据传入的参数来初始化类中的属性
  • 定义使用final修饰的只读属性,避免程序通过setter方法改变属性值

    如果有必要,则重写Any类的hashCode()和equals()方法,equals方法将关键属性作为两个对象是否相等的标准。除此之外,还应保证两个用equals()方法判断为相等的对象的hashcode()也相等

当创建不可变类时,如果她包含的成员属性的类型是可变的,那么其对象的属性值依然是可变的。

class Name(var firstName:String="",var lastName:String=""){//两个形参(属性)是可变的

}

class Person(val name:Name){//两个形参是不可变的

}

fun main(args:Array<String>){
    val n=Name("悟空","孙")
    var p=Person(n)

    //Person对象的name的fistname为“悟空”
    println(p.name.firstName)//连续传递对象及其属性过来
    //改变Person对象的name的firstname值
    n.firstName="八戒"
    //Person对象的name的firstname被改为“八戒”,因为Name中的两个属性实际上是可变的
    println(p.name.firstName)//连续传递对象及其属性过来

}
           

打印:

悟空

八戒

为了保持Person对象的不可变性,必须好保护好Person对象的应用类型的属性:name,让程序无法访问到Person对象的name属性的幕后变量,也就无法利用name属性的可变性来改变Person对象了

13、抽象成员和抽象类

使用abstract修饰的成员,无须使用open修饰。

当使用abstract修饰方法、属性时,说明该类需要被继承,方式属性需要被子类重写。

包含抽象成员的类只能定义成抽象类,抽象类中可以没有抽象成员

抽象方法和抽象类的规则

  • 类和抽象成员必须使用abstract来修饰
  • 抽象类不能被实例化
  • 抽象类可以包含属性、方法(普通方法和抽象方法都可以)、构造器、初始化块、嵌套类(接口、枚举)成员。抽象类的构造器不能用于创建实例,只能被其子类调用
  • 含有抽象成员的类只能被定义成抽象类。
abstract class Shape{
    init {
        println("执行Shape抽象类的方法")
    }

    var color=""
    //定义一个周长的抽象方法
    abstract  fun calPerimete():Double
    //定义一个代表形状的抽象的只读属性,抽象属性不需要初始值
    abstract val type:String
    //定义shape的构造器,该构造器并不是用于创建shape对象的,而是用于被子类调用
    constructor()
    constructor(color:String){
        println("执行Shape的构造器")
        this.color=color
    }
}

//定义三角形的三遍
class Triangle(color: String,var a:Double,var b:Double,var c:Double):Shape(color){
    fun setSides(a:Double,b:Double,c:Double){
        if(a>=b+c||b>a+c ||c>=a+b){
            println("三角形两边之和必须大于第三边")
            return
        }
        this.a=a
        this.b=b
        this.c=c
    }
    //重写shape类计算周长的方法
    override fun calPerimete(): Double {
        return a+b+c
    }
    //重写shape类代表形状的抽象属性
    override val  type:String="三角形"
}

//定义一个圆
class Circle(color: String,var radius:Double):Shape(color){
    //重写shape类计算周长的方法
    override fun calPerimete(): Double {
        return 2*Math.PI*radius
    }
    //重写shape类代表形状的抽象属性
    override val  type:String="圆形"
}
fun main(args:Array<String>){
    var s1:Shape=Triangle("黑色",3.0,4.0,5.0)
    var s2:Shape=Circle("黄色",3.0)

    println(s1.type)
    println(s1.calPerimete())

    println(s2.type)
    println(s2.calPerimete())

}
           

打印:

执行Shape抽象类的方法

执行Shape的构造器

执行Shape抽象类的方法

执行Shape的构造器

三角形

12.0

圆形

18.84955592153876

14、抽象类的作用

从多个具有相同特征的类中抽象出一个抽象类,以这个抽象类作为子类的模板,从而避免了子类设计的随意性。

抽象类是一种模板模式的的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会大致保留抽象类的行为方式

//定义带转速属性的主构造器
abstract class SpeedMeter(var turnRate:Double){
    //把返回车轮半径的方法定义成抽象方法
    abstract  fun calGrith():Double
    //定义计算速度的通用算法
    fun getSpeed():Double{
        //速度等于车轮周长*转速
        return calGrith()*turnRate
    }
}

//这里抽象方法中无法定义伦则的周长(实际是轮子的半径)
public class CarSpeedMeter(var radius:Double):SpeedMeter(0.0){
    override fun calGrith(): Double {
        return radius*2*Math.PI
    }
}


fun main(args:Array<String>){
    val csm=CarSpeedMeter(0.28)
    csm.turnRate=15.0
    println(csm.getSpeed())

}
           

打印:

26.389378290154266

SpeedMeter类提供了车速表的通用算法,但是具体到实现的细节,则推迟到其子类CarSpeedMeter类中实现,这是一种典型的模板模式

模板模式的一些简单规则

  • 抽象父类可以只定义需要使用的某些方式,把不能实现的部分抽象成抽象方法,留给子类去实现
  • 父类中可能包含需要调用其他系列方法的方法,这些被调用方法既可以由父类实现,也可以由其子类实现。父类中提供的方法只是定义了一个通用算法,其实现也许并不完全由自身来完成,而必须依赖其子类的辅助

15、密封类

密封类是 一种特殊的抽象类,专门用于派生子类。

密封类使用sealed关键字

密封类与普通抽象类的区别在于

  • 密封类的子类是固定的,密封类的子类必须与密封类本身在同一个文件中,在其他文件中则不能为密封类派生子类,这样就限制了在其他文件中派生子类
  • 密封类的所有构造方法都是private的,自动添加
  • 密封类的直接子类必须在同一个文件中,但是间接子类不需要在同一个文件中
  • 使用密封类的好处是密封类的子类数量是有限的(在同一个文件中不可能拥有太多直接子类)
//定义一个密封类,其实就是抽象类
sealed class Apple{
    abstract fun taste()
}

open class RedFuJi:Apple(){
    override fun taste() {
        println("红富士苹果香甜可口")
    }
}

data class Gala(var weight:Double):Apple(){
    override fun taste() {
        println("嘎啦果更清脆,重量为:${weight}")
    }
}


fun main(args:Array<String>){
    //使用Apple声明变量,用子类实例赋值
    var ap1:Apple=RedFuJi()
    var ap2:Apple=Gala(2.3)
    ap1.taste()
    ap2.taste()

}
           

打印

红富士苹果香甜可口

嘎啦果更清脆,重量为:2.3

16、接口的定义

与java的接口非常相识

基本语法如下

【修饰符】interface 接口名:父接口1,父接口2...{
零到多个属性定义
零到多个方法定义
零到多个嵌套类,嵌套接口,嵌套枚举定义
}
           
  • 接口只能继承接口不能继承类
  • 默认采用public ,可以采用public、internal、private修饰
  • 接口定义的是一种规范,因此接口中不能包含构造器和初始化块定义,
  • 接口中定义的方法既可以是抽象方法,也可以是非抽象方法,如果一个方法没有方法体,或一直只读属性没有定义getter方法,或者一个读写属性没有定义getter方法和setter方法,则该属性或方法为抽象的
  • 需要被实现类重写的方法,用public修饰,默认也是public
  • 不需要被实现类重写的方法可以用public也可以用private修饰
interface Outputable{
    //只读属性定义了getter方法,非抽象属性
    val name:String
    get()="输出设备"
    //只读属性没有定义getter方法,抽象属性
    val brand:String
    //读写属性没有定义getter、setter方法,抽象属性
    var category:String
    //接口定义的抽象方法
    fun out()
    fun getData(msg:String)
    //在接口中定义的非抽象方法,可使用private修饰
    fun print(vararg msgs:String){
        for (msg in msgs){
            println(msg)
        }
    }
    //在接口中定义的非抽象方法,可使用private修饰
    fun test(){
        println("接口中test()方法")
    }
    
}
           

17、接口的继承

接口支持多继承:一个接口可以有多个直接父接口,并获得父接口中定义的所有方法、属性

多继承接口排在:后面,用逗号,隔开

interface InterfaceA{
    val propA:Int
        get()=5
        fun testA()
}

interface InterfaceB{
    val propB:Int
    get() = 6
    fun testB()
}

interface interfaceC:InterfaceA,InterfaceB{
    val propC:Int
    get() = 7
    fun testC()
}
           

18、使用接口

接口不能用于创建实例,但可以用于声明变量,当使用接口来声明变量时,这个引用类型的变量必须引用到其实现类的对象。

接口的主要用途

  • 就是被实现类实现
  • 定义变量,也可以用于强制类型转换
  • 被其他类实现

一个类可以实现多个接口,直接将被实现的多个接口、父类放在英文冒号之后,且父类、接口之间没有顺序要求,只需要用英文逗号隔开即可

interface Outputable{
    //只读属性定义了getter方法,非抽象属性
    val name:String
        get()="输出设备"
    //只读属性没有定义getter方法,抽象属性
    val brand:String
    //读写属性没有定义getter、setter方法,抽象属性
    var category:String
    //接口定义的抽象方法
    fun out()
    fun getData(msg:String)
    //在接口中定义的非抽象方法,可使用private修饰
    fun print(vararg msgs:String){
        for (msg in msgs){
            println(msg)
        }
    }
    //在接口中定义的非抽象方法,可使用private修饰
    fun test(){
        println("接口中test()方法")
    }

}
interface Product{
    fun getProductTime():Int

}

const val MAX_CACHE_LINE=10
//让printer类实现Outputable和Product接口
class Printer:Outputable,Product{
    private val printData=Array<String>(MAX_CACHE_LINE,{""})
    //用以记录当前需打印的作业数
    private var dataNum=0
    override val brand:String="HP"
    //重写接口的抽象只读属性
    override var category:String="输出外设"
    override fun out() {
        //只要还有作业就一直打印
        while (dataNum>0){
            println("打印机打印:"+printData[0])
            //把作业队列整体前移一位,并将剩下的作业数减1
            System.arraycopy(printData,1,printData,0,--dataNum)
        }
    }

    override fun getData(msg: String) {
        if (dataNum>=MAX_CACHE_LINE){
            println("输出队列已满,添加失败")
        }else{
            //把打印数据添加到队列里,已保存数据的数量加1
            printData[dataNum++]=msg
        }
    }

    override fun getProductTime(): Int {
        return 45
    }

}

fun main(args:Array<String>){
    //创建一个Printer对象,当成Output使用
    var o:Outputable=Printer()
    o.getData("轻量级javaEE企业应用实战")
    o.getData("疯狂java讲义")
    o.out()
    o.getData("疯狂安卓讲义")
    o.getData("疯狂ajax讲义")
    o.out()
    //调用Outputable接口中定义的非抽象方法
    o.print("孙悟空","猪八戒","白骨精")
    o.test()
    //创建一个printer对象,当成product使用
    val p:Printer=Printer()
    println(p.getProductTime())
    //所有接口类型的应用变量都可以直接赋值给Any类型的变量
    val obj:Any=p

}
           

打印:

打印机打印:轻量级javaEE企业应用实战

打印机打印:疯狂java讲义

打印机打印:疯狂安卓讲义

打印机打印:疯狂ajax讲义

孙悟空

猪八戒

白骨精

接口中test()方法

45

19、接口和抽象类

同java。一样一样的

继续阅读