Kotlin类和对象(二)——属性和字段

声明属性

Kotlin中的类可以有属性,它们可以被声明成可变的var也可以被声明成只读val的。

class Address { 
    var name: String = ... 
    var street: String = ... 
    var city: String = ... 
    var state: String? = ... 
    var zip: String = ...
}

要使用属性,我们只需按名称引用它,就好像它是Java中的一个字段。

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>] //方括号内是可选的

初始值、setter、getter都是可选的。如果属性类型能通过初始值或getter返回类型推断,那么也是可选的。像下面这样:

var allByDefault: Int? // 错误: 要求显示初始值, 隐藏setter getter
var initialized = 1 //Int类型是被推算出来,默认有getter setter方法

只读属性声明的完整句法与可变属性的声明存在两点不同:val开头替代了var而且不允许有setter方法。

val simple: Int? //Int类型,默认有getter方法,必须在构造函数中初始化
val inferredType = 1 //Int类型,默认有getter方法

我们可以编写自定义的访问器,就像写普通函数一样,在声明属性的右边。下面举个例子:自定义getter方法:

val isEmpty: Boolean 
    get() = this.size == 0

自定义setter方法:

var stringRepresentation: String
    get() = this.toString() 
    set(value) { 
        setDataFromString(value) 
    }

为了方便,setter参数名称默认使用value,只要你喜欢,也可以使用不同的名称。

Kotlin 版本1.1 起,如果属性类型可以从getter推断出,那么你可以省略属性类型。

扫描二维码关注公众号,回复: 2171253 查看本文章
val isEmpty get() = this.size == 0//Boolean 类型

如果你需要改变一个访问器的权限或添加注解,不需要改变他默认的实现。定义访问器即可,不需要定义方法体。

var setterVisibility: String = "abc" 
    private set // setter 是 private 而且有默认的实现

var setterWithAnnotation: Any? = null 
    @Inject set // setter添加Inject注解

幕后字段

Kotlin的类中不能有字段。然而当使用自定义访问器时,有时候拥有个幕后字段是很有必要的。
为此,Kotlin提供一个自动幕后字段,它可以通过标示符field访问。

var counter = 0 // 初始值直接写给幕后字段
    set(value) {
        if (value >= 0) field = value
    }

标识符field只能用在属性的访问器中。

如果一个属性至少有一个默认实现的访问器或者使用标识符field自定义访问器时,才会生成一个幕后字段。

举个例子,像下述情况就没有幕后字段。

// 只有一个访问器且没有在自定义访问器中使用field。
val isEmpty: Boolean
    get() = this.size == 0

译者注:好多人分不清属性和字段的区别。在Java和其他大多数面向对象编程语言中,字段指类中申明的变量,属性指类中对变量的读写行为(setter和getter)。而Kotlin中已经将两者合成在一起,因此若单独定义字段,将会发生冲突。

幕后属性

如果你想做的事情不符合这个“隐式的幕后字段”设计,你总可以后退一步使用幕后属性。

private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
    get() {
        if (_table == null) {
            _table = HashMap() // Type parameters are inferred
        }
        return _table ?: throw AssertionError("Set to null by another thread")
    }

从各方面看,这恰好和Java一样,由于访问私有属性的默认setter和getter被优化过,所以没有引入函数调用开销。

编译时常量

在编译时已知值的属性可以用const修饰符标记为编译常量。

这些属性需要满足以下要求:
- 在顶层或者object的成员。
- 用String或者别的原生类型初始化。
- 没有自定义的getter。

这些属性可以用在注解中:

const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }

延迟初始化属性

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

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

public class MyTest {
    lateinit var subject: TestSubject
    @SetUp fun setup() {
        subject = TestSubject()
    } @
    Test fun test() {
        subject.method() // 直接引用
    }
}

修饰符只能被用在类体中(不是在主构造函数中)声明的变量属性,而且属性没有自定义的setter和getter方法。属性的类型必须是非空,而且不能使原始类型。

访问没有被初始化的lateinit属性会抛出异常,该异常会详细默描述属性被访问而且事实上没有被初始化。

重写属性

参见重写属性。

委托属性

多数常见的属性都是简单的从幕后属性读取(也有可能写入)值。另一方面,使用自定义的setter和getter可以实现属性的任一行为。介于两者之间必然存在属性如何工作的常见模式。一些例子:懒值、通过key从map获取、访问数据库、访问时通知监听器,等等。

这些常见的行为可以使用委托属性实现为库。

猜你喜欢

转载自blog.csdn.net/flueky/article/details/78303192