>与类定义有关的关键字
>类的定义
[权限修饰符][final|open|abstruct] class [<泛型>] 类名 [主构造器权限修饰符][主构造器] [:继承关系]{
$类属性定义
$类方法定义
}
接口的定义稍微不同:
interface 接口名 {
$接口属性定义
$接口方法定义
}
>类/接口属性定义
在Kotlin中,没有字段的设计,只有属性。
什么是字段和属性?
让我们看一下一个完整的非泛型类定义:
class DemoClass constructor(name:String) : InfoInterface { // 默认为final类、public类,是非open的、不可被重写
//默认属性与方法是public的
private val id:Int by lazy { 5 } // 惰性加载
protected lateinit var sex:String // 延迟加载,仅对可空对象可进行如此操作
public var name:String=name // 与主构造器中的参数名字相同,会被建议修改。但并不影响代码运行
private var age:Int = 0
get() = field
set(value) {
if(value < 0){
field = 0
} else {
field = value
}
}//属性get()与set()的重写
init {
println("初始化代码块")
}
constructor(name:String,sex:String):this(name){
println("构造函数1")
this.sex=sex
}
constructor(name:String,sex:String,a:Int):this(name){
println("构造函数2")
this.sex=sex
age=a
}
//这是一个方法,重写了接口InfoInterface中的方法
override fun info(){
println("id=${this.id} \n name=$name \n sex=$sex \n age=$age")
}
}
刚刚提到了属性与字段的定义,一定有人问前三个属性(id sex name)是不是字段,因为并没有看到相应的get和set方法。答案是否定的。
kotlin中不存在字段,只有属性。事实上,kotlin为默认为每个属性生成一个get与set函数:
var attr:Class = $VALUE
get() = field
set(value) = { field = value }
其中的field就是后端变量(backing field)的关键字。对于刚刚提到的三个属性(id sex name),实际上kotlin就为他们默认生成了这样的方法。
回到最初的类函数定义,观察第四个属性age:
private var age:Int = 0
get() = field
set(value) {
if(value < 0){
field = 0
} else {
field = value
}
}
这段代码的意思是,当调用属性age时,会直接返回这个属性的值( get()=field );当进行赋值时,若所赋的值大于0则正常赋值,否则,赋值为0;本质上,get()后面只要是个表达式就可以,所以也可以这么写:
private val age:Int = 0
get() = if( field > 0 ) field else 0
set(value) { field = value }
所以,kotlin中,对一个属性的完整定义应该为:
var [: ] [= ]
[]
[]
注意,只有var才能有set()方法。总的来说,后端变量机制就是对get()和set()的重写,field代指的是该变量未更新前的值。
>类/接口方法定义
类的方法的定义:
[权限修饰符] [abstruct|final] fun 方法名 (参数表){
函数体
}
如果是重写的方法,需要在前面加上一个
override关键词。
>类的构造
kotlin中,类的构造,分为主构造器和次构造器,有以下几点需要注意区分:
什么是主构造器的用法?看我在文章开头给的那个例子,可以看到在类的第一行给出了主构造器:
class DemoClass constructor(name:String)
其参数常用于类属性的初始化(kotlin中定义类时就要保证类属性不为空),详见
>这篇文章<中关于主构造器赋值的内容。
在我给出的例子中,有两个次构造器:
constructor(name:String,sex:String):this(name){
println("构造函数1")
this.sex=sex
}
constructor(name:String,sex:String,a:Int):this(name){
println("构造函数2")
this.sex=sex
age=a
}
他们都继承了来自主构造器的属性name:String,同时,次构造器1与次构造器2在参数上不同,这就使得在构造时,下面的三个语句进行对象实例化,构造时调用是完全不同的内容:
val d:DemoClass = DemoClass("shepibaipao","girl")//调用次构造器1
val d:DemoClass = DemoClass("shepibaipao","girl",20)//调用次构造器2
val d:DemoClass = DemoClass("shepibaipao")//调用主构造器
但无论如何,在调用次构造器前,一定会调用init{}语句块。具体调用顺序为:
主构造器>init{}语句块>次构造器
由于constructor都是public类型的,想要写出一个单实例类,没法像java一样操作。
解决方法是:在kotlin中,主构造器是可以具有private属性的:
class demo private constructor(name:String){}//此时constructor不可省略
-----------------------------------------------
>Kotlin中的泛型
Kotlin中的泛型函数定义如下:
fun funcA(t:T) {
println(t)
}
Kotlin中的泛型类定义如下:
class A (t:T){
var v = t
}
Koltin中的泛型,是经过类型擦除的,看下面这个例子:
var aInt = A(100086)
var aString = A("shenpibaipao")
println(aInt.javaClass) // 打印:class Box
println(aString.javaClass) // 打印:class Box
这意味着,在编译成字节码时,class A >通配符
先来回顾一下java中的通配符 ?:
>1.类型协变
类型协变用 in 注明消费者,用 out 注明生产者:
class A {
fun consume(c:C){ // 只消费(写入)C的对象c
//deal c
}
fun produce():P{
val p:P by Proxy()
return p // 只生产(读出)P的对象p
}
}
>2.类型投射
kotlin中的类型投射,主要针对“星号投射”,抛开那些绕来绕去的说法,单刀直入吧,对于:
class A < in C , out P >( c:C , val p:P ){
fun f1(c:C){
println(c)
}
fun f2():P{
return p
}
}
val o1:A<*,String> = A (7,"yes") // 只可以读出String的任意父类
val o2:A = A (7,"yes") // 可以安全写入Int
val o3:A<*,*> = A (7,"yes") // 只可以读出String的任意父类,不能安全写入Int
>3.泛型约束
class D < T : List >{
}
这样,List的所有子类均可被接受。当有多个上界时,可以这么写:
class D where T: Comparable , T:List{
}