kotlin - 类与对象

kotlin - 类与对象

类的属性

类的属性的作用其实就是用于保存该类对象的状态数据的。kotlin中如果不写任何修饰符那么这个属性的访问权限默认为public的。类的属性不需要我们编写setter和getter方法,需要自定义的情况除外。

class Person {
    var name: String = ""
    var age: Int = 0
    lateinit var address:String
}

kotlin规定类的属性必须要有初始化器,简单的说就是要有初始化值,如果你不希望属性一上来就初始化可以使用lateinit这个关键字。lateinit这个关键字表示延迟初始化。

lateInit修饰的属性

kotlin中用lateInit来解决属性的延时初始化。使用lateInit的关键修饰的属性,可以不在定义属性时或在构造器中指定初始化值。

lateInit的的使用条件

  • lateInit只能修饰在类体中声明的可变属性(使用val和在主构造中声明的属性都不行)
  • lateInit修饰的属性不能自定义getter和setter方法。
  • lateInit修饰的属性必须是非空类型。
  • lateInit修饰的属性不能是基础类型(java对应的8大基本类型)。
    注意:lateInit修饰的属性由于在定义和构造方法中没有指定初始化值,所以在使用的不能直接访问,要确认是否在使用之前初始化过。

内联属性

inline关键字可以修饰没有幕后字段的属性,至于什么时候没有幕后字段大家可以去看《幕后字段和幕后属性》这篇文章。inline可以直接修饰这个属性(相当于同时修饰getter和setter方法了)或者修饰getter和setter中任意一个。
示例代码如下:

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

class Product{
    var productor:String?=null
    //inline修饰getter方法,表明读取时会内联化
    val proName:Name
    inline get()= Name("guojingbu","非常棒!!")
    //inline修饰了setter方法,表明设置值得时候会内联化
    var author:Name
    get() = Name("guojingbu","")
    inline set(value) {
        this.productor=value.name
    }
    //inline直接修饰属性,表明读取和设置属性值时都是内联化。
    inline var pubHouse:Name
    get() = Name("中国人民出版社","无")
    set(value) {
        this.productor=value.name
    }
}

类的构造方法

在 Kotlin 中的一个类可以有0~1个主构造函数以及一个或多个次构造函数

主构造函数

主构造函数是类头的一部分:它跟在类名(与类型参数)后

class Person constructor(name:String,age:Int) {}

如果主构造函数没有任何注解或者可见性修饰符,constructor关键字可以省略。

class Person (name:String,age:Int){}

主构造函数虽然不能定义执行体,但是可以定义一些形参,这些形参可以在属性声明、初始化块中使用。如下代码:

class Person (a:String,b:Int) {
    var name: String =a
    var age: Int = b
    init {
        this.name=a
        this.age=b
    }
}

初始化块

kotlin中初始化块通过init关键字来标识,初始化块可以有多个,在实例初始化时按照它们的出现顺序执行。代码如下:

class Person (name:String) {
    val firstProperty="firstProperty = $name".also(::println)
    init {
       println("我是初始化器1")
    }
    val secondProperty="secondProperty= ${name}.length".also(::println)
    init {
       println("我是初始化器2")
    }
}

上面的代码打印的结果如下:

firstProperty = guojingbu
我是初始化器1
secondProperty= 9
我是初始化器2

从打印结果我们可以看出属性的初始化和初始化块是按顺序执行的,我们不能在前面的初始化块中给后面声明的属性进行初始化。

次构造函数

  • 次构造函数也是通过constructor来实现,代码如下:
class Person3{
    var name: String=""
    constructor(name: String){
        this.name = name
    }
}
  • 如果自定义了主构造函数那么次构造函数必须直接或间接调用主构造函数,代码如下:
class Person3(){
    var name: String=""
    var age:Int=0
    constructor(name: String):this(){
        this.name = name
    }
    constructor(name: String, age: Int) : this(name) {}
}

**类属性的初始化和类的初始化器都属于主构造函数的一部分,**所以会在次构造之前执行,代码如下:

class Person3(){
    var name: String="属性初始化了----".also(::println)
    constructor(name: String):this(){
        println("次构造执行了----")
        this.name = name
    }
    init {
        println("初始化器执行了----")
    }
}

打印结果如下:

属性初始化了----
初始化器执行了----
次构造执行了----

继承

在kotlin中所有的类没有指定超类的类默认超类是Any。代码如下:

fun main(args:Array<String>){
    println("Example is Any = ${Example() is Any}")
}

class Example

输出:

Example is Any = true

Kotlin中的类默认是final类型的,想要被继承,得用“open”关键字修饰。

open class Animal{}
class Dog: Animal() {
}

子类的所有构造方法必须直接或间接调用一个父类的构造方法

open class Animal {
    constructor(name: String) {
    }
}
class Dog : Animal {
    constructor(name: String) : super(name) {
    }
    constructor(name: String,age: Int):this(name){
    }
}

方法的覆盖

kotlin中能被子类覆盖的方法必须要用open关键字修饰,代码如下:

open class Shape {
    open fun draw() {
    }
}
class Circle() : Shape() {
    override fun draw() {   
    }
}

方法的重写基本和java的规则一致,如果不希望父类的方法被子类重写就不要用open关键字修饰方法。kotlin默认类的方法和属性都是final的。

覆盖遵循的规则

覆盖要遵循两同两小一大规则:

  • 两同:方法名相同、方法列表相同
  • 两小:子类方法的返回值类型应比父类方法的返回类型更小或相等;子类方法声明抛出的异常应比父类方法声明抛出的异常更小或相同。
  • 一大:子类方法的访问权限应比父类方法的访问权限更大或相等。

