类和对象
类声明由类名,类头(指定其类型参数,主构造函数等)和由大括号包围的类体构成,类头和类体都是可选的,如果一个类没有类体,可以省略花括号.
1.主构造函数
在kotlin中的一个类可以有一个主构造函数和多个次构造函数,主构造函数是类头的一部分,跟在类名后.
class Demo constructor(data: String){}
如果主构造函数没有任何注解或者可见性修饰符,可以省略constructor关键字
class Demo(data: String){}
主构造函数不能包含任何的代码,初始化的代码可以放到以init关键字作为前缀的初始化块中
class Demo(data: String){
init{
print(“init”)
}
}
主构造函数的参数可以在初始化块中使用,它们也可以在类体内声明的属性初始化器中使用
class Demo(var data: String){}
与普通属性一样,主构造函数中声明的属性可以是可变的var或者是只读的val
如果构造函数有注解或者可见性修饰符,这个constructor关键字是必需的,并且这些修饰符必须在它的前面
2.次构造函数
类也可以声明前缀有constructor的次构造函数
class Demo {
var data:Int = 0//若是以这样的形式声明成员变量必须赋予初值
constructor(data:Int) {//可以有多个不同参数的次构造函数
}
}
如果一个非抽象类没有声明任何(主或次)构造函数,它会有一个生成的不带任何参数的主构造函数,构造函数的可见性是public,可以声明一个带有非默认可见性的空的主构造函数
class Demo private constructor(){}
注:在jvm上,如果主构造函数的所有的参数都有默认值,编译器会生辰给一个额外的无参构造函数,它将使用默认值
class Demo(var data:Int = 1) {}
var demo = Demo()//demo的成员变量的data的值为1
3.创建类的实例
Kotlin没有new,这点是与java最不同的地方
var demo = Demo()//可变的对象 var
val demo = Demo(1);//不可变的对象 val
4.类成员
一个类可以包含:构造函数,初始化块,函数,属性,嵌套类和内部类,对象声明
5.继承
在kotlin中所有的类都有一个共同的超类Any(类似于java中的Object类),这对于没有超类型声明的类是默认超类
不过Any中的成员很少,只有equals() hashCode() toString()
要声明一个显式的超类型,把类型放到类头的冒号之后:
open class Base()//可以定义为open或者abstract中的一个
class Demo():Base(){}
如果该类有一个主构造函数,其基类必须用基类的主构造函数参数就地初始化
如果类没有主构造函数,那么每个次构造函数必须用super关键字初始化其基类,或委托给另一个构造函数做到这一点,注意:在这种情况下,不同的次构造函数可以调用基类型的不同的构造函数
class MyView : View {
constructor(ctx: Context) : super(ctx)
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}
类上的open标注与java的final相反,它允许其他类从这个类继承,默认情况下,在kotlin中所有的类都是final
6.覆盖方法
Kotlin需要显式标注可覆盖的成员和覆盖后的成员
open class Base{
open fun v(){}
}
class Demo():Base(){
Override fun v(){}
}
在Demo类中的v()函数上必须加上override标注,如果没写,编译器会报错.如果函数没有标注open则子类中不允许定义相同签名的函数,不论加不加override.在一个final类中,开放成员是禁止的,相反在标记为override的成员本事是开放的,也就是说,它可以在子类中覆盖,如果想禁止再次覆盖,可以使用final关键字修饰,这样就禁止被再次覆盖了
open class Demo():Base(){
Final override fun v(){}
}
7.覆盖属性
属性覆盖与方法覆盖类似,在超类中声明然后在派生类中重新声明的属性必须以override开头,并且它们必须具有兼容的类型,每个声明的属性可以由具有初始化器的属性或者具有getter()方法的属性覆盖
open class Base {
open var data:Int = 1
get() {return 2}
}
class Demo : Base(){
override var data:Int = 3
get() = 4
}
最后的Demo的data的值为4
可以用一个var覆盖基类的val,但反之不行.
8.覆盖规则
在kotlin中,实现继承由下述规则规定:如果一个类从它的直接超类继承相同成员的多个实现,它必须覆盖这个成员并提供其自己的实现(也许用继承来的其中之一).为了表示采用从哪个超类型继承的实现,我们使用由尖括号中超类型名限定的super,如super<Base>
open class Base {
open fun f(){}
}
interface Base2{
fun f(){}
}
class Demo : Base(),Base2{
override fun f(){
super<Base2>.f()
super<Base>.f()
}
}
9.抽象类
类和其中的某些成员可以声明为abstract,抽象成员在本类中可以不用实现,需要注意的是,我们并不需要用open标注一个抽象类或者函数,,因为是废话..
可以用一个抽象成员覆盖基类的费抽象的开放成员
10.伴生对象
与java不同,在kotlin中类没有静态方法,在多数情况下,它建议简单地使用包级函数
如果需要写一个可以无需用一个类的实例来调用,但需要访问类内部的函数,也就是对java中的static的需要.在kotlin中可以写成该类内对象声明中的一员,中文意思就是:如果在类内声明一个伴生对象,就可以像在java中调用static相同的语法来调用其成员,只使用类名作为限定符.
如下:
open class Demo {
companion object {
fun f(){
}
}
}
注意:每一个类中只能有一个companion object 此时的对象名称是Companion
也可以自定义名字:
open class Demo {
companion object MyCompanion{
fun f(){
}
}
}
调用的时候:
Demo.f()
Demo.MyCompanion.f()
11.单例模式(对象声明)
在kotlin中单例实现很简单,直接贴代码:
object Single{
fun f(){
println("single")
}
}
注意:对象声明不能在局部作用域中,但是它们可以嵌套到其他对象声明或非内部类中
对象表达式和对象声明的区别:
A.对象表达式是在使用它们的地方立即执行,创建对象
B.对象声明是在第一次被访问到时延迟初始化的,懒汉式的单例模式
C.伴生对象的初始化是在相应的类被加载时,与java静态初始化的寓意
12.属性和字段
声明属性:
Kotlin的类可以有属性,属性可以用var声明为可变的,否则使用只读关键字val
class Demo{
var data1:String = “”
var data2:Int = 0
val data3:Int = 1
}
要使用一个属性,名称引用即可
Getters和Setters
声明一个属性的完整语法是
var <propertyName> [:<propertyType>] [=<property_initializer>]
[<getter>]
[<setter>]
初始器getter和setter是可选的,属性类型如果可以从初始器(或者从getter返回值)中推断出来,也可以省略
EG:
var demo1:Int? //错误:需要显示地初始化,隐含默认getter和setter
var demo2 = 1 //类型Int,默认getter和setter
一个只读属性的语法和一个可变的属性的语法有两方面的不同:1.只读属性的用val开始代替var, 2.只读属性不允许setter
val demo:Int?//类型 Int, 默认getter,必须在构造函数中初始化
val demo2 = 1//类型Int 默认getter
自定义getter和setter的例子
var demo:String
get() = this.toString()
set(value) {
This.demo = value
}
改变一个访问器的可见性或者对其注解,但是不需要改变默认的实现,可以定义访问器而不定义其实现
var demo:String=”hello”
private set//此setter是私有的并且有默认的实现
Var demo2:Any? = null
@Inject set //用Inject注解此setter
幕后字段:
Kotlin中类不能有字段,当使用自定义访问器的时候,有时有一个幕后字段(backing field)有时是必需的.为此kotlin提供一个自动幕后字段,可通过使用field标识符访问
var counter = 0 //此初始化值直接写到幕后字段里面
set(value) {
If(value > 0) filed = value
}
Field标识符只能用在属性的访问器内
如果属性至少一个访问器使用默认实现,或者自定义访问器通过field引用幕后字段,将会为该属性生成一个幕后字段.
例如下面的情况,就没有幕后字段:
val isEmpty : Boolean
get() = this.size == 0
编译期常量
已知值的属性可以使用const修饰符标记为编译期常量,这些属性需要满足以下的要求:
A.位于顶层或者是object的一个成员
B.用String或原生类型值初始化
C.没有自定义getter
这些属性可以用在注解中:
const val demo:String=”hello world”
惰性初始化属性
一般的,属性声明为非空类型必须在构造函数中初始化,然而这经常不方便,例如:属性可以通过依赖注入来初始化,或者在单元测试的setup方法中初始化,这种情况下,不可以在构造函数中提供一个非空初始器,这时候可以用lateinit修饰符标记该属性
lateinit var demo:String
该修饰符只能用于在类体中(不是在主构造函数中)声明的var属性,并且仅当该属性没有自定义getter或者setter时,该属性必须是非空类型,并且不能是原生类型.
在初始化前访问一个lateinit属性会抛出一个特定异常,该异常明确标识该属性被访问及它没有初始化的事实
委托属性
最常见的一类属性就是简单地从幕后字段中读取,另一方面,使用自定义getter和setter可以实现属性的任何行为,介于两者之间,属性如何工作有一些常见的模式