[关闭]
@ltlovezh 2016-04-26T20:08:51.000000Z 字数 8992 阅读 1138

Kotlin语法2

Kotlin

在kotlin中,类的定义包含类名、类头和类体。

  1. class 类名 修饰符[public,internal,protected,private] constructor(参数:参数类型){
  2. init{
  3. initializer blocks
  4. }
  5. //二级构造函数,调用了主构造函数
  6. constructor(参数:参数类型) : this(参数) {
  7. parent.children.add(this)
  8. }
  9. 类体
  10. }
  11. 其中,类头和类体都是可选的。

kotlin中的Class有两种构造函数:主构造函数和二级构造函数。上述constructor(参数:参数类型)就指定了主构造函数,它是类头的一部分。

主构造函数不能包含任何代码。任何初始化操作只能在initializer blocks中进行(可以直接引用主构造函数中的参数值)。

二级构造函数通过constructor关键字进行定义,所有的二级构造函数必须直接或者间接的调用主构造函数(通过this关键字)。

我们在java代码中,经常在类中定义多个属性变量,然后在构造函数中分别初始化它们。对于这种行为,kotlin提供了更加简单的方式:

  1. class User(val name: String = 默认参数值, var age: Int = 默认参数值) {
  2. // ...
  3. }

上述User类定义了两个成员变量,并在主构造函数中完成了初始化。

类的组成

在kotlin中,一个Class可以包含以下几类成员:

  1. Constructors and initializer blocks
    构造函数和初始化代码块,上面已经介绍过了。

  2. Properties
    成员属性,下面会着重介绍。

  3. Functions
    成员函数,和类外部的顶级函数没啥区别,只是范围限制在Class内部了。

  4. Nested and Inner Classes
    在Kotlin中,内部类默认是static的,即不会持有对外部类的引用。但是被inner修饰的内部类是非static的,即会持有对外部类的引用。

  5. Object Declarations
    对象表达式和对象声明,下面会着重介绍。

Properties (属性)

类属性就相当于java中的实例成员属性,它的完整定义如下所示:

  1. var <propertyName>: <PropertyType> [= <property_initializer>]
  2. [<getter>]
  3. [<setter>]

上述的<getter><setter>分别在读取和设置属性时被调用(编译器会默认生成)。val属性只有<getter>函数。
当然,我们可以定制属性的set和get函数,就像定义普通的函数一样。

  1. class Leontli() {
  2. var isEmpty: Boolean = false
  3. get() {
  4. return field
  5. }
  6. set(value) {
  7. field = true
  8. }
  9. }

其中,filed称为backing field,由编译器编译生成,仅仅可在get()和set(value)方法中被访问(若只是重写了get和set方法,而没有主动访问field字段,那么编译器也不会主动生成该字段)。

属性的延迟初始化

在kotlin中,一般情况下,类中的成员属性,必须直接初始化或者在构造函数中初始化。但是有时这是非常不方便的。比如:我们在Activity中经常需要获取各个View的引用,然后进行各种操作。在Java中一般是先定义View变量,然后在onCreate方法中对他们初始化,如下所示:

  1. public class MainActivity extends Activity{
  2. Bubtton mButton;
  3. public void onCreate(Bundle savedInstanceState){
  4. super.OnCreate(savedInstanceState);
  5. setContentView(R.layout.main);
  6. mButton = (Button) findViewById(R.id.ok);
  7. aTextView.setText("Click Me");
  8. // ...
  9. }
  10. }

但是在kotlin中,这样是行不通的,因为非空属性必须被初始化一个非空值。

  1. class MainActivity : Activity() {
  2. var mButton : Button //error : Property must be initialized or be abstract
  3. }

但是如果使用可Null属性,那么后续的操作也会很繁琐,同时也失去了kotlin提供的不可Null属性的便利,可能会导致NPE的频现(又回到了java的老路)。

  1. class MainActivity : Activity() {
  2. var mButton : Button? = null
  3. override fun onCreate(savedInstanceState: Bundle?) {
  4. super.onCreate(savedInstanceState)
  5. setContentView(R.layout.main)
  6. mButton = findViewById(R.id.ok) as Button
  7. //因为mButton可Null,所以只能通过下面的方式来赋值
  8. mButton!!.text = "Click Me"
  9. mButton?.text = "Click Me"
  10. }
  11. }

