语言学习——kotlin

Kotlin——面向对象

2021-08-21  本文已影响0人  So_ProbuING

Kotlin面向对象

类和对象

Kotlin提供了定义类、属性、方法等最基本的功能。类可被认为是一种自定义的数据类型,可以使用类来定义变量,所有使用类定义的变量都是引用变量,它们将会引用类的对象。也就是说 所有类都是引用类型

定义类

类被称为某一批对象的抽象,而对象才是一个具体存在的实体
定义类的简单语法:

[修饰符]class 类名[constructor 主构造器]{
零个到多个次构造器定义..
零个到多个属性..
零个到多个方法..
}
class Empty
class User constructor(firstName:String){
}

上述代码就是类定义主构造器,主构造器就是在类头使用Construstor关键字定义一个无执行体的构造器,虽然主构造器不能定义执行体,但可以定义多个形参,这些形参可以在属性声明、初始化块中使用

class User(firstName:)

Kotlin中,如果没有为非抽象类定义任何主或次构造器,系统会自动提供一个无参数的主构造器,该构造器默认使用public修饰,如果为类提供了构造器,系统将不再为该类提供默认构造器

定义属性的语法格式如下

[修饰符]var|val 属性名:类型=[默认值]

定义构造器的语法格式如下:、

[修饰符]constructor (形参列表){
由零条到多条可执行语句组成的构造器执行体
}
class Person {
    //定义两个属性
    var name:String = ""
    var age:Int = 0

    //定义方法
    fun say(content: String) {
        println(content)
    }
}

对象的产生和使用

调用某个类的构造器即可创建这个类的对象,无须使用new关键字

    var p:Person = Person()

如果访问权限允许,那么在类中定义的方法和属性都可以通过对象来调用。
对象.属性|方法(参数)。

    p.name = "张三"
    p.age = 22
    p.say(p.name+p.age)

对象的this引用

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

class ThisInConstructor{
    var foo: Int
    constructor(){
        val foo = 0;
        this.foo = 22
    }
}

与普通方法类似,大部分时候,在构造器中访问其他属性和方法时都可以省略this前缀,但如果构造器中有一个与属性同名的局部变量,又必须在构造器中访问这个被覆盖的属性,则必须使用this前缀
当this作为对象的默认引用使用时,程序可以像访问普通变量一样来访问这个this引用,甚至可以把this当作普通方法的返回值

方法详解

方法是类或对象的行为特征的抽象。

方法与函数的关系

Kotlin的方法与函数是统一的,定义函数和方法的语法相同,而且定义在类中的方法可独立出来。

class Dog{
    fun run(){
        println("run方法")
    }

    fun eat(food: String) {
        println("正在吃${food}")
    }
}

