Kotlin开发笔记:类的创建,单例和匿名对象

访问修饰符

有四种修饰符:

  • 1.public 所有均可访问
  • 2.private 仅当前类中可访问
  • 3.protected 允许派生类的方法访问
  • 4.internal 允许同一个模块的代码访问

创建类

Kotlin中的类和Java不太一样,下面是一个简单的示例代码,我们通过这个示例代码学习Kotlin中的类的基本内容。

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

    //为address字段设置set方法
    var address:String = ""
        set(value) {
        if (value == ""){
            field = "Kotlin之父"
        }else{
            field = value
        }
    }



    constructor(name:String,age:Int,address:String):this(name,age){
        this.address = address
    }

    public var initNumber = 0

    init {
        initNumber = 100
        println("假人已创建")
    }

}

这是我们的Person类,猜一猜它有几个字段,正确答案是4个字段,或者更加通俗的来说,这个类里有四个成员变量,分别为:

  • val name:String
  • val age:Int
  • var address:String
  • var initNumber:Int

我们会发现,其中的name和age字段我们在类中并没有声明,name和age字段的创建是在第一行:

class Person (val name:String,val age:Int)

中的,其实这种写法是省略的写法,真正完整的写法应该是:

class Person constructor(val name:String,val age:Int) 

类名后面跟的constructor是Person的主构造函数,一个类可以有许多构造方法,但是它们都要直接或者间接地调用主构造方法。

当我们在主构造函数中用val或者var声明变量时,就会在类中自动创建字段;如果不用val或者var声明,比如说 name:String 那么将不会自动生成字段。且在默认情况下,对类及其成员的访问是public,构造函数也是public,当主构造函数是public时,就可以将constructor关键字给省略。在Kotlin中,定义类的行实际上定义了主构造函数。

次构造函数

说完了主构造函数,接下来就看次构造函数。**在次构造函数中,我们是不允许用val或者var修饰符来修饰传入的参数的,也就是说,在次构造函数中,我们不允许自动创建字段。**在上述示例中,我们的次构造方法是:

constructor(name:String,age:Int,address:String):this(name,age){
        this.address = address
    }

次构造函数需要调用主构造函数或者其他次构造函数之一,被调用的构造函数在后面用冒号连接起来,上段代码中的:this(name,age)表明的就是调用了主构造函数,有点类似与子类需要调用父类的构造函数一样。

成员变量的set和get

接下来让我们看这部分:

var address:String = ""
        set(value) {
        if (value == ""){
            field = "Kotlin之父"
        }else{
            field = value
        }
    }

这段代码先定义了一个成员变量address并先将其初始化为"",紧跟在后面的是它的set方法,定义一个变量的set或者get方法的格式就是紧跟在成员变量之后,用set代码块或者get代码块

var 变量名:类型 = 值
set(value){
    ....
    field = value
}
get() = 变量名

其中set代码块中的value 和 field 是两个关键字,分别代表set方法传入的值 和 实际的成员变量,get代码块很好懂,直接返回成员变量的值即可。

如果类要实现接口或者继承类后面用冒号连接即可,比如:

class Person constructor(val name:String,val age:Int):Callable<String>,Runnable 

初始化代码块

主构造函数声明是第一行的一部分。参数和属性在构造函数的参数列表中定义。不通过构造函数参数传递的属性也可以在类中定义。如果初始化对象的代码比只设置值更复杂,那么我们可以通过初始化代码块实现:

init {
        initNumber = 100
        println("假人已创建")
    }

不过这里我们的逻辑并没有多复杂。一个类可以有0个或者多个init块。这些块作为主构造函数执行的一部分来执行。init块的执行顺序是自上而下的。在init块中,只能访问已经在块上面定义的属性。

底层的字段和属性

先给段代码:

println("创建类")
val FakePerson = Person("FakeMan",0,"Moon")
println("${FakePerson.name},${FakePerson.age},${FakePerson.address},${FakePerson.initNumber}")
println("\n")

这里我们看似是通过了FakePerson.name方法直接访问了name字段,但是实际上Kotlin在底层还是通过name的get方法来获取的,一切获取值或者设置值在底层都是通过set和get方法实现的。Kotlin中并不公开类的字段。

匿名类

和Java一样。Kotlin中也有匿名类,匿名类的定义是通过object关键字实现的:

fun drawCirCle() {
 val circle = object {
    val x = 10
    val y = 20
    val radius = 30
 }
 println("${circle.x},${circle.y},${circle.radius}")
}    

在drawCircle方法中,我们通过object关键字生成了一个匿名类,该匿名类拥有x,y,radius三个成员变量,在这个方法中可以访问匿名类的字段。

匿名类产生的匿名对象有下面几个限制:

  1. 匿名对象的内部类型不能作为函数或者方法的返回类型。
  2. 匿名对象的内部类型不能用作函数或方法的参数类型
  3. 如果他们作为属性存储在类中,他们将视为Any类型,他们的任何属性或方法都将无法直接访问。

如果匿名类想要实现接口,只要在object关键字后面用冒号连接要实现的接口即可。

fun createTwoInterface():Runnable {
val twoInterface = object :Runnable,AutoCloseable{
    override fun run() {
        println("Implement run")
    }

    override fun close() {
        println("Implement close")
    }
}

 return twoInterface
}

带有对象声明的单例