明明知道mButton不会为Null,但是每次调用都要添加!! or ?操作符,岂不是很不爽!

针对这种情况,kotlin也给出了可选的解决方案Late-Initialized PropertiesDelegated Properties。每种方案各有其使用场景,下面一一介绍。

Late-Initialized Properties(延迟初始化属性)

通过lateinit关键字可以定义延迟初始化属性。例如:

  1. class MainActivity : Activity() {
  2. lateinit var mButton : Button
  3. override fun onCreate(savedInstanceState: Bundle?) {
  4. super.onCreate(savedInstanceState)
  5. setContentView(R.layout.main)
  6. mButton = findViewById(R.id.ok) as Button
  7. //可直接调用
  8. mButton.text = "Click Me"
  9. }
  10. }

这样就可以满足我们的需求了(和java的写法很类似)。但是使用这种属性存在一些条件:
1. 只适用于可变属性,即var修饰的属性
2. 只适用于Class body内的属性,不能是在主构造函数中声明的属性。
3. 延迟初始化属性不能包含自定义的set和get方法
4. 属性不可为Null,且属性类型不能是原始类型。

毕竟存在这些限制,也就意味着在特殊情况下,无法使用延迟初始化属性。(强迫症...)

Delegated Properties (委托属性 or 代理属性)

Delegated Properties为属性的初始化提供了另外一种方案,即把属性委托给一个表达式。其语法如下所示:

  1. val/var <property name>: <Type> by <expression>

by后面的表达式称为属性代理(delegate)。针对这个delegate,存在一些限制规则:

  1. operator fun getValue(receiver: Any?, metadata: KProperty<*>): 被代理属性的类型 {
  2. //返回被代理属性的值
  3. }

其中,关键字operator是必不可少的。参数receiver表示持有被代理属性的类对象,其参数类型必须是被代理属性持有者的类型或者其父类型。metadata表示被代理属性的元数据,其参数类型必须是KProperty<*>或者其父类型。

  1. operator fun setValue(receiver: Any?, metadata: KProperty<*>, value: 被代理属性的类型) {
  2. //设置被代理属性的值
  3. }

其中,receiver和metadata的含义和getValue方法类似,value则表示对被代理属性设置的新值。

这两个函数既可以是类成员函数,也可以是扩展函数。其中,对被代理属性的访问会被委托给delegate的getValue函数,对被代理属性的赋值会被委托给delegate的setValue函数。下面来看一个例子:

  1. //代理类
  2. class Delegate<T>(arg: T) {
  3. var mSave: T = arg //存储被代理属性的值
  4. operator fun getValue(receiver: Any?, metadata: KProperty<*>): T {
  5. println("$receiver, thank you for delegating '${metadata.name}' to me!")
  6. return mSave
  7. }
  8. operator fun setValue(receiver: Any?, metadata: KProperty<*>, value: T) {
  9. println("$value has been assigned to '${metadata.name} in $receiver.'")
  10. mSave = value
  11. }
  12. }
  13. //被代理属性的持有者
  14. class Example {
  15. //把属性p委托给Delegate类对象
  16. var p: String by Delegate<String>("init")
  17. }
  18. 调用
  19. var example = Example();
  20. println(example.p)
  21. example.p = "Hello"
  22. println(example.p)
  23. 输出:
  24. Example@74f2794a, thank you for delegating 'p' to me!
  25. init
  26. Hello has been assigned to 'p in Example@74f2794a.'
  27. Example@74f2794a, thank you for delegating 'p' to me!
  28. Hello

从上述代码中可以看出,访问属性p时,调用了getValue方法,接收者就是Example对象,而通过元数据metadata,则可以访问被代理属性的名称等属性。而对属性p赋值时,调用了setValue方法,value则表示将要被赋值的新值。

