Kotlin Classes and Objects

Class Definition

Kotlin classes can contain: constructors and initialization blocks, functions, properties, inner classes, and object declarations.

In Kotlin, the keywordclassdeclares a class, immediately followed by the class name:

class Example {  // 类名为 Example
    // 大括号内是类体构成
}

We can also define an empty class:

class Empty

Member functions can be defined in the class:

class Example() {
    fun foo() { print("Foo") } // 成员函数
}

Class Properties

Property Definition

Class properties can use the keywordvarto declare mutable; otherwise, use the read-only keywordvalto declare immutable.

class Example {
    var name: String = ……
    var url: String = ……
    var city: String = ……
}

We can create class instances using constructors just like ordinary functions:

val site = Example() // Kotlin 中没有 new 关键字

To use a property, simply refer to it by name

site.name           // 使用 . 号来引用
site.url

A class in Kotlin can have a primary constructor and one or more secondary constructors. The primary constructor is part of the class header and is located after the class name:

class Person constructor(firstName: String) {}

If the primary constructor has no annotations or visibility modifiers, the constructor keyword can be omitted.

class Person(firstName: String) {
}

getters and setters

The full syntax for declaring a property is:

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

Both getter and setter are optional

If the property type can be inferred from the initializer or from the class's member functions, the type can be omitted. val does not allow a setter function because it is read-only.

var allByDefault: Int? // 错误: 需要一个初始化语句, 默认实现了 getter 和 setter 方法
var initialized = 1    // 类型为 Int, 默认实现了 getter 和 setter
val simple: Int?       // 类型为 Int ,默认实现 getter ,但必须在构造函数中初始化
val inferredType = 1   // 类型为 Int 类型,默认实现 getter

Example

The following example defines a Person class with two mutable variables, lastName and no. lastName overrides the getter method, and no overrides the setter method.

class Person {

    var lastName: String = "zhang"
        get() = field.toUpperCase()   // 将变量赋值后转换为大写
        set

    var no: Int = 100
        get() = field                // 后端变量
        set(value) {
            if (value < 10) {       // 如果传入的值小于 10 返回该值
                field = value
            } else {
                field = -1         // 如果传入的值大于等于 10 返回 -1
            }
        }

    var heiht: Float = 145.4f
        private set
}

// 测试
fun main(args: Array<String>) {
    var person: Person = Person()

    person.lastName = "wang"

    println("lastName:${person.lastName}")

    person.no = 9
    println("no:${person.no}")

    person.no = 20
    println("no:${person.no}")

}

The output is:

lastName:WANG
no:9
no:-1

Classes in Kotlin cannot have fields. However, sometimes a backing field is necessary when using custom accessors. For this purpose, Kotlin provides an automatic backing field, which can be accessed using the field identifier.

A backing field is a concept in Kotlin. It is an automatically generated field used to store a property's value. It can only be used inside accessors (getter or setter), and it exists only if at least one accessor uses the default implementation, or if a custom accessor references it through the field identifier. This means that when a property needs a backing field, Kotlin automatically provides it. You can use the field identifier to reference the backing field inside an accessor.

var no: Int = 100
        get() = field                // 后端变量
        set(value) {
            if (value < 10) {       // 如果传入的值小于 10 返回该值
                field = value
            } else {
                field = -1         // 如果传入的值大于等于 10 返回 -1
            }
        }

Of course, here is an example of a backing field:

class Person {
    var name: String = "initial value"
        set(value) {
            if (value.isNotEmpty()) {
                field = value
            }
        }
}

In this example, the name property has a custom setter that checks whether the incoming value is empty. If it is not empty, it uses the field identifier to assign the value to the backing field. Thus, when we try to assign an empty string to the name property, its value does not change.

val person = Person()
println(person.name) // 输出 "initial value"
person.name = ""
println(person.name) // 仍然输出 "initial value"
person.name = "John"
println(person.name) // 输出 "John"

In the code above, we created an instance of the Person class and tried to change the value of its name property. When we tried to assign an empty string to it, due to the custom setter's check, the value of the name property remained unchanged. When we assigned a non-empty string to it, its value was successfully changed to the new value.

Non-null properties must be initialized at the time of definition. Kotlin provides a way to delay initialization, using thelateinitkeyword to annotate the property:

public class MyTest {
    lateinit var subject: TestSubject

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

    @Test fun test() {
        subject.method()  // dereference directly
    }
}

Primary Constructor

The primary constructor cannot contain any code. Initialization code can be placed in an initializer block, which is prefixed with the init keyword.

class Person constructor(firstName: String) {
    init {
        println("FirstName is $firstName")
    }
}

Note: Parameters of the primary constructor can be used in initializer blocks, and also in property initializers defined in the class body. A concise syntax allows you to define properties and initialize their values through the primary constructor (can be var or val):

class People(val firstName: String, val lastName: String) {
    //...
}

If the constructor has annotations or visibility modifiers, the constructor keyword is required, and the annotations and modifiers must be placed before it.

Example

Create a Example class and pass the website name through the constructor:

