>与类定义有关的关键字
- open :标示一个类,使得这个类可以被继承;
- abstract :标示一个抽象类,这个类默认为open;
- interface :标示一个接口,默认为open;
- internal :标示一个内部类(本篇略去);
- final :标示一个final类,不可被继承和重写;
- constructor :构造器关键词,用于主构造器时可以省略;
- init :初始化代码块,详见下文;
- public :权限修饰符,同JAVA;
- private :权限修饰符,同JAVA;
- protected 权限修饰符,同JAVA;
- lateinit :延迟初始化,用法见:>lateinit与by lazy的应用<;
- set()/get() :属性获取方法,涉及Backing Field机制,详见下文;
- override :函数/方法重写;
- field :后端变量(backing field关键字);
>类的定义
[权限修饰符][final|open|abstruct] class [<泛型>] 类名 [主构造器权限修饰符][主构造器] [:继承关系]{接口的定义稍微不同:
$类属性定义
$类方法定义
}
interface 接口名 {
$接口属性定义
$接口方法定义
}
>类/接口属性定义
在Kotlin中,没有字段的设计,只有属性。
什么是字段和属性?
- 字段,也被叫做类成员,其一般定义形式为val s:Striing = "字段";
- 属性,带有set()和get()方法;在Java中,对于属性atr,若存在setATR(...)及getATR(),则属性atr为类的一个属性。
让我们看一下一个完整的非泛型类定义:
class DemoClass constructor(name:String) : InfoInterface { // 默认为final类、public类,是非open的、不可被重写刚刚提到了属性与字段的定义,一定有人问前三个属性(id sex name)是不是字段,因为并没有看到相应的get和set方法。答案是否定的。
//默认属性与方法是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")
}
}
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 <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
注意,只有var才能有set()方法。总的来说,后端变量机制就是对get()和set()的重写,field代指的是该变量未更新前的值。
>类/接口方法定义
类的方法的定义:
[权限修饰符] [abstruct|final] fun 方法名 (参数表){如果是重写的方法,需要在前面加上一个 override关键词。
函数体
}
>类的构造
kotlin中,类的构造,分为主构造器和次构造器,有以下几点需要注意区分:
- 主构造器的关键字constructor可以省略不写,但次构造器必须写;
- 次构造器都是public的;
- 主构造器只能存在一个,而次构造器可以有很多个;
- 若存在主构造器,每个次构造器使用时,必须使用:this()继承所有来自主构造器的参数;
- 主构造器接受lambda表达式;
- 若主构造器中某个变量被声明为val/var时,那么这实际上可以被当做一个类属性。
什么是主构造器的用法?看我在文章开头给的那个例子,可以看到在类的第一行给出了主构造器:
class DemoClass constructor(name:String)其参数常用于类属性的初始化(kotlin中定义类时就要保证类属性不为空),详见 >这篇文章<中关于主构造器赋值的内容。
在我给出的例子中,有两个次构造器:
constructor(name:String,sex:String):this(name){他们都继承了来自主构造器的属性name:String,同时,次构造器1与次构造器2在参数上不同,这就使得在构造时,下面的三个语句进行对象实例化,构造时调用是完全不同的内容:
println("构造函数1")
this.sex=sex
}
constructor(name:String,sex:String,a:Int):this(name){
println("构造函数2")
this.sex=sex
age=a
}
val d:DemoClass = DemoClass("shepibaipao","girl")//调用次构造器1但无论如何,在调用次构造器前,一定会调用init{}语句块。具体调用顺序为: 主构造器>init{}语句块>次构造器
val d:DemoClass = DemoClass("shepibaipao","girl",20)//调用次构造器2
val d:DemoClass = DemoClass("shepibaipao")//调用主构造器
由于constructor都是public类型的,想要写出一个单实例类,没法像java一样操作。
解决方法是:在kotlin中,主构造器是可以具有private属性的:
class demo private constructor(name:String){}//此时constructor不可省略
-----------------------------------------------
>Kotlin中的泛型
Kotlin中的泛型函数定义如下:
fun <T> funcA(t:T) {
println(t)
}
Kotlin中的泛型类定义如下:
class A <T>(t:T){
var v = t
}
Koltin中的泛型,是经过类型擦除的,看下面这个例子:
var aInt = A<Int>(100086)这意味着,在编译成字节码时,class A <T> 与 class A <Int> 没有任何差异。
var aString = A<String>("shenpibaipao")
println(aInt.javaClass) // 打印:class Box
println(aString.javaClass) // 打印:class Box
>通配符
先来回顾一下java中的通配符 ?:
- < ? extends Father > 上边界限定通配符,可读不可写,在PECS原则中也被称为生产者。
- < ? super Child > 下边界限定通配符,可写不可读,在PECS原则中也被称为消费者。
>1.类型协变
类型协变用 in 注明消费者,用 out 注明生产者:
class A <in C, out P>{
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
}
}
- 当用 * 代替in的类型C时,表示in Nothing,即完全不可写;
- 当用 * 代替out的类型P时,表示out Any?,即可以任意读出P及其父类。
val o1:A<*,String> = A (7,"yes") // 只可以读出String的任意父类>3.泛型约束
val o2:A<Int,*> = A (7,"yes") // 可以安全写入Int
val o3:A<*,*> = A (7,"yes") // 只可以读出String的任意父类,不能安全写入Int
Kotlin中的泛型约束相当于<? extends Father>:
class D < T : List<T> >{这样,List的所有子类均可被接受。当有多个上界时,可以这么写:
}
class D <T> where T: Comparable<T> , T:List<T>{
}