Kotlin学习(二):类和接口

Kotlin学习(二):类和接口

类声明

Kotlin中,类的声明与Java一样,也使用class关键字。

class MyClass{
    
    
    ...
}

类声明由类名、类头(指定其类型参数、主构造函数等)以及由花括号包围的类体构成。类头与类体都是可选的; 如果一个类没有类体,可以省略花括号。

构造函数

在Kotlin中,一个类可以有一个主构造函数( primary constructor )及多个次构造函数( secondary constructor )。

主构造函数

主构造函数是类头的一部分,紧跟在类名的后面,参数是可选的。

 class Person constructor(name: String ) {
    
     //申明主构造函数
    ...
 }

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

class Person(name: String) {
    
     //声明主构造函数
   ... 
}

主构造函数不能包含任何的代码。初始化的代码可以放到以 init 关键字作为前缀的**初始化块(initializer blocks)**中。

class Person constructor(name: String) {
    
     //申明主构造函数
    init {
    
    
        println("name = [ $name ]")
    }
}

对比一下同样的java代码:

class Person{
    
    
	public Person(String name){
    
    
        System.out.println("name = [ "+name+" ]");
    }
}

要注意的是,主构造函数的参数不仅可以用在 init 块中,还可以用于对类属性的初始化

class Person constructor(name: String) {
    
     //申明主构造函数
    var mName: String = name // 初始化成员属性
    init {
    
    
        println("name = [ $name ]")
    }
}

var 和 val 关键字也可以用于主构造函数的参数,如果使用 var ,参数对于构造函数来说是变量,可以在构造函数内部修改变量的值,如果构造函数的参数使用 val 声明,参数就变成了常量,在构造函数内部不能修改该参数的值。要注意的是,即使使用 var 声明变 量,在构造函数内部修改参数变量值后,并不会把修改的值传到对象外部。

次构造函数

Kotlin 类除了可以声明一个主构造函数外,还可以声明若干个次构造函数。次构造函数需要在类中声明,前面必须加 constructor 关键字。并且如果类中声明了主构造函数,那么所有次构造函数都需要在声明后面调用主构造函数,或通过另外一个次构造函数间接的调用主构造函数

class Person constructor(var name: String) {
    
     //申明主构造函数
    var mName: String = name // 初始化成员属性
    var age: Int = 0
    var gender: Int = 0
    init {
    
    
        println("name = [ $name ]")
    }

    // 申明次构造函数(通过 this 直接调用了主构造函数)
    constructor(name: String, age: Int) : this(name) {
    
    
        this.age = age
    }

    // 申明次构造函数(通过 this 调用了次构造函数,间接的调用了主构造函数)
    constructor(name: String, age: Int, gender: Int) : this(name, age) {
    
    
        this.gender = gender
        println("name = [${
      
      name}], age = [${
      
      age}], gender = [${
      
      gender}]")
    }

注意:在主构造函数参数中可以使用 var 和 val ,但在次构造函数参数中不能使用 var 和 val 。这就意味着,次构造函数的参数都是只读的,不能在构造器内部改变参数的值。

Kotlin 中的 Singleton 模式

如果没有在类中定义主构造函数, Kotlin 编译器也会自动生成一个没有参数的主构造函数,这一点和 Java 完全一样,只是 Java 并不分主构造函数和次构造函数,在 Java 中都统称为构造函数。无论是自动参数的构造函数,还是按前面的方式定义的构造函数,外部都是可访问的,也就public 类型的,如果要满足特殊需求,如实现单例模式( singleton ),可以使用 private 来声明周构造函数和次构造函数。

class Common private constructor() {
    
    

    // kotlin 单例模式 伴生对象
    // 在Kotlin 中并没有静态类成员的概念,但并不等于不能实现类似于静态类成员的功能
    // 陪伴对象(Companion Objects)就是 Kotlin 用来解决这个问题的语法糖。
    // 如果在 Kotlin 类中定义对象,那么就称这个对象为该类的陪伴对象。伴生对象要使用
    // companion 关键字声明。
    companion object {
    
    
        fun getInstance() = Holder.instance
    }

