以下内容皆有feint进行翻译,不定期进行更新。翻译若有不正确的地方,欢迎在评论中指出。
属性和字段
声明属性
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
}
Getters and Setters
声明一个属性的完整语法是:
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
初始化器, getter 和 setter 都是可选的. 如果可以从初始化器推断的话,那么属性的类型也是可选的 (或是像下面一样,从getter的返回类型中获取).
例如:
var allByDefault: Int? // 错误:需要显示的初始化器,getter 和 setter 默认是隐式的。
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 推断出来的话,则可以选择将其隐藏:
val isEmpty get() = this.size == 0// 为 Boolean 类型
如果你想改变一个属性的可见性,或是为它添加注解, 但是不需要改变默认的实现, 你可以定义访问器时不去定义它的主体部分:
var setterVisibility: String = "abc"
private set //setter 是私有的并且拥有默认的实现
var setterWithAnnotation: Any? = null
@Inject set // 使用 Inject setter 标注
辅助字段(backing fields)
Kotlin中的类不能拥有字段。 然而,有时候当我们使用一个属性访问器时必须有一个辅助字段。因为这些原因, Kotlin提供了一个自动生成的辅助字段,并可以使用 field 标识进行访问:
var counter = 0 // 初始化的值被直接写入到 field 中
set(value) {
if (value >= 0) field = value
}
field 标识仅仅能在属性访问器中使用。
如有有一个访问器使用了默认的实现,属性就会生成一个辅助字段,或是有一个自定义的访问器通过 field 标识引用它。
例如,下面这种情况就没有辅助字段:
val isEmpty: Boolean
get() = this.size == 0
辅助属性
如果你想做的事不符合这条 "隐式辅助字段" 的规则,你可以退一步使用 辅助字段:
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中是一样的,因为通过默认的getter 和 setter 访问私有属性的过程会被优化,而不产生函数调用的开销。
编译时常量
当属性的值是在编译时确定的可以被标记为 编译时常量,这种属性使用 const 进行修饰。这种属性需要完全满足一下几种要求:
位于顶层或是一个对象的成员
使用String 或者原声类型的值进行初始化
没有自定义 getter
属性也可在注解中进行使用:
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }
延迟初始化属性
一般来说, 属性声明后必须在构造函数中初始化一个非空类型。 然而,公平的说这样子往往是很不方便的。 举个例子, 属性可以通过独立的注入进行初始化, 或是通过 unit test 中的 setup 方法。在这些情况下, 你无法在构造函数中提供非空构造器, 但是你仍然想引用类内部的属性和防止空值检验。
为了解决这种情况,你可以使用 lateinit 修饰符标记那个属性:
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method()// 直接使用
}
}
这个修饰符只能用在类体内部使用 var 声明的属性 (不在主构造器中), 并且不能有自定义的 getter 和 setter。 属性的类型必须是非空的,它还不能是原生类型。
在被初始化之前访问一个 lateinit 属性会抛出一个特定的以异常:" clearly identifies the property being accessed and the fact that it hasn't been initialized"(属性在未被初始化的情形下被访问)。
属性继承
参照 属性重载
属性委托
绝大多数公有类型的属性都是简单的从一个辅助字段中读取(或写入。另一方面,通过自定义 getters and setters 可以实现属性的任意行为。介于两者之间的,属性可能有一些公共的工作模式。例如: lazy values、通过一个给定的 key 从 map 中读取、访问一个数据库、访问时通知监听器等等。
这些公有的行为可以通过使用 属性委托 实现成库
接口
Kotlin中的接口和Java8非常相似。他们包含抽象方法的声明, 以及方法的实现。和抽象类不同之处在于接口不能保存状态。它们可以拥有属性,但必须是抽象的或是提供访问器的实现。
使用interface关键字来定义一个接口:
interface MyInterface {
fun bar()
fun foo() {
// optional body
}
}
接口实现
一个类或对象可以实现一个或多个接口。
class Child : MyInterface {
override fun bar() {
// body
}
}
接口中的属性
你可以在接口中声明属性。 接口中声明的属性可以是抽象的, 或是提供访问器的实现。 接口中声明的属性没有辅助(backing)字段, 所以不能在接口中声明的访问器里引用它们。
interface MyInterface {
val prop: Int // 抽象
val propertyWithImplementation: String
get() = "foo"
fun foo() {
print(prop)
}
}
class Child : MyInterface {
override val prop: Int = 29
}
解决重载冲突
当我们在父类型列表中声明了很多类型时, 可能会发生继承的同一种方法却拥有不只一种实现的状况。 例如:
interface A {
fun foo() { print("A") }
fun bar()
}
interface B {
fun foo() { print("B") }
fun bar() { print("bar") }
}
class C : A {
override fun bar() { print("bar") }
}
class D : A, B {
override fun foo() {
super<A>.foo()
super<B>.foo()
}
override fun bar() {
super<B>.bar()
}
}
接口 A 和 B 都声明了函数 foo() 和 bar()。 它们都实现了 foo(),但仅仅只有 B 实现了 bar() (bar() 在 接口 A 中没有标记为抽象, 因为这在接口中是默认的, 前提是该函数没有函数体)。 现在,如果我们从 A 派生一个实体类 C, 显而易见的,我们需要虫子 bar() 函数并且提供一个实现。
然而, 如果我们从 A 和 B 派生出 D , 我们需要实现从多个接口继承的所有方法。 这条规则适用于我们继承的单一实现的 (bar()) 方法和多实现的 (foo()) 方法。
可见性修饰符
类、对象、接口、构造函数、函数、属性和它们的setter都能拥有 可见性修饰符。(getter 的可见性一般和属性是一样的。)在Kotlin中一共有四种可见性修饰符: private, protected, internal 和 public。在没有任何显示的修饰符的时候,默认的可见性是 public.
接下来,请找出它们对于不同类型声明的作用域的详细说明。
包
函数、属性和类、对象和接口都能在“顶级”声明,也就是说直接在一个包里面声明:
// file name: example.kt
package foo
fun baz() {}
class Bar {}
如果你没有明确任何可见性修饰符,默认是使用 public , 这意味这个声明到处都是可见的;
如果你将某个声明标记为 private, 他只能在包含这个声明的文件内部可见;
如果你将他标记为 internal, 它在同一个 模块的任何地方都是可见的;
protected 不适合顶级声明。
举个例子:
// file name: example.kt
package foo
private fun foo() {} // 在example.kt内部可见
public var bar: Int = 5 // 该属性到处可见
private set // setter在example.kt内部可见
internal val baz = 6// 在同一个模块内部可见
类和接口
类的内部成员的声明:
private — 意味着仅仅在类内部可见 (包括它的所有成员);
protected — 在 private 的基础上加上子类也可见;
internal — 在同一模块内部 能看见类的声明,也就能看见 internal 成员;
public — 在任何类可见的地方,其 public 成员也都可见。
注意: Java的使用者: Kotlin中内部类的私有成员对于外部类是不可见的。
如果你重载了一个 protected 成员并且没有显示的标明可见性,重载的成员的可见性依旧是 protected 。
举个例子:
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 同样不可见
}
构造函数
想要确定一个类的主构造函数的可见性,请使用以下语法(注意:你需要显示的添加constructor关键字):
class C private constructor(a: Int) { ... }
这个构造函数的私有的。 默认情况下,所有构造函数都是 public, 只要是类可见的地方它也同样可见 (也就是说,一个 internal 类的构造函数只在同一个模块内可见 )。
局部声明
局部变量、函数和类都没有可见性修饰符
模块
internal 可见性修饰符意味着,在同一个模块内的成员可见。 更加具体点,一个模块是经过编译了的Kotlin文件的集合:
一个 IntelliJ IDEA 模块;
一个 Maven 工程;
一个 Gradle 源码集合;
一个 Ant 任务编译的文件集合。