fun main() {
    //定义一个函数类型变量
    var rn:(Dog)->Unit = Dog::run
    val d = Dog()
    rn(d)
    var et = Dog::eat
    et(d,"骨头")

中缀表示法

Kotlin的方法还可以使用Infix修饰,这样该方法就可通过中缀表示法调用。就像这些方法是双目运算符一样。infix方法只能有一个参数。

class Apple(weight: Double){
    var weight = weight

    infix fun add(other: Apple):Apple {
        return Apple(weight+other.weight)
    }

    infix fun drop(other: Apple): Apple {
        return Apple(weight-other.weight)
    }
}
fun main() {
    var apple1 = Apple(3.0)
    var apple2 = Apple(2.1)
    //不使用infix修饰调用
    println(apple1.add(apple2).weight)
    println(apple1.drop(apple2).weight)
    //使用infix修饰调用
    println((apple1 add apple2).weight)
    println((apple1 drop apple2).weight)

componentN方法与解构

Kotlin允许将一个对象的N个属性 解构给多个变量

var(name.pass)=user

上面这行代码相当于将user对象的两个属性分别赋值给name、pass两个变量,这两个变量的类型会根据user对象的属性类型来判断
实际上kotlin会将上面的赋值代码转换为:

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

如果希望将对象解构给多个变量,那么必须为该对象的类定义componentN()方法。程序希望将对象解构给几个变量,就需要为该类定义几个componentN()方法,并且该方法需要使用operator修饰

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

    operator fun component1():String{
        return this.name
    }

    operator fun component2():String{
        return this.pass

    }

    operator fun component3():Int{
        return this.age
    }
}
fun main() {
    val user= User("a","b",1)
    //将user对象解构给2个变量
    val (name,pass:String) = user
    println(name)
    println(pass)
    //将user对象解构给3个变量
    val (name2,pass2,age2) = user
    println(name2)
    println(pass2)
    println(age2)

在某些时候,程序希望解构对象后面几个componentN()方法的返回值、忽略前面几个componentN()方法的返回值,此时可通过下划线(_)来占位

    var(_,pass3,age3) = user
    println(pass3)
    println(age3)

数据类和返回多个值的函数

Kotlin本身并不支持定义返回多个值的函数或方法,但我们可以让Kotlin函数返回多个值——让Kotlin返回一个支持解构的对象
Kotlin提供了一种特殊的类:数据类,数据类专门用于封装数据
数据类需要使用data修饰,还需要满足:

fun factorial(n: Int): Result {
    if (n==1) {
        return Result(1,"成功")
    }else if(n>1){

        return Result(2,"大于1")
    }else{
        return Result(-1,"小于1")
    }
}
fun main() {
    //通过解构获取函数的两个返回值
    var(rt,status) = factorial(6)
    println(rt)
    println(status)

Kotlin标准库中提供了Pair和Triple两个数据类,Pair数据类可包含两个任意类型的属性,Triple可包含三个任意类型的属性,方便直接使用

在Lambda表达式中解构

Kotlin允许对Lambda表达式使用解构,如果Lambda表达式的参数是支持解构的类型(Pair或Map.Entry等),它们都具有operator修饰的componentN()方法,那么即可通过将它们放在括号中引入多个新参数来代替单个参数

{a -> ...} 一个参数
{a,b->...}两个参数
{(a,b) ->...}一个解构对
{(a,b),c->....}一个解构对和第三个参数

属性和字段

Kotlin的属性相当于Java的成员变量。

读写属性和只读属性

Kotlin使用val定义只读属性,使用var定义读写属性。系统会为val属性生成getter方法,会为var属性生成getter和setter方法
在定义普通属性时,要么在定义时指定初始值,要么在构造器中指定初始值

自定义getter和setter

自定义getter和setter方法中可以假如自己的控制逻辑,其中getter是一个形如get(){}:无参数、带一个返回值的方法,setter是一个形如set(){}:带一个参数,无返回值的方法

class User(first: String, last: String){
    var first:String = first
    var last:String = last
    val fullName:String
    //自定义get方法
    get() {
        return "${first}${last}"
    }
}

如果是一个读写属性,那么程序既可以重写它的getter方法,也可以重写它的setter方法

幕后字段

在Kotlin中定义一个普通属性时,Kotlin会为该属性生成一个field、getter、setter方法。Kotlin为该属性所生成的field就被称为幕后字段

那么Kotlin何时会为属性生成幕后字段呢?

Kotlin允许开发者在getter或setter方法中通过field关键字引用系统自动生成的字段。

class Person(name: String, age: Int) {
    var name = name
        set(newName) {
            if (newName.length > 6 || newName.length < 2) {
                print("输入长度不合法")
            }else{
                field =newName
            }
        }
    var age = age
        set(newAge) {
            if (newAge>200||newAge<0) {
                println("输入的年龄不合法")
            }else{
                field = newAge
            }
        }

}

fun main() {
    var p = Person("wx",22)
    //属性赋值
    p.age = 2000
    p.age = 100
    println(p.age)

注意:

当程序重写getter或setter方法时,不能通过点语法来对name、age赋值,假如在name的setter方法中使用点语法对name赋值,由于点语法本质时调用setter方法,这样就会形成无限递归。所以在getter、setter方法中需要通过field关键字引用幕后字段进行赋值

幕后属性

在个别情况下,开发者希望自己定义field,并为该field提供setter、getter方法。
幕后属性就是用Private修饰的属性

class BackingProperty(name: String){
    private var _name = name
    var name
    get() = _name
        set(newName) {
            if (newName.length > 6 || newName.length < 2) {
                print("输入长度不合法")
            }else{
                _name =newName
            }
        }
}

延迟初始化属性

Kotlin提供了lateinit修饰符来实现属性的延迟初始化,使用lateinit修饰的属性,可以在定义该属性时和在构造器中都不指定初始值
对lateinit修饰符的使用有以下限制

class lateUser{
   lateinit var name:String
   lateinit var age:Date
}
fun main() {
    var l = lateUser()
    l.name = "asda"
    l.age = Date()

内联属性

inline修饰符可修饰没有幕后字段的属性的getter或setter方法,既可单独修饰属性的getter或setter方法,也可修饰属性本身
对于使用inline修饰的getter、setter方法,就像前面介绍的内联函数一样,程序在调用getter和setter方法时也会执行内联化

使用inline修饰必须是没有幕后字段的属性

隐藏和封装

封装:面向对象的三大特征之一,指的是将对象的状态信息隐藏在对象的内部,不允许外部程序直接访问对象内部的信息,而是通过该类所提供的方法来实现对内部信息的操作和访问

包和导包

Kotlin的包和Java的包相同,既是逻辑上的一个程序单元,也是一个命名空间。如果希望把函数、类放在指定的包下,需要在源程序的第一个非注释行放置如下代码

package packagename

为了使用其他包中的函数和类,Kotlin同样使用import执行导入
Kotlin的import语法和Java类似,同样支持精确导入和通配符导入

import foo.Bar
import foo.*

Kotlin的import语句支持as关键字,可以为导入的类指定别名

Kotlin的默认导入

Kotin默认会导入如下包

访问控制修饰符

Kotlin提供了4个访问控制修饰符:private、internal、protected和public

Kotlin不同作用域中的成员可支持的访问控制符

  1. 位于包内的顶层成员
    位于包内的顶层成员包括:顶层的类、接口、函数、属性只能使用private、internal和public其中之一,不能使用Protected修饰符
  1. 位于类、接口之内的成员
    对于位于类、接口之内的成员(顶层类、接口、函数、属性)能使用Private、internal、protected和public之一
  1. 类的主构造器
    类的主构造器可以使用Privae、internal、protected和public其中之一,默认为Public

深入构造器

主构造器和初始化块

Kotlin的初始化块可以接收主构造器传入的参数
初始化块的语法:

init{
    //初始化块中的可执行代码,可以使用主构造器定义的参数
...
}
初始化块中的代码可以使用主构造器定义的参数,也可以包含任何可执行语句,包括定义局部变量、调用其他对象的方法,以及使用分支、循环语句等
class Person2(name: String) {
    //定义初始化块
    init {
        var a = 6;
        if (a > 4) {
            println("Person2初始化块,a的数值为${a}")
        }
        println("Person2的初始化块,主构造器name的值为${name}")
    }
}

fun main(args: Array<String>) {

    Person2("孙行者")
}
输出:
Person2初始化块,a的数值为6
Person2的初始化块,主构造器name的值为孙行者

从上面的程序可以看出,当程序通过主构造器创建对象时,系统其实就是调用该类里定义的初始化块。主构造器的主要作用就是为初始化块定义参数。也可以说初始化块就是主构造器的执行体。

如果希望为对象的属性显式指定初始值,也可以通过初始化块来指定

如果程序员没有为Kotlin类提供任何构造器,则系统会为这个类提供一个无参数的主构造器,这个构造器的执行体为空。也就是说无论如何,Kotlin类至少包含一个构造器

通过初始化块对类进行自定义的初始化操作

class ConstructorTest(name:String,count:Int){
    //定义类的属性
    var name:String
    var count:Int
    //初始化块
    init {
        this.name = name
        this.count = count
    }
}
fun main(args: Array<String>) {

    var con = ConstructorTest("测试",123)
    //输出属性
    println(con.count)
    println(con.name)
}

一旦程序员提供了自定义的构造器,系统就不再提供默认的构造器

如果希望该类保留无参数的构造器或者希望有多个初始化过程,则可以为该类提供多个构造器

次构造器和构造器重载

Kotlin允许使用constructor关键字定义N个次构造器,次构造器类似于Java传统的构造器。Kotlin要求所有的次构造器必须委托调用主构造器,可以直接委托或通过别的次构造器间接委托。”委托“其实就是要先调用主构造器,然后才执行次构造器

同一个类里具有多个构造器,多个构造器的形参不同,即被称为构造器重载。程序可通过不同的构造器来创建多个对象。但不管使用哪个构造器,首先都要调用主构造器

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
    }

}
fun main(args: Array<String>) {
    //通过无参数的构造器创建对象
    var oc1 = ConstructorOverload()
    //有参数的构造器创建对象2
    var oc2 = ConstructorOverload("a",100)
    //访问oc1的属性
    println(oc1.name + oc1.count)
    //访问oc2的属性
    println(oc2.name + oc2.count)
}

主构造器声明属性

Kotlin允许在主构造器上声明属性,直接在参数之前使用var或val即可声明属性,var读写属性、val只读属性。当程序调用这种方式声明的主构造器创建对象时,传给该构造器的参数将会赋值给对象的属性

class Item(val code:String, var price:Double){}

fun main(args: Array<String>) {
    var item = Item("cccc", 22.02)
    println(item.code + item.price)
}

主构造器的所有参数为默认值,程序能以构造参数的默认值来调用该构造器,不需要为构造参数传入值

class Item(val code:String = "dasd", var price:Double = 11.00){}

fun main(args: Array<String>) {
    var it = Item()
    println(it.code + it.price)
}
上一篇 下一篇

猜你喜欢

热点阅读