属性的覆盖

kotlin中属性的覆盖和类成员方法的覆盖是相似的,同样是需要用open关键字来修饰属性。父类中val关键字修饰的属性,可以在子类中覆写为var关键字修饰,反之则不能。代码如下:

open class Shape {
    open val sideSize:Int=0
}
class Circle() : Shape() {
    override var sideSize:Int=1
}

注意:子类成员的访问权限可以比父类的大不能比父类的小。

调用父类的属性和方法

子类要调用父类的方法和属性可以通过super关键字。代码如下:

open class Shape {
    open val name: String = "Shape"
    open fun draw() {}
}

open class Rectangle : Shape() {
    override var name: String = super.name
    override fun draw() {
        super.draw()
    }
}

内部类可以调用外部类父类的方法使用"super@外部类名称"的方式。代码实例如下:

open class Parent {
    open fun method() { println("Parent.method") }
}
class Child:Parent(){
    inner class Inner{
        fun test(){
            super@Child.method()
        }
    }
}

覆盖的规则

当继承的父类和实现的接口中出现了相同的方法时,可以使用super<类名>的形式来指定访问哪个方法。示例代码如下:

interface Action {
    fun eat() {
        println("Action")
    }
}

open class Animal {
    open fun eat() {
        println("Animal")
    }
}

class Dog() : Animal(), Action {
    override fun eat() {
        super<Action>.eat()
        super<Animal>.eat()
    }
}

方法与函数

顶层函数与类的成员方法的区别

  • 顶层函数不能使用protected、abstract
    和final修饰符,但是类中的方法可以使用public、internal、private、final、abstract、open修饰符。
  • 调用方式不同,顶层函数是通过包名.函数名(),而类中的方法通过实例.方法名()

方法与函数的关系

我们一般把类外面的定义的方法称之为函数而把类中定义的方法称之为方法。他们的定义语法相同,他们之间也是可以转换的比如下面的列子:

//方法与函数的关系
class Dog{
    fun eat(food:String){
        println("eat---正在吃饭: $food")
    }
    fun run(){
        println("run---开始跑步")
    }
}
fun main(args:Array<String>) {
    //将Dog类的eat方法赋值给变量et
    //系统会自动推断出变量et的类型是(Dog,String)->Unit
    var et=Dog::eat
    //将Dog类的run方法赋值给变量rn
    //变量rn的类型应该是(Dog) -> Unit
    var rn: (Dog) -> Unit =Dog::run
}

从上面的实例可以看出一下几点:

  • 引用一个类的方法需要使用类名::方法名
  • 当程序将类的方法(run())独立为函数时,方法(run())的调用者(Dog)会作为方法的第一个参数传入。

中缀表示法

infix修饰有一个参数的类方法可以使用中缀表示法调用,类似于双目运算符。
实例如下:

class ApplePack(weight:Int){
    var weight = weight
    override fun toString(): String {
        return "ApplePack:[weight = $weight]"
    }
}
class Apple(weight: Int){
    var weight=weight
    infix fun add(other:Apple):ApplePack{
        return ApplePack(this.weight+other.weight)
    }
}
//调用
fun main(args:Array<String>) {
    val origin = Apple(10)
    var applePack:ApplePack = origin add Apple(5)
    println(applePack.toString())
}

使用中缀表示法调用的条件:

  • 方法需要用关键字infix修饰
  • 方法必须有一个参数
  • 必须要类中或者扩展函数中定义

componentN方法与解构

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

var(name,age,address)= user

上面这行代码的意思是把user对象里面的属性值赋值给name,age,address变量。
kotlin中通过在类中定义operator关键字修饰componentN()方法来实现对象的解构,实例代码如下:

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

    operator fun component1():String{
        return this.name
    }
    operator fun component2():Int{
        return this.age
    }
    operator fun component3():String{
        return this.address
    }
}
//使用
fun main(args:Array<String>) {
    val user = User("guojingbu", 20, "北京")
    var(name,age,address)= user
    println("name = $name  age = $age  address= $address")
}

如果我们想忽略对象的一部分属性可以使用”_“占位,代码如下:

  val user = User("guojingbu", 20, "北京")
 	var(_,age2,address2)=user
   println(" age2 = $age2  address2= $address2")

注意:解构方法的名字必须是以component+数字命名,否则会报错
kotlin中Map就支持解构语句,比如我们可以如下的代码来遍历map集合:

for((key,value) in map){
}

类的导包

kotlin中导包的规则基本与java一致,kotlin的import语句支持as关键字为导入类取一个别名。主要的目的是解决导入多个不同包同名的类。
示例代码:

import java.util.Date
import java.sql.Date as SDate
fun main(args:Array<String>) {
    var date = Date(System.currentTimeMillis())
    var sdate =SDate(System.currentTimeMillis())
    }

类型转换

is和!is运算符

  • kotlin中使用is、!is来检查类型。is或!is前面的类型属于编译时类型,后面的叫运行时类型。is前面的类型必须是后面的类型或者其子类型才能返回true。
  • 使用了is关键字以后is前面的变量类型会自动转换为目标类型。

as和as?运算符

as:是不安全的强制类型转换符,如果转型失败,程序会报ClassCastException异常
as?:安全的强制类型转换符,如果转型失败,程序不会发生异常,而是直接返回null。

你可能感兴趣的:(kotlin,kotlin,开发语言,android)