Классы и объекты Kotlin

Определение класса

Классы Kotlin могут содержать: конструкторы и блоки инициализации, функции, свойства, внутренние классы и объявления объектов.

В Kotlin ключевое словоclassобъявляет класс, сразу за которым следует имя класса:

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

Мы также можем определить пустой класс:

class Empty

Функции-члены можно определять в классе:

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

Свойства класса

Определение свойства

Свойства класса могут использовать ключевое словоvar, чтобы объявить их изменяемыми; в противном случае используйте ключевое слово только для чтенияval, чтобы объявить их неизменяемыми.

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

Мы можем создавать экземпляры классов с помощью конструкторов так же, как обычные функции:

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

Чтобы использовать свойство, просто обратитесь к нему по имени

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

Класс в Kotlin может иметь первичный конструктор и один или несколько вторичных конструкторов. Первичный конструктор является частью заголовка класса и располагается после имени класса:

class Person constructor(firstName: String) {}

Если у первичного конструктора нет аннотаций или модификаторов видимости, ключевое слово constructor можно опустить.

class Person(firstName: String) {
}

геттеры и сеттеры

Полный синтаксис объявления свойства:

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

И геттер, и сеттер необязательны

Если тип свойства можно вывести из инициализатора или из функций-членов класса, тип можно опустить. val не допускает функцию-сеттер, поскольку оно доступно только для чтения.

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

Пример

В следующем примере определяется класс Person с двумя изменяемыми переменными, lastName и no. lastName переопределяет метод-геттер, а no переопределяет метод-сеттер.

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}")

}

Вывод:

lastName:WANG
no:9
no:-1

Классы в Kotlin не могут иметь полей. Однако иногда при использовании пользовательских аксессоров необходимо резервное поле. Для этой цели Kotlin предоставляет автоматическое резервное поле, к которому можно получить доступ с помощью идентификатора field.

Резервное поле — это концепция в Kotlin. Это автоматически сгенерированное поле, используемое для хранения значения свойства. Его можно использовать только внутри аксессоров (геттера или сеттера), и оно существует только в том случае, если хотя бы один аксессор использует реализацию по умолчанию или если пользовательский аксессор ссылается на него через идентификатор field. Это означает, что когда свойству требуется резервное поле, Kotlin автоматически предоставляет его. Вы можете использовать идентификатор field, чтобы ссылаться на резервное поле внутри аксессора.

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

Конечно, вот пример резервного поля:

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

В этом примере свойство name имеет пользовательский сеттер, который проверяет, пусто ли входящее значение. Если оно не пусто, он использует идентификатор field, чтобы присвоить значение резервному полю. Таким образом, когда мы пытаемся присвоить пустую строку свойству name, его значение не изменяется.

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

В приведённом выше коде мы создали экземпляр класса Person и попытались изменить значение его свойства name. Когда мы попытались присвоить ему пустую строку, из-за проверки в пользовательском сеттере значение свойства name осталось неизменным. Когда мы присвоили ему непустую строку, его значение было успешно изменено на новое.

Свойства, не допускающие значение null, должны быть инициализированы во время определения. Kotlin предоставляет способ отложить инициализацию, используя ключевое словоlateinitдля аннотирования свойства:

public class MyTest {
    lateinit var subject: TestSubject

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

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

Первичный конструктор

Первичный конструктор не может содержать код. Код инициализации можно разместить в блоке инициализации, которому предшествует ключевое слово init.

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

Примечание: параметры первичного конструктора можно использовать в блоках инициализации, а также в инициализаторах свойств, определённых в теле класса. Краткий синтаксис позволяет определять свойства и инициализировать их значения через первичный конструктор (может быть var или val):

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

Если у конструктора есть аннотации или модификаторы видимости, требуется ключевое слово constructor, а аннотации и модификаторы должны быть размещены перед ним.

Пример

Создайте класс Example и передайте имя веб-сайта через конструктор:

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()
}

Вывод:

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

Вторичные конструкторы

Класс также может иметь вторичные конструкторы, которым нужен префикс constructor:

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

Если у класса есть первичный конструктор, каждый вторичный конструктор должен делегировать первичному конструктору — напрямую или косвенно через другой вторичный конструктор. Используйте ключевое слово this для делегирования другому конструктору того же класса:

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

Если неабстрактный класс не объявляет ни одного конструктора (первичного или вторичного), будет создан конструктор без параметров. Этот конструктор является public. Если вы не хотите, чтобы у вашего класса был public-конструктор, нужно объявить пустой первичный конструктор:

class DontCreateMe private constructor () {
}

Примечание: на JVM, если все параметры первичного конструктора имеют значения по умолчанию, компилятор создаст дополнительный конструктор без аргументов, который напрямую использует значения по умолчанию. Это упрощает работу Kotlin с такими библиотеками, как Jackson или JPA, которые создают экземпляры классов с помощью конструкторов без аргументов.

class Customer(val customerName: String = "")

Пример

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()
}

Вывод:

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

Абстрактные классы

Абстракция — одна из особенностей объектно-ориентированного программирования. Сам класс или некоторые члены класса могут быть объявлены абстрактными. Абстрактные члены не имеют конкретных реализаций в классе.

Примечание: нет необходимости аннотировать абстрактные классы или абстрактные члены аннотацией open.

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

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

Вложенные классы

Мы можем вкладывать классы в другие классы. См. следующий пример:

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.

Внутренний класс хранит ссылку на объект внешнего класса, поэтому внутренний класс может получать доступ к свойствам-членам и функциям-членам внешнего класса.

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)   // 内部类可以引用外部类的成员,例如:成员属性
}

Чтобы устранить неоднозначность, при доступе к this из внешней области мы используем this@label, где @label — это метка, указывающая на источник this.


Анонимные внутренние классы

Используйте объектные выражения для создания анонимных внутренних классов:

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("对象表达式创建匿名内部类的实例")
        }
    })
}

Модификаторы класса

Модификаторы класса включают classModifier и _accessModifier_:

  • classModifier: модификатор свойств класса, указывающий на собственные характеристики класса.

    abstract    // 抽象类  
    final       // 类不可继承,默认属性
    enum        // 枚举类
    open        // 类可继承,类默认是final的
    annotation  // 注解类
    
  • accessModifier: модификатор прав доступа

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

Пример

// 文件名:example.kt
package foo

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

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

internal val baz = 6    // 相同模块内可见
Другие расширения