天天看点

疯狂Kotlin讲义学习笔记07章:面向对象(上)对象,中缀,解构,幕后字段、属性,延迟初始化,访问控制符,构造器,继承,重写,super限定,重写,多态,is类型检查,as强制类型转换

1、定义类的标准格式

修饰符 class 类名 [ constructor 主构造器]{
	零到多个次构造器定义
	零到多个属性....
	零到多个方法....
}
           

修饰符open是final的反义词,用于修饰一个类,方法或属性,表明类可派生子类、方法或属性可被重写

定义主构造器

  • 如果主构造器没有任何注解及修饰符,则可以省略constructor关键字如:
  • 可以定义0-1个主构造器,0-n个次构造器
  • 如果没有定义主构造器,系统会默认定义一个以public修饰的构造器(同java)

定义属性

标准格式为:

【修饰符】var|val 属性名:类型【=默认值】
【getter】
【setter】
           

修饰符:public、 protected、internal、private、final、open、abstruct

val是只读

定义方法

同函数的定义

定义构造器的方法

【修饰符】constructor(形参列表){
构造器执行体
}
           

示例

class Demo002 {
    var name:String=""
    var age:Int=0
    
    fun say(content:String){
        println(content)
    }
}
           

2、对象的产生和使用

无需使用new关键字创建对象

格式:

var person:Person=Person()
           

通过点句法访问属性和方法

fun main() {
    var person:Person=Person()
    person.name="zhangsan"
    person.age=18
    println(person.name)
    println(person.age)
    person.say("say hello to all")
}
           

打印

zhangsan

18

say hello to all

3、对象的this应用(和java基本一致)

this总是指向调用该方法的对象。根据this出现位置的不同,this作为对象的默认应用有两种情形

this作为对象的默认引用有两种情形

  • 在构造器引用该构造器正在初始化的对象
  • 在方法中应用调用该方法的对象

this关键字的最大作用就是让类的一个方法访问该类的另一个方法或属性

当局部变量和属性同名的时候,程序要访问隐藏的属性的时候必须使用this关键字

class ReturnThis{
    var age=0
    fun grow():ReturnThis{
        age++
        return this
    }
}
fun main() {
    var rt=ReturnThis()
    //可以持续调用同一个方法
    rt.grow().grow().grow()
    println(rt.age)
}
           

打印

3

4、方法与函数的关系

函数与方法是统一的。其实两者之间并没有太多的区别

获取的类方法名前面添加两个冒号(:)

class Dog{
    fun run(){
        println("这是一个方法")
    }

    fun eat(food:String){
        println("现在正在吃"+food)
    }
}
fun main() {
    var rn:(Dog)->Unit=Dog::run//直接使用类名加方法名的方式应用
    val d=Dog()
    rn(d)

    var et=Dog::eat//et的变量类型应该是(Dog,String)->Unit,这里是及时系统自动推断出来的。调用类的对象方法,必须要给他一个对象,尽管方法体里面没有对象参数
    et(d,"肉骨头")
}
           

打印

这是一个方法

现在正在吃肉骨头

5、中缀表示法

中缀方法可以是方法想双目运算符一项

class ApplePack(weight:Double){
    var weight=weight
    override fun toString(): String {
        return "ApplePack[weight=${this.weight}]"
    }
}

class Apple(weight: Double){
    var weight=weight
    override fun toString(): String {
        return "Apple[weight=${this.weight}]"
    }

    //定义中缀方法,使用infix修饰
    infix  fun add(other:Apple) :ApplePack{
        return ApplePack(this.weight+other.weight)
    }

    infix  fun drop(other:Apple) :Apple{
        this.weight-=other.weight
        return this
    }

}
fun main() {
    var origin=Apple(3.5)
    //使用add方法。add是一个双目运算符
    val ap=origin add Apple(2.4)//这里有个匿名的Apple实例,add方法没有使用小括号
    println(ap)

    origin drop Apple(1.5)
    println(origin)
}
           

ApplePack[weight=5.9]

Apple[weight=2.0]

6、componetN方法与结构

kotlin允许将一个对象的N个属性 “解构” 给多个变量,写法如下

实际上执行了

var name=user.component1()
var pass=user.component2()
           

有几个属性就执行几个n

