Kotlin基础语法-属性(Property)与域(Field)

属性(Property)与域(Field)

声明属性

Kotlin 中的类可以拥有属性. 可以使用 var 关键字声明为可变(mutable)属性, 也可以使用 val 关键字声明为只读属性.

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

使用属性时, 只需要简单地通过属性名来参照它, 和使用 Java 中的域变量(field)一样:

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)都是可选的. 如果属性类型可以通过初始化器自动推断得到, 或者可以通过这个属性覆盖的基类成员属性推断得到, 则属性类型的声明也可以省略.

示例:

var allByDefault: Int? // 错误: 需要明确指定初始化器, 此处会隐含地使用默认的取值方法和设值方法
var initialized = 1 // 属性类型为 Int, 使用默认的取值方法和设值方法

只读属性声明的完整语法与可变属性有两点不同: 由 val 开头, 而不是 var, 并且不允许指定设值方法:

val simple: Int? // 属性类型为 Int, 使用默认的取值方法, 属性值必须在构造器中初始化
val inferredType = 1 // 属性类型为 Int, 使用默认的取值方法

我们可以编写自定义的访问方法, 与普通的函数很类似, 访问方法的位置就在属性定义体之内. 下面是一个自定义取值方法的示例:

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

自定义设值方法的示例如下:

var stringRepresentation: String
  get() = this.toString()
  set(value) {
    setDataFromString(value) // 解析字符串内容, 并将解析得到的值赋给对应的其他属性
  }

Kotlin 的编程惯例是, 设值方法的参数名称为 value, 但如果你喜欢, 也可以选择使用不同的名称.

如果你需要改变属性访问方法的可见度, 或者需要对其添加注解, 但又不需要修改它的默认实现, 你可以定义这个方法, 但不定义它的实现体:

var setterVisibility: String = "abc"
  private set // 设值方法的可见度为 private, 并使用默认实现

var setterWithAnnotation: Any? = null
  @Inject set // 对设值方法添加 Inject 注解

属性的后端域变量(Backing Field)

Kotlin 的类不能拥有域变量. 但是, 使用属性的自定义访问器时, 有时会需要后端域变量(backing field). 为了这种目的, Kotlin 提供了一种自动的后端域变量, 可以通过 field 标识符来访问:

var counter = 0 // 初始化给定的值将直接写入后端域变量中
  set(value) {
    if (value >= 0)
      field = value
  }

field 标识符只允许在属性的访问器函数内使用.

如果属性 get/set 方法中的任何一个使用了默认实现, 或者在 get/set 方法的自定义实现中通过 field 标识符访问属性, 那么编译器就会为属性自动生成后端域变量.

比如, 下面的情况不会存在后端域变量:

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

后端属性(Backing Property)

如果你希望实现的功能无法通过这种 “隐含的后端域变量” 方案来解决, 你可以使用 后端属性(backing property) 作为替代方案:

private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
  get() {
    if (_table == null)
      _table = HashMap() // 类型参数可以自动推断得到, 不必指定
    return _table ?: throw AssertionError("Set to null by another thread")
  }

不管从哪方面看, 这种方案都与 Java 中完全相同, 因为后端私有属性的取值方法与设值方法都使用默认实现, 我们对这个属性的访问将被编译器优化, 变为直接读写后端域变量, 因此不会发生不必要的函数调用, 导致性能损失.

编译期常数值

如果属性值在编译期间就能确定, 则可以使用 const 修饰符, 将属性标记为_编译期常数值(compile time constants)_. 这类属性必须满足以下所有条件:

  • 必须是顶级属性, 或者是一个 object 的成员
  • 值被初始化为 String 类型, 或基本类型(primitive type)
  • 不存在自定义的取值方法

这类属性可以用在注解内:

const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"

@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }

延迟初始化属性(Late-Initialized Property)

通常, 如果属性声明为非 null 数据类型, 那么属性值必须在构造器内初始化. 但是, 这种限制很多时候会带来一些不便. 比如, 属性值可以通过依赖注入来进行初始化, 或者在单元测试代码的 setup 方法中初始化. 这种情况下, 你就无法在构造器中为属性编写一段非 null 值的初始化代码, 但你仍然希望在类内参照这个属性时能够避免 null 值检查.

要解决这个问题, 你可以为属性添加一个 lateinit 修饰符:

public class MyTest {
    lateinit var subject: TestSubject

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

    @Test fun test() {
        subject.method()  // 直接访问属性
    }
}

这个修饰符只能用于 var 属性, 而且只能是声明在类主体部分之内的属性(不可以是主构造器中声明的属性), 而且属性不能有自定义的取值方法和设值方法. 属性类型必须是非 null 的, 而且不能是基本类型.

在一个 lateinit 属性被初始化之前访问它, 会抛出一个特别的异常, 这个异常将会指明被访问的属性, 以及它没有被初始化这一错误.

属性的覆盖

参见 成员的覆盖

委托属性(Delegated Property)

最常见的属性只是简单地读取(也有可能会写入)一个后端域变量. 但是, 通过使用自定义的取值方法和设值方法, 我们可以实现属性任意复杂的行为. 在这两种极端情况之间, 还存在一些非常常见的属性工作模式. 比如: 属性值的延迟加载, 通过指定的键值(key)从 map 中读取数据, 访问数据库, 属性被访问时通知监听器, 等等.

这些常见行为可以使用委托属性(delegated property), 以库的形式实现.

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据提供的引用内容,出现了两个关于Kotlin的错误信息。第一个引用中显示了一个无法解析依赖的错误,指出无法下载kotlin-reflect.jar文件。第二个引用中显示了一个关于kotlin-gradle-1.8.10.jar (org.jetbrains.kotlin:kotlin-reflect)",这个错误通常是由于Gradle无法找到所需的kotlin-reflect库而引起的。解决这个问题的方法是确保你的项目的Gradle配置正确,并且指定了正确的Kotlin版本。 你可以尝试以下几个步骤来解决这个问题: 1. 确保你的项目的build.gradle文件中包含了正确的Kotlin版本和kotlin-gradle-plugin版本。你可以在build.gradle文件中找到类似于以下代码的部分: ```groovy ext { kotlin_version = '1.8.10' } dependencies { // ... implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" // ... } buildscript { // ... dependencies { // ... classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // ... } } ``` 请确保kotlin_version变量的值与你想要使用的Kotlin版本一致,并且在dependencies和buildscript的classpath中正确引用了kotlin-gradle-plugin。 2. 如果你已经确认了build.gradle文件中的配置正确无误,那么可能是因为Gradle无法从远程仓库下载kotlin-reflect.jar文件。你可以尝试清除Gradle的缓存并重新构建项目。在命令行中执行以下命令: ```shell ./gradlew clean ``` 然后重新构建项目: ```shell ./gradlew build ``` 这将清除Gradle的缓存并重新下载所需的依赖。 3. 如果上述步骤***切换到其他网络环境来解决这个问题。 希望以上步骤能够帮助你解决问题。如果你还有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值