    private object Holder {
    
    
        val instance = Common()
    }

创建类的实例

要创建一个类的实例,我们就像普通函数一样调用构造函数:

val invoice = Invoice()

val customer = Customer("Joe Smith")

注意 Kotlin 并没有 new 关键字。当然 ,在Kotlin中,还有一些特殊的类,如 Data 类、嵌套类。这些类的定义和使用方法,在这不做详细赘述。

类成员

Kotlin 中的类可以包含多种成员,除了前面所说的构造函数外,还有属性、函数、嵌套类与内部类等。

属性

声明属性

Kotlin 类中的属性既可以用关键字 var 声明为可变的,也可以用关键字 val 声明为只读的。

class Address {
    
    
    var name: String = "Holmes, Sherlock"
    var street: String = "Baker"
    var city: String = "London"
    var state: String? = null
    var zip: String = "123456"
}

要使用一个属性,只要用名称引用它即可:

fun copyAddress(address: Address): Address {
    
    
    val result = Address() // Kotlin 中没有“new”关键字
    result.name = address.name // 将调用访问器
    result.street = address.street
    // ……
    return result
}
Getter 与 Setter

声明一个属性的完整语法是

var <propertyName>[: <PropertyType>] [= <property_initializer>]
    [<getter>]
    [<setter>]

其初始器(initializer)、getter 和 setter 都是可选的。属性类型如果可以从初始器 (或者从其 getter 返回值,如下文所示)中推断出来,也可以省略。

例如:

var allByDefault: Int? // 错误:需要显式初始化器,隐含默认 getter 和 setter
var initialized = 1 // 类型 Int、默认 getter 和 setter

如果属性是只读的,需要将属性声明为 val,并只添加一个 getter 形式。如果属性是读写的,需要使用 var 声明属性,并添加 getter 和 setter 形式。如果 getter 和setter 中只有一行实现代码,直接用等号(=〉分隔 getter 和代码即可。如果包含多行代码,需要使用{...}处理。

class Customer {
    
    
    // 只读属性
    val name: String
        get() = "Bill"
    
    var v: Int = 20
    // 读写属性
    var value: Int
        get() = v
        set(value) {
    
    
            println("value 属性被设置")
            v = value
        }
    //如果可以从 getter 推断出属性类型,则可以省略它
    val isEmpty get() = v == 0  // 具有类型 Boolean
}
幕后字段

In Kotlin, a field is only used when needed as part of a property to hold its value in memory. Fields can not be declared directly.(在Kotlin中,字段仅在需要作为属性的一部分在内存中保存其值时使用。不能直接声明字段。)

在上面的例子中,value 属性使用了成员变量 v 来保存属性的值。 Kotlin 为我们提供了更便捷的方式解决这个问题,这就是 field 标识符。在属性的 getter 和 setter 中, 可以将 field 当做成员变量使用,也就是通过 field 读写属性值。

class Customer {
    
    
    // 只读属性
    val name: String
        get() = "Bill"
    var v: Int = 20

    //如果可以从 getter 推断出属性类型,则可以省略它
    val isEmpty get() = v == 0  // 具有类型 Boolean

    // 读写属性
    var value: Int = 0
        get() = field //从 field 中读取属性值
        set(value) {
    
    
            println("value 属性被设置")
            field = value // 将属性值写入 field 中
        }

}

fun main(args: Array<String>) {
    
    
    var c = Customer()
    c.value = 30
    println(c.value) 
}
// 输出内容:
// value 属性被设置
// 30
延迟初始化属性与变量

一般地,属性声明为非空类型必须在构造函数中初始化。 然而,这经常不方便。例如:属性可以通过依赖注入来初始化, 或者在单元测试的 setup 方法中初始化。 这种情况下,你不能在构造函数内提供一个非空初始器。 但你仍然想在类体中引用该属性时避免空检测。

为处理这种情况,你可以用 lateinit 修饰符标记该属性:

public class MyTest {
    lateinit var subject: TestSubject

    @SetUp fun setup() {
        subject = TestSubject()
    }

    @Test fun test() {
        subject.method()  // 直接解引用
    }
}

该修饰符只能用于在类体中的属性(不是在主构造函数中声明的 var 属性,并且仅当该属性没有自定义 getter 或 setter 时),而自 Kotlin 1.2 起,也用于顶层属性与局部变量。该属性或变量必须为非空类型,并且不能是原生类型。

在初始化前访问一个 lateinit 属性会抛出一个特定异常,该异常明确标识该属性被访问及它没有初始化的事实。

嵌套类

类可以嵌套在其他类中:

class Outer {
    
    
    private val bar: Int = 1
    class Nested {
    
    
        fun foo() = 2
    }
}

val demo = Outer.Nested().foo() // == 2

您还可以使用嵌套接口。类和接口的所有组合都是可能的:可以在类中嵌套接口,在接口中嵌套类,在接口中嵌套接口。

interface OuterInterface {
    
    
    class InnerClass
    interface InnerInterface
}

class OuterClass {
    
    
    class InnerClass
    interface InnerInterface
}

标记为 inner 的嵌套类能够访问其外部类的成员。内部类会带有一个对外部类的对象的引用:

class Outer {
    
    
    private val bar: Int = 1
    inner class Inner {
    
    
        fun foo() = bar
    }
}

val demo = Outer().Inner().foo() // == 1

修饰符

类、对象、接口、构造函数、方法、属性和它们的 setter 都可以有 可见性修饰符。 (getter 总是与属性有着相同的可见性。) 在 Kotlin 中有这四个可见性修饰符:privateprotectedinternalpublic。 如果没有显式指定修饰符的话,默认可见性是 public 。这四个修饰符的作用如下:

  • private :意味着只在这个类内部(包含其所有成员)可见。
  • protected:和 private 类似,但 在子类中也可以访问。
  • internal: 任何在模块内部类都可以访问。
  • public: 任何类都可以访问
open class Outer {
    
    
    private val a = 1
    protected open val b = 2
    internal val c = 3
    val d = 4  // 默认 public
    
    protected class Nested {
    
    
        public val e: Int = 5
    }
}

class Subclass : Outer() {
    
    
    // a 不可见
    // b、c、d 可见
    // Nested 和 e 可见

    override val b = 5   // “b”为 protected
}

class Unrelated(o: Outer) {
    
    
    // o.a、o.b 不可见
    // o.c 和 o.d 可见(相同模块)
    // Outer.Nested 不可见,Nested::e 也不可见
}

类的继承

Kotlin 类的继承需要使用冒号( : )," : " 后面需要调用父类的构造函数。Kotlin 和 Java 一样,都是单继承的,也就是说一个类只能由一个父类。Kotlin 类默认是 final 的,默认时类不能继承,需要显示地使用 open 关键字允许继承。

open class Person {
    
     // 需要使用 open 声明 Person 类,才允许其他类继承 Person
    protected var mName: String = "Bill"
    fun getName(): String {
    
    
        return mName
    }
}
class Student : Person(){
    
     // Student 继承了Person类,现在属性mName和方法getName都可以访问
    fun printName(){
    
    
        println(getName())
    }
}

重写方法和属性

Kotlin 中,不仅类默认是不可继承的,方法也是如此。因此如果要在子类中重写方法,就需要在父类相应方法前加 open 关键字,而且要在子类重写的方法前加 override 关键字。

open class Person {
    
     // 需要使用 open 声明 Person 类,才允许其他类继承 Person
    protected var mName: String = "Bill"
    // 只有加 open 关键字,才能被子类重写
    open fun getName(): String {
    
    
        return mName
    }
}
class Student : Person(){
    
     // Student 继承了Person类,现在属性mName和方法getName都可以访问
    fun printName(){
    
    
        println(getName())
    }
    // 重写父类的 getName 方法
    override fun getmName(): String {
    
    
        return "< ${
      
      super.getName()} >"
    }
}

属性的重写方式与方法类似,被重写的属性必须使用 open 声明,子类中重写的属性必须用 override 声明。不过要注意的是, val 属性可以被重写为 var 属性,但反过来不可以。

open class Person {
    