class User(name:String,pass:String,age:Int){
    var name=name
    var pass=pass
    var age=age

    //定义operator修饰的componentN方法,用于解构
    operator fun component1():String{
        return this.name
    }
    //定义operator修饰的componentN方法,用于解构
    operator fun component2():String{
        return this.pass
    }
    //定义operator修饰的componentN方法,用于解构
    operator fun component3():Int{
        return this.age
    }
}
fun main() {
   //创建user对象
    val user=User("zhangsan","558899",23)
    //将user对象解构给2个变量
    var (name,pass:String)=user
    println(name)
    println(pass)
    println("-----")

    var (name2,pass2,age)=user
    println(name)
    println(pass)
    println(age)
    println("-----")

    var(_,pass3,age2)=user
    println(name)
    println(pass)
    println(age)
}
           

打印

zhangsan
558899
-----
zhangsan
558899
23
-----
zhangsan
558899
23
           

7、数据类型和返回多个值的函数

数据类型就是为了简化解构的实现,用来封装数据的类称之为数据类型

数据类型除了使用data修饰符外,还需要满足如下要求

  • 主构造器至少需要由一个参数
  • 主构造器的所有参数需要用val或var声明为属性
  • 数据类不能用abstract,open,sealed修饰,也不能定义成内部类
  • 数据类可以继承其他类

定义数据类后,系统自动为数据类型生成如下内容

生成equals()/hashCode()方法

自动重写toString()方法,返回形如“User(name=John,age=42)”的字符串

为每个属性自动生成operator修饰的componentN()方法

生成copy()方法,用于完成对象复制

后面暂时没看懂

8、在lambda表达式中解构

后面暂时没看懂

9、读写属性和只读属性

val为只读属性,只会为生成getter方法

var为读写属性,系统为其生成getter和setter方法

定义属性的时候一般需要用权限修饰符进行修饰

显示指定方法为setter和getter方法与java一样

属性的调用使用点(.)句法

10、自定义getter和setter

大致和java的自定义的getter方法和setter方法类似

11、幕后字段

设置getter和setter方法是可以自定义属性的get和set方式,实际上就是重写getter和setter方法

12、幕后属性

使用private修饰的属性就是幕后属性。不生成任何getter和setter,因此程序不能直接访问幕后属性,开发者需要为幕后属性提供getter和setter方法

13、延迟初始化属性

延迟初始化是暂时不在定义属性之初赋值。

kotlin提供lateinit修饰符来解决属性的延迟初始化,对lateinit修饰符有如下限制

  • 只能修饰var定义的可变属性,val定义的不可以
  • lateinit修饰的属性不能有自定义的getter或setter方法
  • lateinit修饰的属性必须是非空类型
  • lateinit修饰的属性不能是原生类型(也就是java的8种基本类型对应的属性)

    类文件

class User {
    //设置延迟属性
    lateinit var name:String
    lateinit var birth:Date

}
           

主函数

fun main(args:Array<String>){
    var user1=User()

    //println(user1.name) //报初始化异常
    //println(user1.birth)//报初始化异常

    user1.name="zhangsan"
    user1.birth= Date()
    println(user1.name) //不再报初始化异常
    println(user1.birth)//不再报初始化异常

}
           

打印

zhangsan

Thu Sep 17 09:13:12 CST 2020

14、内联属性

实际就是对没有幕后属性(自定义getter、setter)的属性进行修饰,使其拥有了内联化(设定getter、setter)

使用inline修饰符,既可以修饰属性,也可以修饰getter或setter方法

class Name(name: String,desc:String){
    var name=name
    var desc=desc
}

class Product{
    var porductor:String?=null
    //inline修饰属性的getter方法,表明读取属性时会内联化
    val proName:Name
    inline get()=Name("疯狂安卓讲义","最系统的kotlin书")
    //inline修饰属性的setter方法,表明设置属性值时,会内联化
    var author:Name
    get() = Name("李刚","无")
    inline set(v){
        this.porductor=v.name
    }
    //inline修饰属性本身,表明读取getter和设置setter属性时都会发生内联化
    inline var pubHouse:Name
    get()=Name("电子工业出版社","无" )
    set(v){
        this.porductor=v.name
    }
}
           

15、包和导包

与java的包概念相同

package packagename