上面代码中,我们用mSave属性存储了被代理对象的值,这是因为我们不能在getValue和setValue函数中访问被代理属性p,道理很简单,因为访问被代理属性又会触发getValue和setValue函数,导致死循环。

当然,上面仅仅是一个简单的例子,通过属性代理(delegate),我们可以实现任何对属性的取值和赋值操作。

针对委托属性,kotlin类库提供了一些现成的属性代理(delegate)。这里简单介绍下其中两种。

Lazy

lazy其实是一个接收函数字面量,且返回Lazy对象的高阶函数。Lazy对象是一个delegate,实现了对被代理属性的懒加载(Lazy对象的getValue函数就是通过扩展函数形式来提供的)。只有属性在第一次被访问时,才会执行函数字面量对属性进行赋值。且后续属性值不可变更,即懒加载属性必须是不可变的(val修饰)。看下例子:

  1. val lazyValue: String by lazy {
  2. println("computed!")
  3. "Hello"
  4. }
  5. //调用
  6. println(lazyValue)
  7. println(lazyValue)
  8. //输出
  9. computed!
  10. Hello
  11. Hello
Observable

Delegates是一个Standard property delegates。它提供了observablevetoable函数,分别用来生成不同的属性代理。它们的函数签名如下所示:

  1. //接收一个初始值和一个函数(包含属性、旧值和新值共3个参数),返回一个ObservableProperty属性代理对象
  2. public inline fun <T> observable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit) : ReadWriteProperty<Any?, T> = object : ObservableProperty<T>(initialValue) {
  3. //重写了afterChange函数,在被代理属性被修改后,才会被调用。
  4. override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue)
  5. }
  6. //接收一个初始值和一个函数(包含属性、旧值和新值共3个参数),返回一个ObservableProperty属性代理对象
  7. public inline fun <T> vetoable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Boolean) : ReadWriteProperty<Any?, T> = object : ObservableProperty<T>(initialValue) {
  8. //重写了beforeChange函数,在被代理属性被修改前,会被调用,若onChange返回了false,则赋值会被终止掉。
  9. override fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean = onChange(property, oldValue, newValue)
  10. }

前者observable接收的参数函数会在赋值之后被调用,后者vetoable接收的参数函数会在赋值之前被调用,并且有权中断赋值。我们可以想一下,这种控制逻辑应该是在ObservableProperty属性代理的setValue方法中实现的。果不其然:

  1. public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
  2. val oldValue = this.value
  3. //真正赋值之前的调用,若beforeChange返回了false,则中断赋值
  4. if (!beforeChange(property, oldValue, value)) {
  5. return
  6. }
  7. this.value = value //真正赋值
  8. //赋值之后的调用,若beforeChange返回了false,则不会走到这里
  9. afterChange(property, oldValue, value)
  10. }

但是总感觉有些遗憾,为什么没有函数既可以在赋值前被调用,又可以在赋值后被调用那?其实这也是可以理解的,若是beforeChange函数返回了false,那么afterChange便不会被调用;若是beforeChange函数返回了true,那么afterChange的参数和前者完全是一样的,就没啥意义了。

但是,出于练习目的,我们完全可以实现这种函数,基本思路就是为Delegates类提供扩展函数,返回同时实现了beforeChangeafterChange的ObservableProperty属性代理对象。

  1. //initialValue是初始值,onBefore在赋值前被调用,onAfter在赋值后被调用
  2. inline fun <T> Delegates.all(initialValue: T,
  3. crossinline onBefore: (property: KProperty<*>, oldValue: T, newValue: T) -> Boolean,
  4. crossinline onAfter: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit):
  5. ReadWriteProperty<Any?, T> = object : ObservableProperty<T>(initialValue) {
  6. //重写了两个函数
  7. override fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean = onBefore(property, oldValue, newValue)
  8. override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onAfter(property, oldValue, newValue)
  9. }

