一、类定义
Kotlin 类可以包含:构造函数和初始化代码块、函数、属性、内部类、对象声明。
Kotlin 中使用关键字 class 声明类,后面紧跟类名:
//Kotlin 中的类可以有一个 主构造器,以及一个或多个次构造器,主构造器是类头部的一部分,位于类名称之后:
class Person constructor(firstName: String){
var name: String = ……
var url: String = ……
//Kotlin 中类不能有字段。提供了 Backing Fields(后端变量) 机制,备用字段使用field关键字声明,field 关键词只能用于属性的访问器
var num: Int = 100
get() = field // 后端变量
set(value) {
if (value < 10) { // 如果传入的值小于 10 返回该值
field = value
} else {
field = -1 // 如果传入的值大于等于 10 返回 -1
}
}
//非空属性必须在定义的时候初始化,kotlin提供了一种可以延迟初始化的方案,使用 lateinit 关键字描述属性:
lateinit var subject: TestSubject
//次构造函数
constructor(parent: Person) {
// 初始化...
}
constructor (name: String, age:Int) : this(name) {
// 初始化...
}
init {
//初始化代码
}
//在类中定义成员函数
fun foo() {
print("Foo")
}
}
// 创建对象,Kotlin 中没有 new 关键字
val person = Person()
//使用属性,只要用名称引用它即可; 使用 . 号来引用
person.name
person.url
我们也可以定义一个空类:
class Empty
1、类的属性
属性定义
类的属性可以用关键字 var 声明为可变的,
否则使用只读关键字 val 声明为不可变。
getter 和 setter
属性声明的完整语法:
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
getter 和 setter 都是可选
如果属性类型可以从初始化语句或者类的成员函数中推断出来,那就可以省去类型,val不允许设置setter函数,因为它是只读的。
2、主次构造器
- 主构造器中不能包含任何代码,初始化代码可以放在初始化代码段中,初始化代码段使用 init 关键字作为前缀;
- 如果构造器有注解,或者有可见度修饰符,这时constructor关键字是必须的,注解和修饰符要放在它之前;
- 次构造函数:类也可以有二级构造函数,需要加前缀 constructor;
- 如果类有主构造函数,每个次构造函数都要,或直接或间接通过另一个次构造函数代理主构造函数。在同一个类中代理另一个构造函数使用 this 关键字;
- 如果一个非抽象类没有声明构造函数(主构造函数或次构造函数),它会产生一个没有参数的构造函数。构造函数是 public 。如果你不想你的类有公共的构造函数,你就得声明一个空的主构造函数:
class Person constructor(firstName: String) {
init {
println("FirstName is $firstName")
}
//第3 类也可以有二级构造函数,需要加前缀 constructor:
constructor(parent: Person) {
parent.children.add(this)
}
//第4
constructor (name: String, age:Int) : this(name) {
// 初始化...
}
//第5
class DontCreateMe private constructor () {
}
}
主构造函数-->init代码块-->次构造函数
二、类的修饰符
类的修饰符包括 classModifier 和_accessModifier_:
classModifier: 类属性修饰符,标示类本身特性。
abstract // 抽象类
final // 类不可继承,默认属性
enum // 枚举类
open // 类可继承,类默认是final的
annotation // 注解类
accessModifier: 访问权限修饰符
private // 仅在同一个文件中可见
protected // 同一个文件中或子类可见
public // 所有调用的地方都可见
internal // 同一个模块中可见
三、抽象类
抽象是面向对象编程的特征之一,类本身,或类中的部分成员,都可以声明为abstract的。抽象成员在类中不存在具体的实现。
注意:无需对抽象类或抽象成员标注open注解。
open class Base {
open fun f() {}
}
abstract class Derived : Base() {
override abstract fun f()
}
四、嵌套类
我们可以把类嵌套在其他类中,看以下实例:
class Outer { // 外部类
private val bar: Int = 1
class Nested { // 嵌套类
fun foo() = 2
}
}
fun main(args: Array<String>) {
val demo = Outer.Nested().foo() // 调用格式:外部类.嵌套类.嵌套类方法/属性
println(demo) // == 2
}
kotlin 嵌套类相当于 java 的静态内部类(有static );
kotlin 内部类则对应于 java 的非静态内部类(无 static)
五、内部类
内部类使用 inner 关键字来表示。
内部类会带有一个对外部类的对象的引用,所以内部类可以访问外部类成员属性和成员函数。
class Outer {
private val bar: Int = 1
var v = "成员属性"
/**嵌套内部类**/
inner class Inner {
fun foo() = bar // 访问外部类成员
fun innerTest() {
var o = this@Outer //获取外部类的成员变量
println("内部类可以引用外部类的成员,例如:" + o.v)
}
}
}
fun main(args: Array<String>) {
val demo = Outer().Inner().foo()
println(demo) // 1
val demo2 = Outer().Inner().innerTest()
println(demo2) // 内部类可以引用外部类的成员,例如:成员属性
}
为了消除歧义,要访问来自外部作用域的 this,我们使用this@label,其中 @label 是一个 代指 this 来源的标签。
六、匿名内部类
kotlin 中:用对象表达式取代了匿名内部类;
对象表达式用 object : 开头,后面是要实现/继承的父类/父接口。如果没有父类/父接口,则只需要 object 即可:
var myobject = object {
init {
println("init called")
}
//实现方法
}
//如果它需要实现多个接口或父类,用逗号分隔
var myobject = object: MyInterface, MyAbstractClass() {
override fun print(i: Int) {
println("i = $i")
}
}
class Test {
var v = "成员属性"
fun setInterFace(test: TestInterFace) {
test.test()
}
}
/**
* 定义接口
*/
interface TestInterFace {
fun test()
}
fun main(args: Array<String>) {
var test = Test()
/**
* 采用对象表达式来创建接口对象,即匿名内部类的实例。
*/
test.setInterFace(object : TestInterFace {
override fun test() {
println("对象表达式创建匿名内部类的实例")
}
})
}
对象表达式 vs lambda 表达式
在 kotlin 中,如果对象表达式实现的是一个函数式接口(该接口只有一个抽象方法),我们可以将它转换成 lambda 表达式。
fun setInterFace(test: TestInterFace) {
test.test()
}
fun setInterFace{
test.test()
}
七、数据类
Kotlin 可以创建一个只包含数据的类,关键字为 data:
data class User(val name: String, val age: Int)
编译器会自动的从主构造函数中根据所有声明的属性提取以下函数:
equals() / hashCode()
toString() 格式如 "User(name=John, age=42)"
componentN() functions 对应于属性,按声明顺序排列
copy() 函数
如果这些函数在类中已经被明确定义了,或者从超类中继承而来,就不再会生成。
为了保证生成代码的一致性以及有意义,数据类需要满足以下条件:
- 主构造函数至少包含一个参数。
- 所有的主构造函数的参数必须标识为val 或者 var ;
- 数据类不可以声明为 abstract, open, sealed 或者 inner;
- 数据类不能继承其他类 (但是可以实现接口)。
标准数据类
标准库提供了 Pair 和 Triple 。在大多数情形中,命名数据类是更好的设计选择,因为这样代码可读性更强而且提供了有意义的名字和属性。
八、密封类
密封类用来表示受限的类继承结构:当一个值为有限几种的类型, 而不能有任何其他类型时。
在某种意义上,他们是枚举类的扩展:枚举类型的值集合 也是受限的,但每个枚举常量只存在一个实例,而密封类的一个子类可以有可包含状态的多个实例。
声明一个密封类,使用 sealed 修饰类,密封类可以有子类,但是所有的子类都必须要内嵌在密封类中。
sealed 不能修饰 interface ,abstract class(会报 warning,但是不会出现编译错误)
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
fun eval(expr: Expr): Double = when (expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
}
使用密封类的关键好处在于使用 when 表达式 的时候,如果能够 验证语句覆盖了所有情况,就不需要为该语句再添加一个 else 子句了。
九、枚举类
枚举类最基本的用法是实现一个类型安全的枚举。
枚举常量用逗号分隔,每个枚举常量都是一个对象。
enum class Color{
RED,BLACK,BLUE,GREEN,WHITE
}
枚举还支持以声明自己的匿名类及相应的方法、以及覆盖基类的方法。
enum class ProtocolState {
WAITING {
override fun signal() = TALKING
},
TALKING {
override fun signal() = WAITING
};
abstract fun signal(): ProtocolState
}
使用枚举常量
Kotlin 中的枚举类具有合成方法,允许遍历定义的枚举常量,并通过其名称获取枚举常数。
EnumClass.valueOf(value: String): EnumClass // 转换指定 name 为枚举值,若未匹配成功,会抛出IllegalArgumentException
EnumClass.values(): Array<EnumClass> // 以数组的形式,返回枚举值
获取枚举相关信息:
val name: String //获取枚举名称
val ordinal: Int //获取枚举值在所有枚举数组中定义的顺序