导包使用import关键字,是java的import和import static的合体,不仅可以导入类,还可以导入如下内容

  • 顶层函数及属性
  • 在对象生命中声明的函数和属性
  • 枚举常量

16、kotlin的默认导入

类似于List,Map都默认自动导包了

17、使用访问控制符

private:同java

internal:internal成员可以在该类的内部或文件的内部或者同一模块内被访问

protected:在该哪类的内部或文件的内部或者子类中被访问

public:同java

取消了默认访问权限,引入internal权限

取消了protected的包访问权限

默认访问权限是public

位于包内的顶层成员

对于位于包内的顶层成员(包括顶层类、接口、函数、属性),只能使用private,internal,和public其中之一,不能使用protected修饰符

位于类,接口之内的成员

位于类、接口之内的成员(包括顶层类,接口,函数,属性),能使用private,internal,protected和public其中之一。

类的住构造器,由于是在类头部分声明的,如果需要为主构造器指定访问权限修饰符,则一定要使用constructor关键字,并在该关键字前面添加private、internal、protected和public其中之一。如果不为主构造器指定访问全休修饰符,那么主构造器默认的访问权限修饰符是public

18、主构造器和初始化块

类定义时可以定义0-1个主构造器,0-n个次构造区,如果主构造器没有任何的注解或可见性修饰符,则可以省略constructor关键字

主构造器可以有形参,但没有执行体,形参有两点作用

  • 初始化块可以使用主构造器定义的形参
  • 在声明属性石可以使用主构造器定义的形参
fun main(args:Array<String>){
    var person=Person("zhangsanfeng")

}
class Person(name:String){
    //定义一个初始化块
    init{
        var a=6
        if (a>4){
            println("person初始化块,局部变量a的值大于4")
        }
        println("Person的初始化块")
        println("name参数为:${name}")
    }
    //定义第二个初始化块
    init {
        println("Person的第二个初始化块")
    }
}
           

打印:

person初始化块,局部变量a的值大于4

Person的初始化块

name参数为:zhangsanfeng

Person的第二个初始化块

构造器最大的用处就是在创建对象时执行初始化,可见,主构造器的作用就是为初始化块定义参数,因此主构造器更像是初始化块的一部分,也可以说,初始化块就是主构造器的执行体

一旦程序员提供了自定义的构造器,系统就不在提供默认的构造器。也可以提供多个构造器,构成构造器重载

fun main(args:Array<String>){
    var tc=ConstructorTest("疯狂java讲义",9000)
    println(tc.name)
    println(tc.count)

}
class ConstructorTest(name:String,count:Int){
    var name:String
    var count:Int
    init {
        this.name=name
        this.count=count
    }
}
           

打印:

疯狂java讲义

9000

19、次构造器和构造器重载

kotlin要求素有的次构造器都必须委托调用主构造器,可以直接委托或通过别的次构造器简介委托。所谓“委托”、其实就是要先调用主构造器(执行初始化快中的代码),然后才执行次构造器代码

如果两个构造器有相同的代码,可以放在初始代码块中执行(初始代码块总是先于构造器执行)如果初始化代码块需要参数,则可将参数放在主构造器中定义,从而提高代码的复用性。

系统通过形参列表的不同来区分不同的次构造器(构造器重载)

fun main(args:Array<String>){
   //无参构造对象
    var c1=ConstructorOverLoad()
    var c2=ConstructorOverLoad("zhangsan",33)
    println("${c1.name},${c1.count}")
    println("----")
    println("${c2.name},${c2.count}")

}
class ConstructorOverLoad{
    var name:String?
    var count:Int
    init {
       println("初始化块")
    }
    constructor(){
        name=null
        count=0
    }
    constructor(name:String,count:Int){
        this.name=name
        this.count=count
    }
}
           

打印:

初始化块
初始化块
null,0
----
zhangsan,33
           

又例

fun main(args:Array<String>){
    var user1=User("孙悟空")
    println("${user1.name}--${user1.age}--${user1.info}")
    var user2=User("白骨精",16)
    println("${user2.name}--${user2.age}--${user2.info}")
    var user3=User("蜘蛛精",26,"吐丝结网")
    println("${user3.name}--${user3.age}--${user3.info}")
}
//定义主构造器