class Example  constructor(name: String) {  // 类名为 Example
    // 大括号内是类体构成
    var url: String = "http://www.example.com"
    var country: String = "CN"
    var siteName = name

    init {
        println("初始化网站名: ${name}")
    }

    fun printTest() {
        println("我是类的函数")
    }
}

fun main(args: Array<String>) {
    val example =  Example("Example")
    println(example.siteName)
    println(example.url)
    println(example.country)
    example.printTest()
}

The output is:

初始化网站名: Example
Example
http://www.example.com
CN
我是类的函数

Secondary Constructors

A class can also have secondary constructors, which need the prefix constructor:

class Person { 
    constructor(parent: Person) {
        parent.children.add(this) 
    }
}

If the class has a primary constructor, each secondary constructor must delegate to the primary constructor, either directly or indirectly through another secondary constructor. Use the this keyword to delegate to another constructor of the same class:

class Person(val name: String) {
    constructor (name: String, age:Int) : this(name) {
        // 初始化...
    }
}

If a non-abstract class does not declare any constructors (primary or secondary), it will generate a constructor with no parameters. The constructor is public. If you do not want your class to have a public constructor, you need to declare an empty primary constructor:

class DontCreateMe private constructor () {
}

Note: On the JVM, if all parameters of the primary constructor have default values, the compiler will generate an additional no-argument constructor, which will directly use the default values. This makes it easier for Kotlin to work with libraries like Jackson or JPA, which create class instances using no-argument constructors.

class Customer(val customerName: String = "")

Example

class Example  constructor(name: String) {  // 类名为 Example
    // 大括号内是类体构成
    var url: String = "http://www.example.com"
    var country: String = "CN"
    var siteName = name

    init {
        println("初始化网站名: ${name}")
    }
    // 次构造函数
    constructor (name: String, alexa: Int) : this(name) {
        println("Alexa 排名 $alexa")
    }

    fun printTest() {
        println("我是类的函数")
    }
}

fun main(args: Array<String>) {
    val example =  Example("Example", 10000)
    println(example.siteName)
    println(example.url)
    println(example.country)
    example.printTest()
}

The output is:

初始化网站名: Example
Alexa 排名 10000
Example
http://www.example.com
CN
我是类的函数

Abstract Classes

Abstraction is one of the features of object-oriented programming. A class itself, or some members of a class, can be declared abstract. Abstract members do not have concrete implementations in the class.

Note: There is no need to annotate abstract classes or abstract members with the open annotation.

open class Base {
    open fun f() {}
}

abstract class Derived : Base() {
    override abstract fun f()
}

Nested Classes

We can nest classes inside other classes. See the following example:

class Outer {                  // 外部类
    private val bar: Int = 1
    class Nested {             // 嵌套类
        fun foo() = 2
    }
}

fun main(args: Array<String>) {
    val demo = Outer.Nested().foo() // 调用格式:外部类.嵌套类.嵌套类方法/属性
    println(demo)    // == 2
}

Inner Classes

Inner classes are marked with the inner keyword.

An inner class carries a reference to an object of the outer class, so the inner class can access the member properties and member functions of the outer class.

class Outer {
    private val bar: Int = 1
    var v = "成员属性"
    /**嵌套内部类**/
    inner class Inner {
        fun foo() = bar  // 访问外部类成员
        fun innerTest() {
            var o = this@Outer //获取外部类的成员变量
            println("内部类可以引用外部类的成员,例如:" + o.v)
        }
    }
}

fun main(args: Array<String>) {
    val demo = Outer().Inner().foo()
    println(demo) //   1
    val demo2 = Outer().Inner().innerTest()   
    println(demo2)   // 内部类可以引用外部类的成员,例如:成员属性
}

To eliminate ambiguity, when accessing this from the outer scope, we use this@label, where @label is a label referring to the source of this.


Anonymous Inner Classes

Use object expressions to create anonymous inner classes:

class Test {
    var v = "成员属性"

    fun setInterFace(test: TestInterFace) {
        test.test()
    }
}

/**
 * 定义接口
 */
interface TestInterFace {
    fun test()
}

fun main(args: Array<String>) {
    var test = Test()

    /**
     * 采用对象表达式来创建接口对象,即匿名内部类的实例。
     */
    test.setInterFace(object : TestInterFace {
        override fun test() {
            println("对象表达式创建匿名内部类的实例")
        }
    })
}

Class Modifiers

Class modifiers include classModifier and _accessModifier_:

  • classModifier: class property modifier, indicating the class's own characteristics.

    abstract    // 抽象类  
    final       // 类不可继承,默认属性
    enum        // 枚举类
    open        // 类可继承,类默认是final的
    annotation  // 注解类
    
  • accessModifier: access permission modifier

    private    // 仅在同一个文件中可见
    protected  // 同一个文件中或子类可见
    public     // 所有调用的地方都可见
    internal   // 同一个模块中可见
    

Example

// 文件名:example.kt
package foo

private fun foo() {} // 在 example.kt 内可见

public var bar: Int = 5 // 该属性随处可见

internal val baz = 6    // 相同模块内可见
Other Extensions