泛型
与Java类似,Kotlin中的类也可以有类型参数。要创建这样类的实例,我们需要提供类型参数,但是如果类型参数可以推断出来,例如从构造函数的参数或者从其它途径,允许省略类型参数
class Box<T>(t: T){
var value = t
}
val box: Box<Int> = Box<Int>(1)
//1具有类型Int,所以编译器知道我们说的是 Box<Int>
val box: Box(1)
内部类
标记为 inner 的嵌套类能够访问其他外部类的成员。内部类会带有一个对外部类的对象的引用
class Outer{
private val bar: Int = 1
inner class Inner{
fun foo() = bar
}
}
val demo = Outer().Inner().foo() // == 1
枚举类
枚举类的最基本的用法是实现类型安全的枚举, 每个枚举常量都是一个对象,枚举常量用逗号分隔
enum class Direction{
NORTH, SOUTH, WEST, EAST
}
//因为每个枚举都是枚举类的实例,所以他们可以是这样初始化过的:
enum class Color(val rgb: Int){
RED(0xFF0000)
GREEN(0x00FF00)
BLUE(0x0000FF)
}
//枚举常量还可以声明其带有相应方法以及覆盖了其基类方法的匿名类
enum class ProtocolState{
WAITING{
override fun signal() = TALKING
},
TAKING{
override fun signal() = WAITING
};
abstract fun signal(): ProtocolState
}
如果枚举类定义任何成员,那么使用分号将成员定义中的枚举常量定义分隔开。
枚举条目不能包含内部类以外的嵌套类型
在枚举类中实现接口
一个枚举类可以实现接口(但不能从类继承),可以为所有条目提供统一的接口成员实现,也可以在相应匿名类中为每个条目提供各自的实现。只需将接口添加到枚举类声明中即可(也就是将接口使用 import 导入到枚举类中)
对象
伴生对象
类内部的对象声明可以用 companion 关键字标记
class MyClass{
companion object Factory{
fun create(): MyClass = MyClass()
}
}
//该伴生对象的成员可以只通过类名作为限定符来调用
val instance = MyClass.create()
//可以省略伴生对象的名称,在这种情况下将使用名称 Companion
class MyClass{
companion object{}
}
val x = MyClass.Companion
即使伴生对象看起来像其他语言的静态成员,在运行时他们仍然是真实对象的实例成员
对象表达式
创建一个继承自某个(或某些)类型的匿名类的对象
window.addMouseListener(object: MouseAdapter(){
override fun mouseClicked(e: MouseEvent){/*...*/}
override fun mouseMoved(e: MouseEvent){/*....*/}
})
匿名对象可以用作只在本地和私有作用域中声明的类型。如果使用匿名对象作为公有函数的返回类型或者用作公有属性的类型,那么该函数或属性的实际类型会是匿名对象生命的超类型,如果没有声明任何超类型,就会是 Any。在匿名对象中添加的成员将无法访问
class C{
private fun foo() = object{
val x: String = "x"
}
fun publicFoo() = object{
val x: String = "x"
}
fun bar(){
val x1 = foo().x //没问题
val x2 = publicFoo().x //错误:未能解析的引用“X”
}
}
对象声明
在 object 关键字后跟一个名称,就像变量声明一样,对象声明不是一个表达式,不能用在赋值语句的右边。对象声明的初始化过程是线程安全的,如需引用该对象,我们直接使用其名称即可
object DataProviderManager{
fun registerDataProvider(provider: DataProvider){
//...
}
}
//直接使用
DataProviderManager.registerDataProvider(....)
注意:对象声明不能在局部作用域(即直接嵌套在函数内部),但是它们可以嵌套到其他对象声明或非内部类中
对象表达式和对象声明之间的语义差异
- 对象表达式是在使用他们的地方立即执行(及初始化)的
- 对象声明是在第一次被访问到时延迟初始化的
- 伴生对象的初始化是在相应的类被加载(解析)时,与java静态初始化器的语义相匹配
类型别名
类型别名不会引入新类型,它们等效于相应的底层类型。当你在代码中添加 typealias Predicate<T> 并使用 Predicate<Int> 时,Kotlin编译器总是把它扩展为 (Int)-> Boolean
typealias MyHandler = (Int, String, Any) -> Unit
typealias Predicate<T> = (T) -> Boolean
fun foo(p: Predicate<Int>) = p(42)
fun main(){
val f: (Int) -> Boolean = { it > 0}
println(foo(f)) //输出true
val p: Predicate<Int> = { it > 0}
println(listOf(1,-2).filter(p)) //输出"[1]"
}
内联类
有时候业务逻辑需要围绕某种类型创建包装器;然而,由于额外的堆内存分配问题,它会引入运行时的性能开销。此外,如果被包装的类型是原生类型,性能的损失是很糟糕的,因此为原生类型通常在运行时就进行了大量的优化,然而他们的包装器却没有得到任何特殊的处理
为了解决这类问题,Kotlin 引入了一种被称为内联类的特殊类,通过在类的定义前面定义一个 inline 修饰符来声明
内联类必须含有唯一的一个属性在主构造函数中初始化。在运行时,将使用这个唯一属性来表示内联类的实例
inline class Password(val value: String)
//不存在“Password”类的真实实例对象
//在运行时, sucurePassword仅仅包含 “String”
//这就是内联类的主要特性,他的灵感来源于“inline”这个名称:类的数据被“内联”到该类使用的地方
val sucurePassword = Password("Don't try this in production")
当然,内联类的成员也有一些限制
- 内联类不能含有 init 代码块
- 内联类不能含有幕后字段(因此内联类只能含有简单的计算属性)
在生成的代码中,Kotlin编译器为每个内联类保留一个包装器,内联类的实例可以在运行时表示为包装器或者基础类型(Int可以表示为原生类型int 或者包装器 Integer)
在Gradle 中启用内联类
compileKotlin{
kotlinOptions.freeCompilerArgs +=["-Xinline-classes"]
}
委托
委托属性
有一些常见的属性类型,虽然我们可以在每次需要的时候手动实现它们,但是如果能够为大家把他们只实现一次并放入一个库会更好。例如包括
- 延迟属性(lazy properties):其值只在首次访问时计算
- 可观察属性(observable properties):监听器会收到有关此属性变更的通知
- 把多个属性存储在一个映射(map)中,而不是每个存在单独的字段中
//语法是: val/var <属性名>: <类型> by <表达式>
//在by后面的表达式是该委托,因为属性对应的 get()/set()会被委托给它的getValue()/setValue()方法
//属性的委托不必实现任何的接口,但是需要提供一个 getValue()函数(与setValue()——对于var属性)
class Example{
var p: String by Delegate()
}