class User (name:String){
    var name:String
    var age:Int
    var info:String?=null
    init {
        println("User的初始化块")
        this.name=name
        this.age=0
    }
    //委托给主构造器
    constructor(name:String,age:Int):this(name){
        this.age=age
    }
    //委托给次构造器并通过次构造器间接委托给主构造器
    constructor(name: String,age: Int,info:String):this(name,age){
        this.info=info
    }
}
           

打印:

孙悟空–0--null

User的初始化块

白骨精–16–null

User的初始化块

蜘蛛精–26–吐丝结网

主构造器声明属性

类的主构造器属性

fun main(args:Array<String>){
   var im=Item("123456",6.7)
    println(im.code)
    println(im.price)
}
//定义主构造器
class Item (val code:String,var price:Double){

}
           

打印

123456

6.7

如果主构造器的所有参数都有默认值,程序能以构造参数的默认值来调用该构造器(即不需要为构造参数传入值),此时看上去就像调用无参数的构造器

//定义主构造器
class Customer(val name:String="匿名",var addr:String="天河"){

}
fun main(args:Array<String>){
    //调用有参的主构造器
    var ct=Customer("孙悟空","花果山")
    println(ct.name)
    println(ct.addr)
    //以构造参数的默认值调用构造器,看上去像调用无参数的构造器
    var ctm=Customer()
    println(ctm.name)
    println(ctm.addr)
}
           

打印:

孙悟空

花果山

匿名

天河

20、继承的语法

使用冒号:来继承

Any类是所有类的父类,类似于java的Object类

如果kotlin类未显示指定父类,默认继承自Any类

kotlin的类默认为final修饰,如果需要该类被继承,应当在前面用open进行修饰

kotlin的子类构造器最终都要调用父类的构造器,kotlin将其称之为委托父类构造器,这里分两种情况

  • 子类主构造器调用父类构造器
  • 子类次构造器调用父类构造器

子类主构造器调用父类构造器

子类如果没有显式声明主构造器,默认有一个主构造器,因此要在声明继承是委托调用父类构造器

//定义基类
open class BaseClass{
    var name:String
    constructor(name:String){
        this.name=name
    }
}

//子类没有显式声明主构造器,默认有一个主构造器,因此要在声明继承是委托调用父类构造器
class Subclass1:BaseClass("张三"){

}

//子类显式声明主构造器,主构造器必须声明继承是委托调用父类构造器
class Subclass2(name:String):BaseClass(name){

}
fun main(args:Array<String>){
    var s1=Subclass1()
    println(s1.name)
    var s2=Subclass2("李四")
    println(s2.name)
}
           

打印

张三

李四

子类次构造器调用父类构造器

分三种情况调用父类构造器

  • 子类构造器显式使用:this(参数)显式调用本类中重载的构造器,系统根据this(参数)调用中传入的实参列表调用本类中的另一个构造器。最终调用父类构造器
  • 子类构造器显式使用:super(参数)显式调用父类构造器,系统根据super(参数)调用中传入的实参列表调用父类对应的构造器
  • 子类构造器既没有使用super(参数)也没有使用this(参数)条用,系统将会在执行子类构造器之前,隐式调用父类无参数的构造器

    //定义基类

open class BaseClass{
    constructor(){
        println("Base基类的无参构造器")
    }
    constructor(name:String){
        println("Base的带参(String)构造器:${name}的构造器")
    }
}

class Sub:BaseClass{
    //构造器没有显式委托,因此该次构造器将会隐式委托调用父类无参数的构造器
    constructor(){
        println("Sub子类的无参构造器")
    }
    //构造器用super(name)显式委托父类带String参数的构造器
    constructor(name:String):super(name){
        println("Sub的String构造器,String参数为${name}")
    }
    //构造器用super(name)显式委托本类带String参数的构造器
    constructor(name: String,age:Int):this(name){
        println("Subd的String构造器,Int构造器,Int参数为:${age}")
    }
}

fun main(args:Array<String>){
    Sub()
    Sub("Sub")
    Sub("子类",29)
}
           

打印:

Base基类的无参构造器

Sub子类的无参构造器

Base的带参(String)构造器:Sub的构造器

Sub的String构造器,String参数为Sub

Base的带参(String)构造器:子类的构造器

Sub的String构造器,String参数为子类

Subd的String构造器,Int构造器,Int参数为:29