     // 需要使用 open 声明 Person 类,才允许其他类继承 Person
   protected open val mName: String = "Bill" // 初始化成员属性
       get() = field
   // 只有加 open 关键字,才能被子类重写
   open fun getName(): String {
    
    
       return mName
   }
}
class Student : Person(){
    
     // Student 继承了Person类,现在属性mName和方法getName都可以访问
   override var mName: String = "Mike"
       get() = field
       set(value) {
    
    
           field = value
       }
   fun printName(){
    
    
       println(getName())
   }
   // 重写父类的 getName 方法
   override fun getmName(): String {
    
    
       return "< ${
      
      super.getName()} >"
   }
}

接口

Kotlin 中的接口与 Java 中的接口类似,使用 interface 关键字声明。一个类可以实现多个接口 ,实现的方法和类继承相同。而且,接口中的属性和方法都是 open 的。

interface MyInterface {
    
     // 定义 MyInterface 接口
    fun process()
    fun getName(): String {
    
    
        // 可选的方法体
        return "Bill"
    }
}
class Child : MyInterface {
    
    
    // 接口里面没有具体实现,必须重写
    override fun process() {
    
    
        println("process")
    }

    // 接口里面有具体实现,可选重写
    override fun getName(): String {
    
    
        return "Mike"
    }
}

抽象类

类以及其中的某些成员可以声明为 abstract。 抽象成员在本类中可以不用实现。 需要注意的是,我们并不需要用 open 标注一个抽象类或者函数——因为这不言而喻。

我们可以用一个抽象成员覆盖一个非抽象的开放成员

open class Polygon {
    
    
    open fun draw() {
    
    }
}

abstract class Rectangle : Polygon() {
    
    
    abstract override fun draw()
}

猜你喜欢

转载自blog.csdn.net/u010812857/article/details/120990042