Kotlin学习笔记(三)类与对象
类
类的声明
类的定义:类通过class关键字来声明,类声明由类名、类头(指定其类型参数、主 构造函数等)和由大括号包围的类体构成。类头和类体都是可选的; 如果一个类没有类体,可以省略花括号。类具有一个唯一的主构造函数,只需在类名后面写上参数,如果需要函数体,可以写在init块中。
//如果非抽象类没有声明任何主、次构造函数,会自动生成一个无参构造函数
class Student
class Student2 constructor(name:String,age: Int){
}
//如果主构造函数没有任何注解或可见性修饰符,可以省略constructor关键字。构造函数默认是public的,如需私有,自己添加private等可见性修饰符。
class Student3(name:String,age: Int){
}
class Student4(name: String,age: Int){
init {
//主构造函数体
}
}
//如果类有一个主构造函数,每个次构造函数需要委托给主构造函数, 可以直接委托或者通过别的次构造函数间接委托。委托到同一个类的另一个构造函数 用 this 关键字即可:
class Person(val name: String) {
constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
}
}
类的实例化
//类的实例化不需要new关键字,kotlin中也没有这个关键字
val student1 = Student()
var student2 = Student("jack",18)
类继承
Kotlin中所有类都有一个共同的超类Any,要声明一个显式的超类型,我们把类型放到类头的冒号之后:
//kotlin中的非抽象类默认是不可继承的,如需继承,需要自己声明open
open class People
class Student: People()
方法也是默认为final,如需继承,必须加open,同时子类的方法必须加override,且子类中的方法可以再次被覆盖,除非声明为final:
open class Student():People(){
final override fun name(){ }
}
属性覆盖与方法覆盖类似,每个声明的属性可以由初始化属性或者getter方法的属性覆盖:
open class People {
open val x: Int get { …… }
}
//你也可以用一个 var 属性覆盖一个 val 属性,但反之则不行。因为一个val 属性本质上声明了一个 getter 方法,而将其覆盖为 var 只是在子类中额外声明一个setter 方法。
class Student : People() {
override val x: Int = ……
}
对于多继承来说,有时候会碰到超类中有同名函数,可以利用super来指明我们继承的是哪个超类:
open class A {
open fun f() { print("A") }
fun a() { print("a") }
}
interface B {
fun f() { print("B") } // 接口成员默认就是“open”的
fun b() { print("b") }
}
class C() : A(), B {
// 编译器要求覆盖 f():
override fun f() {
super<A>.f() // 调用 A.f()
super<B>.f() // 调用 B.f()
}
}
可以用抽象成员覆盖一个非抽象的开放成员。
属性
声明属性的完整语法:
var <propertyName> : [<propertyType>] [ = <property_init>]
[<getter>]
[<setter>]
其中初始值和getter、setter都是可选的,如果可以从初始值或者getter返回值中推断出类型,则属性类型也是可以省略的。上面定义的是一个可变属性,也可以用val来定义一个只读属性,只读属性不允许有setter方法。
如果需要在getter和setter中访问这个属性自身的值,它需要创建一个 backing field 。可以使用 field 这个预留字段来访问,它会在属性初始化时自动创建。需要注意的是,如果我们直接调用了属性,那我们会使用setter和getter而不是直接访问这个属性。 backing field 只能在属性访问器内访问。
var name: String = "jack"
var age: Int = 18
set(value) {
//age = value//注意这种方式会触发死循环,无限调用set,最终崩溃
field = value
}
val sex = "男"
val address
get() = "长沙"
var score: Int = 88
get() = 22 //无论什么时候调用score,得到的都会是18
set(value) {
field = value
}
...
val student = Student()
println(student.age)//18
println(student.score)//22
student.age = 20
student.score = 99
println(student.age)//20
println(student.score)//22
编译期常量
已知值的属性可以使用 const 修饰符标记为编译期常量,类似于java中的static final(java中的static final修饰的属性在调用时不需要将类初始化,仅仅static修饰的值调用是需要进行类的初始化的)。 这些属性需要满足以下要求:
- 位于顶层(即包级属性,不能写在类中)或者是 object 的一个成员
- 用 String 或原生类型值初始化
- 没有自定义 getter
const val pai = 3.1415926
object Per{
const val sex = "男"
}
fun main(args: Array<String>) {
println(pai)//3.415926
println(sex)//男
}
惰性初始化属性
一般地,属性声明为非空类型必须在构造函数中初始化。但这有时候很不方便,比如我想声明一个数据集合为非空,但在初始化时我们又得不到这个数据,可以使用lateinit来修饰变量,延迟初始化。注意lateinit不能修饰val,只能修饰非空可变变量,并且该变量不能为原始类型,不能不能有自定义getter或setter。
class Teacher {
var name = "YoYo"
var age: Int
init {
age = 25
}
lateinit var students: List<Student>
}
对象
对象表达式
kotlin中的对象表达式类似于java的匿名内部类。object表达式可以直接访问包含这个表达式的作用域的变量,而不用像java一样需要加final修饰。
interface Pl2303 {
fun connectSuccess()
}
class Pl2303Activity {
val s = object {//没有超类
val i = 1
}
fun addListener(pl2303: Pl2303) {}
fun connect() {
addListener(object : Pl2303 {//有超类
override fun connectSuccess() {
}
})
}
}
请注意,匿名对象可以用作只在本地和私有作用域中声明的类型。如果你使用匿名对象作为公有函数的返回类型或者用作公有属性的类型,那么该函数或属性的实际类型会是匿名对象声明的超类型,如果你没有声明任何超类型,就会是 Any 。在匿名对象中添加的成员将无法访问,就像java中的匿名内部类,也只能在父类里面访问。
class C {
// 私有函数,所以其返回类型是匿名对象类型
private fun foo() = object {
val x: String = "x"
}
// 公有函数,所以其返回类型是 Any
fun publicFoo() = object {
val x: String = "x"
}
fun bar() {
val x1 = foo().x // 没问题
val x2 = publicFoo().x // 错误:未能解析的引用“x”
}
}
对象声明
object A{//对象也可以有超类,跟在冒号后面即可,多个超类用逗号隔开
fun a_1(){
}
}
class B{
fun b_1(){
A.a_1()
}
}
A是一个对象声明,跟在object关键字的后面,对象声明不是表达式,不能跟在等号的右边。可以直接通过对象名来调用。
对象声明不能在局部作用域(即直接嵌套在函数内部),但是它们可以嵌套到其他对象声明或非内部类中。
伴生对象
与Java不同,Kotlin中并没有静态方法的概念。如需使用,可以用companion object(其实可以简单看成一个对象声明,默认名为Companion)。
class Chess {
companion object {//省略了对象名Companion
val WHITE_MAX: Double = 100.0
val BLACK_MAX: Double = 100.0
val WHITE_MIN: Double = -100.0
val BLACK_MIN: Double = -100.0
val RAW_NUM = 19
val TYPE_QUICK_CHECK: String = "~DEB1#"
val GET_ALL_DATA_IN_QUICK_MODE: String = "~REQ#"
}
....
//调用方式:
Student.id//kotlin中调用
Student.Companion.getId()//java中调用,Companion为默认的伴生对象名,也可以通过companion object 后跟一个对象名来改变。
}
三者区别:
- 对象表达式是在使用他们的地方立即执行(及初始化)的
- 对象声明是在第一次被访问到时延迟初始化的
- 伴生对象的初始化是在相应的类被加载(解析)时,与 Java 静态初始化器的语义相匹配
第三条很好理解,主要看下前两条:
class B {
init {
println("BBBBBB")
}
val s = object {
init {
println("ssssss")
}
fun p(){
print("pppppp")
}
}
object A {
init {
println("AAAAAAA")
}
fun a_1() {
println("aaaaaaa")
}
}
fun b_1() {
A.a_1()
}
}
fun main(args: Array<String>) {
val b = B()
println("------------------")
b.b_1()
}
//结果
//BBBBBB
//ssssss
//------------------
//AAAAAAA
//aaaaaaa
说明在类初始化的时候,对象表达式身为属性已经被执行,而对象声明在被访问的时候才被执行。