1.5 Kotlin独特语法
(1)表达式可以作为函数的主体。
比如:fun sum(a: Int, b: Int) = a + b。
(2)Java下的void在Kotlin中变为Unit:
比如:“fun exitNow():Unit {return Unit}”,其实此函数的参数与返回语句都可以删掉。注意,Unit也是一个对象。
(3)不定参数使用关键字vararg定义,在函数内以数组对待。
这一点跟Java一样,看一个例子:
(4)可以为表达式加个标签(标签就是表达式的名字),从而直接跳转到表达式位置执行。
这个功能只用在三个指令上:break、continue、return。
下面的例子是break或continue的位置标签:
本来break应该打破内部循环,加了标签便变成了打破外部循环。
(5)Lambda中的return会导致外部函数的返回,如果仅想从Lambda中返回,要用到标签。
看一个return的例子:
forEach后面是一个Lambda,return出现在Lambda中。猜一下,这个return是从哪里返回的。从Lambda返回?这种写法在Kotlin中是从foo()返回!相当于在foo()中调用return。那么如何从Lambda中返回呢?这样做:
return后加了标签,变成了退出到forEach(),也就相当于从Lambda返回。
注意,Lambda的参数如果只有一个,就可以在定义时省略,然后通过“it”引用。
(6)可以通过主构造方法的参数为类直接定义属性。
这个特性前面已经接触过。其实还可以在参数前加访问性修饰(public、private等关键字),例如:
(7)具体类默认是final的,不能被继承,如果需要被继承,就要用open修饰。
这点与Java相反,Java类默认是可以被继承的。抽象类默认肯定是open,因为抽象类必须被继承才有存在的意义。
(8)在类内部定义的类有两种:嵌套类和内部类。
嵌套类相当于Java中的静态内部类,不能使用外部类的this。内部类必须以“inner class”定义,相当于Java中的私有非静态内部类,可以访问外部类的this。跟Java一样,支持匿名内部类。
(9)所有类都从Any派生。
Any就是Java中的对象(Object)。
(10)方法默认是final的,所以要想被子类覆盖(Override),需用open修饰。
这个设计与类一致。
(11)要想让类的属性或方法属于类型而不是实例,应在“companion object(伴随对象/伴生对象)”代码块中定义。
看如下示例代码:
方法create()就是MyClass的一个类型方法(也就是静态方法),可以这样调用:
val instance = MyClass.create()
(12)用object创建内部匿名类。
看这个例子:
这是Android中常见的语法,设置事件侦听器,这个侦听器类必须从MouseAdapter类派生。语法看起来与Java很相似,最大的差别就是多了“object:”,实际上它表示定义一个“对象表达式”,内部匿名类实际上是把类的定义与创建实例合在一起了,因为最终创建的是一个实例,所以用object来标识。
(13)语法上直接支持单例模式。
要创建一个全局唯一静态对象(也就是单例),比Java下简单得多。其做法是用关键字object而不是class来定义一个类。这相当于先定义一个类,然后用它创建对象,并想办法保证此对象在进程中是唯一的。下面是创建单例的示例:
下面是使用它的代码,可以直接通过类名调用方法,而不用先创建实例,因为它本身就是一个实例:
DataProviderManager.registerDataProvider(...)
(14)可以在一个类中为另一个类定义扩展:
“扩展”就是为已存在的类添加新的方法或属性,而那个类的原有代码不会受影响。这种语法有时看起来很奇怪,因为随时可以干这种事,比如在某个类内部为另一个类添加方法:
我们先定义一个类A,它有一个方法methodOfA();再定义类B,其中第二个方法A.method2OfA()看起来是在B中定义的,但实际上是A的方法,因为其最前面指定了A的类名。
注意扩展的影响范围,在上面的例子中,A的扩展方法只能在B中调用,B之外是不能用的,如果要在更大的范围内使用,可以在类外定义A的扩展。
(15)可定义只用于包含数据的类。
这种类叫作“数据类”,以“data class”修饰。注意,并不是说这种类不能包含方法,它的本质与普通类没有区别,“data”修饰符起的作用是:为类添加了几个方法,它们的功能分别是:比较值是否相等(equals())、求取Hash码(hashCode())、转字符串(toString())、复制数据的方法(copy())等。当然还可以自由添加方法,但是由于我们的设计目标就是把它当作数据容器,所以尽量不要为它添加包含业务逻辑的方法。
例如:
data class User(val name: String, val age: Int)
(16)支持封闭类。
封闭类是什么?密封类的子类数量有限,这一点不同于Enum Class(Enum Class的实例数有限)。
封闭类要求子类必须在其所在的文件中创建。如此一来,其他人就因为不能修改此类的源码而无法创建此类的新子类,于是达到了“封闭”的目的。看下面这个例子:
sealed class Expr data class Const(val number: Double) : Expr() data class Sum(val e1: Expr, val e2: Expr) : Expr() object NotANumber : Expr()
定义了一个封闭类Expr,从它派生了三个类,其中最后一个类是一个单例。这些代码必须在同一个文件中。
(17)定义函数类型时还可以指定调用此函数的对象。
先看一个例子:
val sum: Int.(Int) -> Int = {other -> this.plus(other)}
sum是一个函数常量,其类型是“Int.(Int) -> Int”,参数是Int,返回类型是Int,但是前面多了一个“Int.”,表示调用此函数时必须通过Int类型的实例(叫作目标对象的类型),比如“44.sum(33)”。注意:大括号内是Lambda,other是33,this指向44(this是可以省略的)。
实际上sum并不是Int的方法,但是这样定义之后就成了Int的方法,跟扩展的效果很相似。
再看一个例子:
定义了一个类HTML,并在类外定义了函数html()。html()有唯一的参数init(是一个函数),在它的类型定义中指定了必须通过HTML的实例调用init。当然,在html()的实现中也是这样做的。
最后是对html()函数的调用,由于它只有一个参数,因此小括号被省略。其参数是一个Lambda,Lambda中调用了HTML实例的方法body()。根据html()函数的定义可以推断出Lambda中所调用的方法所属的类,所以可以做到如此简洁的函数调用语法。这种语法的一个主要应用就是实现“类型安全的构建器”,看下面的例子:
以上是常用特性中比较特殊的地方,其余方面与Java差不多。