21、重写父类方法

子类继承父类,可以获得父类的全部属性和方法

被子类重写的方法前面依然要用open关键字来修饰,子类重写的方法用override方法修饰

open class Bird{
    open fun fly(){
        println("我在天空中自由自在的翱翔")
    }
}

class Ostrich:Bird(){
    //重写fly方法
    override fun fly(){
        println("我只能在地上奔跑")
    }
}

fun main(args:Array<String>){
    var myostrich=Ostrich()
    myostrich.fly()

}
           

打印:

我只能在地上奔跑

方法要遵循“两同两小一大”,同java

22、重写父类属性

和重写属性相似,父类使用open,子类使用override

属性重写的额外限制

  • 重写的子类属性的类型与父类属性的类型要兼容
  • 重写的子类属性要提供更大的访问权限。子类访问权限要相同或更大,只读属性可以被读写属性重写
//定义基类
open class Item{
    open protected var price:Double=10.9
    open val name:String=""
    open var validDays:Int=0
}

class Book:Item{
    override public var price:Double
    override  var name="图书"
    constructor(){
        price=3.0
    }
}
fun main(args:Array<String>){
  
}
           

23、super限定

子类方法中调用父类中被覆盖的方法或属性,则可以使用super限定

open class Bird{
    open fun fly(){
        println("我在天空中自由自在的翱翔")
    }
}

class Ostrich:Bird(){
    //重写fly方法
    override fun fly(){
        println("我只能在地上奔跑")
    }

    fun callOverideMehod(){
        //子类中显示调用父类中被覆盖的方法
        super.fly()
    }
}

fun main(args:Array<String>){
    var myostrich=Ostrich()
    myostrich.fly()
    myostrich.callOverideMehod()

}
           

打印

我只能在地上奔跑

我在天空中自由自在的翱翔

又一例,重写属性

open class BaseClss{
    open var a:Int=5
}

class SubClass:BaseClss(){
    //重写属性
    override var a:Int=7
    fun  accessOwner(){
        println(a)
    }

    fun accessBase(){
        //访问父类属性
        println(super.a)
    }
}

fun main(args:Array<String>){
   val sc=SubClass()
    sc.accessOwner()
    sc.accessBase()

}
           

打印

7

5

24、强制重写

如果子类从多个直接超类(接口或类)继承了同名的成员,kotlin要求子类必须重写该成员。如果需要在子类中使用super来应用超类中的成员,则可使用尖括号加超类型名限定的super进行引用,如

例子

open class Foo {
    open fun test(){
        println("Foo的test")
    }
}

interface Bar {
    //接口中的成员是默认open的
    open fun test(){
        println("Bar的test")
    }
}

class Wow:Foo(),Bar{
    //编译器要求必须重写test()
    override fun test(){
        super<Foo>.test()//调用父类Foo的test()
        super<Bar>.test()//调用父接口Bar的test()
    }

}
fun main(args:Array<String>){
    var w=Wow()
    w.test()
}
           

打印:

Foo的test

Bar的test

25、多态性

编译时类型,和运行时类型

编译时类型由声明该变量时使用的类型决定,运行时类型由实际付给该变量的对象决定。

当编译时和运行时类型不一致的时候,就称之为多态

向上转型和向下转型是多态的核心概念

26、使用is检查类型

为了保证类型转换不会出错,kotlin提供类型检查运算符is和**!is**

interface Testable{}
fun main(args:Array<String>){
    val hello:Any="hello"//编译时Any类型,运行时是String类型
    println(hello is String)//返回true
    println(hello is Date)//返回false
    println(hello is Testable)//由于hello没有实现Testable接口,返回false
}
           

打印:

true

false

false

kotlin的is和 !is非常智能,只要程序使用is或者!is对变量进行了判断,系统就会自动将变量的类型转换为目标类型。

27、使用as运算符转型

强制转换成运行时类型,需要借助as和as?强制转换类型符号

  • as:不安全的强制转换运算符,如果转型失败,引发异常
  • as?:安全的强制转换运算符,如果转型失败,不会引发异常,而是返回null
fun main(args:Array<String>){
    val obj:Any="hello"//编译时Any类型,运行时是String类型
    val objstr=obj as String
    println(objstr)
}
           

打印:

hello

继续阅读