上面我们通过扩展函数all,综合了observablevetoable的功能。传入的参数onBefore会在赋值前被调用,onAfter会在赋值后被调用(赋值不被中断的前提下)。下面看下具体使用:

  1. class Example {
  2. //isPass也是被代理属性,在使用前必须先赋值
  3. var isPass: Boolean by Delegates.notNull<Boolean>()
  4. var name: String by Delegates.all("<no name>", { prop, old, new ->
  5. println("before $old -> $new")
  6. isPass //闭包哈
  7. }) {
  8. prop, old, new ->
  9. println("after $old -> $new")
  10. }
  11. }
  12. //调用
  13. var example = Example();
  14. example.isPass = true
  15. println(example.name)
  16. example.name = "leon"
  17. println(example.name)
  18. example.isPass = false
  19. example.name = "android"
  20. println(example.name)
  21. //输出:
  22. <no name>
  23. before <no name> -> leon
  24. after <no name> -> leon
  25. leon
  26. before leon -> android
  27. //这里赋值被阻断后,after没有执行哈
  28. leon

Object Expressions and Declarations (对象表达式和对象声明)

对象表达式和对象声明都是用来创建对象的。其中,前者用来创建匿名类对象,可以直接赋值给一个变量;后者用来创建单例对象,不可直接赋值给一个变量。

除此之外,两者还有一个重要的差异:Object Expressions在定义的地方被立即执行;而Object Declarations则会延迟到第一次被访问的才会执行。

Object Expressions

对象表达式的格式如下所示:

  1. object : 父类 , 接口1 , 接口2 , ...{
  2. //实现的接口方法
  3. }

假如我们要为Button设置Click监听器:

  1. var mClick = object : View.OnClickListener{
  2. override fun onClick(v: View?) {
  3. Toast.makeText(this@MainActivity, "Click Me", Toast.LENGTH_LONG).show()
  4. }
  5. }
  6. mButton.setOnClickListener(mClick)

当然还有更直接的方法:

  1. mButton.setOnClickListener{
  2. Toast.makeText(this@MainActivity, "Click Me", Toast.LENGTH_LONG).show()
  3. }

Object Declarations

对象声明的格式如下所示:

  1. object 单例对象名 父类 , 接口1 , 接口2 , ...{
  2. //实现自定义方法和接口方法
  3. }

后续就可以通过单例对象名来访问对象方法了。但是不可以在函数内进行对象声明。

Companion Objects

在Kotlin中,没有静态方法,而是推荐使用顶级函数来代替。但是我们可以通过Companion Objects来实现类似静态方法的方法。

具体方式是在类内部的Object Declarations前面加上Companion关键字。这样我们就可以像使用类静态方法一样,直接通过类名调用Companion Objects内的方法了。看个例子:

  1. class OurClass {
  2. var a = 1
  3. companion object Factory {
  4. fun create(): OurClass {
  5. //error,无法访问外部类的属性。从这里可以看出Factory更像是静态内部类,不过有待确认?
  6. println("a = $a")
  7. return OurClass()
  8. }
  9. }
  10. }
  11. //调用
  12. println(OurClass.create())
  13. println(OurClass.Factory.create())
  14. println(OurClass.Factory)
  15. //输出
  16. OurClass@3e99f610
  17. OurClass@6de9b48b
  18. OurClass$Factory@a4c4a0d

从上面的输出可以看出,create方法不是真正的static方法,而是内部类Factory的实例方法。因此,Factory也可以像普通的Object Declarations一样,实现接口。

类继承

在Kotlin中,所有的Kotlin类都有一个共同的父类Any(不是java.lang.Object)。
默认情况下,一个Kotlin类不可以被继承,成员函数也不可以被重写(这点和java不同,倒和C++很像)即默认都是final的。除非用关键词open明确指明。同时,在一个final Class内部,是不允许有open成员的。看一个官网的例子:

  1. open class Base {
  2. open fun v() {}
  3. fun nv() {}
  4. }
  5. class Derived() : Base() {
  6. //因为和父类v方法签名相同,所以必须明确指明是重写父类方法,否则编译不过
  7. override fun v() {}
  8. //因为和父类nv方法签名相同,所以要么修改方法签名,要么把父类nv方法声明为open,且这里明确指明是重写父类方法。
  9. //fun nv() {}
  10. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注