如果在object关键字和块{}之间放置一个名称,那么Kotlin将认为定义是语句或者声明而不是表达式。使用一个对象表达式声明来穿件一个单例:

object Util{
    fun numberOfProcessors() = println(Runtime.getRuntime().availableProcessors())
}

这样就相当于声明了一个Util单例。

单例也并不局限于拥有方法,他也可以拥有属性和实现接口:

object Sun : Runnable{
    val radiusInKM = 696000
    var coreTemperatureInC = 1500000

override fun run() {
    println("Sun's Run")
    }
}

fun moveIt(runnable:Runnable){
    runnable.run()
} 

该单例实现了Runnable接口并且拥有自己的成员变量。

顶层函数,包和单例

如果我们想要实现一个类似于工具类的类,我们可以结合使用Package,单例和顶层函数:

package PackageTest

fun unitsSupported() = listOf("Mertic","Imperial")

fun precision(): Int = throw java.lang.RuntimeException("Error!!!")

//单例
object Tempterature {
    fun c2f(c:Double) = c * 9.0/5 +32
    fun f2c(f:Double) = (f - 32) * 5.0/9
}

object Distance{
    fun milesToKm(miles:Double) = miles * 1.609
    fun kmToMiles(kms:Double) = kms / 1.609
}

下面就是一个工具类的实现,我们用单例和包将它的方法组织起来,让它具有更好的结构并且可以像工具类一样使用它。总结下来就是:

  • 如果一些函数是高级的,通用的和广泛使用的,那么就把它们放在顶级函数中。
  • 如果一些函数之间的关系比其他的函数更加密切,就把它们放在一个单例中。
  • 如果一些函数存在依赖关系,可以把它们放在一个单例中。

在使用时,我们就可以导包来使用。

完整演示代码

import PackageTest.*
import java.util.concurrent.Callable

fun main(){
    //1.匿名类
    println("匿名类")
    drawCirCle()
    println("\n")
    //2.匿名类实现接口
    println("匿名类实现接口")
    createRuna().run()
    println("\n")
    //3.object 单例
    println("单例")
    Util.numberOfProcessors()
    println("\n")
    //4.单例实现接口
    println("单例实现接口")
    moveIt(Sun)
    println(Sun.radiusInKM)
    println("\n")
    //5.Package + 单例 + 方法
    println("Package + 单例 + 方法")
    println(PackageTest.unitsSupported())
    println(PackageTest.Tempterature.f2c(75.253))
    println(Tempterature.c2f(24.305))
    println("\n")
    //6.创建类
    println("创建类")
    val FakePerson = Person("FakeMan",0,"Moon")
    println("${FakePerson.name},${FakePerson.age},${FakePerson.address},${FakePerson.initNumber}")
    println("\n")
}

//使用匿名内部类--也只能在该方法里访问
fun drawCirCle() {
    val circle = object {
        val x = 10
        val y = 20
        val radius = 30
    }
    println("${circle.x},${circle.y},${circle.radius}")
}

//方法返回一个Runnable 其中的匿名类实现了 Runnable接口
fun createRuna():Runnable {

    var runn = object : Runnable{
        override fun run() {
        println("Execute--Runnable")
        }
    }

    return runn;
}

//实现两个接口的匿名类
fun createTwoInterface():Runnable {
    val twoInterface = object :Runnable,AutoCloseable{
        override fun run() {
            println("Implement run")
        }

        override fun close() {
            println("Implement close")
        }
    }

    return twoInterface
}

//简单实现接口
fun createSimpleInterface():Runnable = Runnable { println("Simple Implement Run Method") }

//单例对象
object Util{
    fun numberOfProcessors() = println(Runtime.getRuntime().availableProcessors())
}

//单例拥有属性字段和方法
object Sun : Runnable{
    val radiusInKM = 696000
    var coreTemperatureInC = 1500000

    override fun run() {
        println("Sun's Run")
    }
}

fun moveIt(runnable:Runnable){
    runnable.run()
}

//创建类
class Person constructor(val name:String,val age:Int):Callable<String>,Runnable {

    //为address字段设置set方法
    var address:String = ""
        set(value) {
        if (value == ""){
            field = "Kotlin之父"
        }else{
            field = value
        }
    }
    get() = address



    constructor(name:String,age:Int,address:String):this(name,age){
        this.address = address
    }

    public var initNumber = 0

    init {
        initNumber = 100
        if(name == ""){
            initNumber = 99
        }
        println("假人已创建")
    }

    override fun call(): String {
        TODO("Not yet implemented")
    }

    override fun run() {
        TODO("Not yet implemented")
    }

}


/** package中的演示 **/
package PackageTest

fun unitsSupported() = listOf("Mertic","Imperial")

fun precision(): Int = throw java.lang.RuntimeException("Error!!!")

//单例
object Tempterature {
    fun c2f(c:Double) = c * 9.0/5 +32
    fun f2c(f:Double) = (f - 32) * 5.0/9
}

object Distance{
    fun milesToKm(miles:Double) = miles * 1.609
    fun kmToMiles(kms:Double) = kms / 1.609
}

//总结下来:如果一些函数是高级的,通用的和广泛使用的,那么就把它们放在顶级函数中。
//如果一些函数之间的关系比其他的函数更加密切,就把它们放在一个单例中
//如果一些函数存在依赖关系,可以把它们放在一个单例中
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