类与对象
1. 属性与字段
-
Getters与Setters
set
方法与get
方法中如果需要用到变量,需要用field
来表示当前变量而不能直接引用,否则会造成死循环。
关键字field
表示幕后字段,只能在getter
和setter
内对它访问。
// 正确方法
var gender: String = "Man"
set(value) {
println("setter $value")
field = value
}
// 错误方法
var gender: String = "Man"
set(value) {
println("setter $value")
gender = value
}
2. 可见性修饰符
-
包
private
:不指定任何可见行修饰符,默认为public
protected
:只在声明他的文件内可见
internal
:在相同模块内随处可见
public
:不适用与顶层声明
注:要使用另一包中可见的顶层声明,仍需要将其导入进来。
-
类和接口
private
:只在该类的内部(包括所有成员)可见
protected
:类内部与子类中可见
internal
:能见到类声明的本模块内的任何客户端都可见
public
:能见到类声明的任何客户端
注:覆盖一个protected
成员并没有显式指定可见性,该成员仍为protected
-
构造函数
构造函数默认都是public
// 指定一个类的主构造函数的可见性,需要添加显式constructor关键字
class C private constructor(a: Int) { }
3. 扩展
-
扩展函数
扩展函数是静态分发的,它们是由扩展函数所在的表达式的类型来决定的,而不是由表达式运行时求值结果决定的。
fun main() {
open class Shape
class Rectangle: Shape()
fun Shape.getName() = "Shape"
fun Rectangle.getName() = "Rectangle"
fun printClassName(s: Shape) {
println(s.getName())
}
printClassName(Rectangle())
}
// 输出:Shape
如果一个类的成员函数与扩展函数具有相同的接收者类型和形同的函数名,并且都适用该定的参数,这种情况总是取成员函数。
fun main() {
class Example {
fun printFunctionType() { println("Class method") }
}
fun Example.printFunctionType() { println("Extension function") }
Example().printFunctionType()
}
// 输出:Class Method
-
拓展声明为成员
声明为成员的扩展可以声明为open
并在子类中覆盖。这意味着这些函数的分发对于分发接收者类型是虚拟的,但对于扩展接收者类型是静态的。
4. 数据类
主构造函数需要至少有一个参数。
componentN()
函数按声明顺序对应与所有属性。
5. 密封类
声明:类名前添加sealed
修饰符。
一个密封类是自身的抽象的,它不能直接实例化。
密封类用来表示受限的类继承结构:当一个值为有限几种类型,而不能有其他类型时。
使用密封类的关键好处:使用when
表达式的时候,如果能够验证语句覆盖了所有情况,就不需要为该语句再添加else
子句了。
sealed class SealedClass
class Bird(var name: String) : SealedClass()
class Fish(var name: String) : SealedClass()
fun getAnimalName (animal: SealedClass): String = when(animal) {
is Bird -> "鸟类:${animal.name}"
is Fish -> "鱼类:${animal.name}"
}
6. 泛型
泛型协变:取出对象(out)是安全的,传入对象(in)不可靠。
泛型逆变:传入对象(in)是安全的,取出对象(out)不可靠。
-
声明处型变(declaration-site variance)
注:只能读取的对象为生产者(out),只能写入的对象为消费者(in)。
标注Source
的类型参数T
来确保它从Source<T>
中返回(生产),并从不被消费。使用out
修饰符。
class User<out T> {
val info: T
constructor(info: T) {
this.info = info
}
fun test(): T {
println("执行test方法")
return info
}
}
当一个类 C
的类型参数 T
被声明为 out
时,它就只能出现在 C
的成员的输出位置,但回报是 C<Base>
可以安全地作为 C<Derived>
的超类。out
修饰符被称为型变注解,也称为声明处型变。
in
使得一个类型参逆变:只能被消费而不能被生产。
如果泛型T
只出现在该类的方法的返回值声明中,那么该类泛型形参即可使用out修饰T
;如果泛型T只出现在该类的方法的形参声明中,那么该泛型形参即可使用in修饰T
。
-
类型投影(使用处型变,type projections)
import java.lang.StringBuilder
fun main() {
fun fill(dest: Array<in String>, value: String) {
if (dest.isNotEmpty()) {
dest[0] = value
}
}
val arr1: Array<CharSequence> = arrayOf("a", "b", StringBuilder("cc"))
fill(arr1, "kotlin")
println(arr1.contentToString())
var intArr: Array<in Int> = arrayOf(1, 2, 3)
intArr[0] = 4
println(intArr.contentToString())
val numArr: Array<Number> = arrayOf(1, 2.1, 3.33333)
intArr = numArr
println(intArr.contentToString())
}
Array<out Any>
相当于Java的泛型上限:Array<? extends Objcject>
。
Array<in Int>
相当于Java的泛型下限:Array<? super Int>
,因此这种泛型型变用于支持逆变,还可以保证向其中安全地添加元素。
-
星投影
类似Java的原始数据类型。
如果类型被声明为Interface Function<in T, out U>
,则:
Function<*, String>
表示Function<in Nothing, String>
;
Function<Int, *>
表示Function<Int, out Any?>
;
Function<*, *>
表示Function<in Nothing, out Ant?>
。
-
泛型函数
类型参数放在函数名称之前。
fun <T> singletonList(item: T): List<T> { }
// 调用
val a1 = singletonList<Int>(1)
val a2 = singletonList(1)
-
泛型约束
上界:冒号之后指定的类型是上界。
fun <T : Comparable<T>> sort(list: List<T>) { }
默认的上界是Any?
。尖括号中只能指定一个上界,如果同一类型参数需要多个上界,则需要一个单独的where
子句。
fun <T> copyWhenGreater(list: List<T>, threshold: T): List<String>
where T : CharSequence,
T : Comparable<T> {
return list.filter { it > threshold }.map { it.toString() }
}
7. 嵌套类与内部类
-
嵌套类
class Outer {
private val bar: Int = 1
class Nested {
fun foo() = 2
val innerBar = bar // 编译无法通过
}
}
val demo = Outer.Nested().foo() // ==2
-
内部类
inner
关键字的嵌套类能够访问外部类的成员。内部类带有一个对外部类对象的引用。
class Outer {
private val bar: Int = 1
inner class Inner {
fun foo() = bar
}
}
val demo = Outer().Inner().foo() // == 1
-
匿名内部类
使用对象表达式创建匿名内部类实例。
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { }
override fun mouseEntered(e: MouseEvent) { }
})
8. 对象
-
对象表达式
如果只需要一个对象,采用如下方式:
fun foo() {
val adHoc = object {
var x: Int = 0
var y: Int = 0
}
print(adHoc.x + adHoc.y)
}
-
伴生对象
类内部的对象声明可以用companion
关键字标记。
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
伴生对象的成员可通过只使用类名作为限定符来调用。
val instance = MyClass.create()
省略对象的名称将默认使用名称Companion
。
class MyClass {
companion object { }
}
val x = MyClass.Companion
其自身的类名可用作该类的伴生对象的引用。
class MyClass1 {
companion object Named { }
}
val x = MyClass1 // MyClass1$Named@816f27d
class MyClass2 {
companion object { }
}
val y = MyClass2 // MyClass2$Companion@87aac27
9. 类型别名
为现有类型提供替代名称。
class A {
inner class Inner {
fun inner() {
println("A's inner")
}
}
}
typealias AInner = A.Inner
fun main() {
val aInner: AInner = A().Inner()
aInner.inner() // A's inner
}
10. 内联类
内联类必须含有唯一的一个属性在主构造函数中初始化。
内联类支持普通类中的一些功能。特别是,内联类可以声明属性与函数。
inline class Name(val s: String) {
val length: Int
get() = s.length
fun greet() {
println("Hello, $s")
}
}
注:内联类不能含有init
代码块,不能含有幕后字段。
内联类只能含有简单的计算属性。
内联类允许继承接口,不能继承其他类。
interface Printable {
fun prettyPrint(): String
}
inline class Name(val s: String) : Printable {
override fun prettyPrint(): String = "Let's $s!"
}
-
表示方式
只要将内联类用作另一种类型,它就会被装箱。
interface I
inline class Foo(val i: Int) : I
class Bar(val i: Int): I
fun <T> id(x: T): T = x
fun main() {
val f = Foo(10)
val foo = id(f) // Foo(i=10)
val b = Bar(20)
val bar = id(b) // Bar@1218025c
}
-
内联类与类型别名
区别:类型别名与其基础类型是赋值兼容的,内联类不满足。内联类引入了一个真实的新类型。
typealias StringAlias = String
inline class StringInlineClass(val s: String)
fun acceptString(s: String) {}
fun acceptStringAlias(n: StringAlias) {}
fun acceptStringInlineClass(p: StringInlineClass) {}
fun main() {
val stringAlias: StringAlias = ""
val stringInlineClass: StringInlineClass = StringInlineClass("")
val string = ""
acceptString(stringAlias) // 正确: 传递别名类型的实参替代函数中基础类型的形参
acceptString(stringInlineClass) // 错误: 不能传递内联类的实参替代函数中基础类型的形参
acceptStringAlias(string) // 正确: 传递基础类型的实参替代函数中别名类型的形参
acceptStringInlineClass(string) // 错误: 不能传递基础类型的实参替代函数中内联类类型的形参
}
11. 委托与委托属性
-
标准委托
(1)延迟属性 Lazy
by lazy
只能作用于val
关键字标注的属性。当属性用到的时候才初始化lazy{}
中的内容,再次调用属性的时候只会得到结果,不再执行lazy{}
的运行过程。
val lazyValue: String by lazy {
println("computed")
"hello"
}
fun main() {
println(lazyValue)
println(lazyValue)
}
// 输出
// computed
// hello
// hello
(2)可观察属性 Observable
import kotlin.properties.Delegates
class User {
var name: String by Delegates.observable("<no name>") {
prop, old, new ->
println("$old -> $new")
}
}
fun main() {
val user = User()
user.name = "first" // no name -> first
user.name = "second" // first -> second
}
Delegates.observable
接受两个参数:初始值与修改时处理程序(handler)。每次给属性赋值会执行该处理程序。它有三个参数:被赋值的属性、旧值与新值。
-
将属性储存在映射映射中
这种情况下,可以使用映射实例自身作为委托来实现委托属性。
class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
fun main() {
val user = User(mapOf(
"name" to "John",
"age" to 25
))
println(user.name) // John
println(user.age) // 25
}
-
属性委托要求
(1)val
属性,必须提供getValue()
函数,该函数具有如下参数:
— thisRef
与属性所有者类型(扩展属性的被扩展类型)相同或者是其超类型。
— property
必须是类型KProperty<*>
或其超类型。
getValue()
必须返回与属性相同的类型或其子类型。
(2)var
属性,必须额外提供一个setValue()
函数,该函数具有如下参数:
— thisRef
与属性所有者类型(扩展属性的被扩展类型)相同或者是其超类型。
— property
必须是类型KProperty<*>
或其超类型。
— value
必须与属性类型相同或者是其超类型。