第一次知道kotlin这个语言是在JakeWharton的这个dex-method-list 项目里,本来这个项目用的是java后来大神又用Kotlin实现了一下,前两天1.0正式版发布了,被各种新闻刷了一下后有必要学习一下。

花了半天看完了《Kotlin for Android》这份文档,之前很多人说像Swift,看完这份文档后才发现它更像C#,比如带问号的可null类型,委托,lambda和的Linq类似,还有 explicit,implicit cast (协变逆变),sealed(密封类),里面还提供了和Linq To Sql 很像的Anko框架,以及getter,setter。

一. Android下的Kotlin

按照惯例我们做一个”Helloworld”项目来尝尝鲜

环境配置

使用Kotlin开发Android应用需要安装Android Studio 和其配套的插件
需要安装Kotlin插件,是使Android Studio支持Kotlin的基础插件,在《Kotlin for Android》一书中说还需要 Kotlin Extensions For Android 插件,这个插件在最近的版本中已经obsolote,这个插件已经被集成到Kotlin插件里,所以不需要安装这个插件了。

enter description here

1. 使用AndroidStudio创建一个新项目

我是用的Android Studio是最新的2.0 Beta5,Gradle是2.10

enter description here

2. 将MainActivity类转换成Kotlin代码

这是一个很给力的特性,可以把Java代码无缝的转换成Kotlin代码。这种转换并不是最优化的,但是在学习Kotlin的时候这个方法很有用,可以对比之前java代码和Kotlin代码语法的转换。
先打开MainActivity类,然后有三种方式进行转换:

  1. 选择code->Convert File To Kotlin File
  2. 选择Help->Find Action或者使用快捷键 在弹出的对话框里输入convert 就会有提示,如下图
  3. 直接使用快捷键shift+alt+command可以直接触发转换工具

enter description here

3. 配置Gradle

代码转换完成后会提示配置Kotlin,可以手动配置也可以使用Android Studio自带的配置工具进行配置。
暂不介绍手动配置,自动配置会在第一次代码转换完成后有个configure提示(如下图),选择ok即可自动更新Gradle配置文件,非常方便
enter description here

配置完成后Gradle配置文件里会自动加上kotlin gradle的插件配置
enter description here

之后选择sync项目,将gradle插件下载下来即可运行项目了。
enter description here

4. 运行项目

Kotlin里有个对Android开发来说很好用的特性就是它剔除了findViewById()方法的调用。

配置gralde

使用这个特性需要在app的build.gradle文件配置里增加下面这个plugin引用。

1
2
3
4
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
//增加这句
apply plugin: 'kotlin-android-extensions'

修改代码

activity_main.xml修改如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="dodola.kotlinandroid1.MainActivity">
<TextView
android:id="@+id/helloText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
</RelativeLayout>

修改MainActivity.kt代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package dodola.kotlinandroid1
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
helloText.text="hello dodola"
}
}

我们需要import进一个特殊的包,import kotlinx.android.synthetic.main.activity_main.* 这个包有个命名规则,后面再介绍。
helloText.text="hello dodola"代码中可以看出不需要调用findViewById,也没有调用setText方法,而是直接使用text来进行赋值,这个使用的是Kotlin的一个叫做Getter Setter技术,这种机制类似C#里的Accessors(访问器),后面再详细介绍,会发现很多特性都可以在C#里找到相应的技术,我之前是做.Net开发的,所以对C#相对敏感,后面会拿C#和Swift和Kotlin做对比,这样也有助于理解Kotlin里的一些技术。

二. Kotlin 介绍

下面介绍一下Kotlin语言的一些细节。内容是从官方文档和其他文章里总结得来的,目标是可以快速的学习Kotlin语言。

1. 基础

1.1 基本类型

Kotlin中没有像Java中的原始基本类型,基本类型都被当做对象形式存在。

数值类型

Kotlin提供的数值类型如下:

Type Bitwidth
Double 64
Float 32
Long 64
Int 32
Short 16
Byte 8

需注意Char在Kotlin中不属于数值类型

字面值常量

1
2
3
4
5
6
整型:123
长整型:123L //需要增加L后缀
16进制:0x0f
二进制:0b00001011
Double123.5123.5e10//默认是Double类型
Float123.5f //Float类型需要增加F或f后缀

注:不支持8进制字面值常量

1
2
3
4
5
val i = 12 // An Int
val iHex = 0x0f // 一个十六进制的Int类型
val l = 3L // A Long
val d = 3.5 // A Double
val f = 3.5F // A Float

显式转换

数值类型不会自动转型,必须要显示的进行类型转换,例子

1
2
3
//Byte -> Int
val b:Byte =1
val i:Int=b.toInt()

比如下面这种情况:

1
2
3
fun pow(a:Int):Double{
return Math.pow(a.toDouble(),2);//此处会出现Kotlin: The integer literal does not conform to the expected type kotlin.Double 的编译错误
}

上面例子pow(double a, double b) 传入的是两个Double类型,但是直接写入Int类型并不会进行隐式转换。需要做一次显示转换

1
2
3
fun pow(a:Int):Double{
return Math.pow(a.toDouble(),2.toDouble());
}

每个数值类型都支持下面转换:

1
2
3
4
5
6
7
toByte(): Byte
toShort(): Short
toInt(): Int
toLong(): Long
toFloat(): Float
toDouble(): Double
toChar(): Char

隐式转换,基于运算符重载技术,例子:

1
val l=1.toLong() + 1 //Long+Int=>Long

字符类型Char

字符类型使用Char表示,不能当成数值使用。

和Java一样,字符类型值是用单引号包起来的1,’\n’,可以显示转换成Int类型

1
2
val c:Char='c'
val i: Int = c.toInt()

布尔值

1
val booltest=true

字符串

使用String声明,Kotlin中有两种样式的String

一种是和Java类似的声明:

1
val s="Hello world"

一种是多行形式的表示

1
2
3
4
val text = """
for (c in "foo")
print(c)
"""

运行结果如下图:
![enter description here][9]

可以像数组那样访问,并且可以被迭代:

1
2
3
4
5
6
7
val s = "Example"
val c = s[2] // 这是一个字符'a'
// 迭代String
val s = "Example"
for(c in s){
print(c)
}

模板

字符串可以包含模板表达式,模板表达式由一个 $ 开始并包含另一个简单的名称:

1
2
3
val i = 10
val s = "i = $i"
//结果为 i = 10

或者是一个带大括号的表达式:+

1
2
3
val s = "abc"
val str = "$s.length is ${s.length}"
//结果为 abc.length is 3

1.2 数组

创建数组

指定长度

1
2
3
val sizeArray=arrayOfNulls<Int>(5)//sizeArray: Integer[5]{null}
val sizeArray1=IntArray(5)//sizeArray1: {0,0,0,0,0}
//同样的类型还有:BooleanArray,ByteArray,IntArray,CharArray,DoubleArray,FloatArray,LongArray,ShortArray,

使用值创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//使用装箱操作
val sizeArray2=arrayOf(1,2,3,4,5)
/**
sizeArray2: Integer[]
0 = {Integer@447} "1"
1 = {Integer@448} "2"
2 = {Integer@449} "3"
3 = {Integer@450} "4"
4 = {Integer@451} "5"
**/
//下面方法创建的数组是未装箱的
val sizeArray3=intArrayOf(1,2,3,4,5)//sizeArray3:int[5] {1,2,3,4,5}
//同类的方法还有
//booeanArrayOf,byteArrayOf,doubleArrayOf,floatArrayOf,intArrayOf,longArrayOf,shortArrayOf

空数组

1
val emptyArray=emptyArray<Int>()//emptyArray: Integer[0]

访问数组

1
2
3
val arr = arrayOf(1, 2, 3)
println(asc[1]) // 1
println(asc.get(1)) // 1

遍历数组

1
2
3
4
5
6
7
8
for(i in arr){
println(i)
}
//1 2 3
for(j in asc.indices){
println(j)
}
//0 1 2

1.3 基本语法

定义包名

1
2
package my.demo //在源文件的最开头
import java.util.

包名不必和文件夹路径一致

定义类

1
2
3
class MainActivity{
//包含一个默认的构造器
}

构造方法

1
2
3
class Datas{
}

定义方法

1
2
3
4
5
6
//第一种形态
fun pow(a:Int):Double{
return Math.pow(a.toDouble(),2.toDouble());
}
//第二种形态,一个表达式函数体和一个可推断类型
fun pow(a:Int)= Math.pow(a.toDouble(),2.toDouble());

定义局部变量

val声明只读变量:

1
2
3
4
5
6
7
8
9
10
11
//-------in kotlin---------
val a:Int=1
val b = 1
val c:Int //声明
c=1
c=2//ERROR: val 只能赋值一次,这里会造成编译错误
//--------in java---------
final int a=1;

使用var声明可变变量:

1
2
3
4
5
6
7
//--------in kotlin-------
var x=5
x+=1
//--------in java ---------
int x=5;

字符串模板

Kotlin允许在字符串中嵌入变量表达式

1
2
3
4
5
6
val name = "Bob"
println("My name is ${name}") //打印"My name is Bob"
val a = 10
val b = 20
println("The sum is ${a+b}") //打印"The sum is 30"

if 表达式

1
2
3
4
5
6
7
8
fun max(a: Int, b: Int): Int {
if (a > b)
return a
else
return b
}
fun max(a: Int, b: Int) = if (a > b) a else b

when表达式

用于替代switch,但功能更强大

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
val age = 17
val typeOfPerson = when(age){
0 -> "New born"
in 1..12 -> "Child"
in 13..19 -> "Teenager"
else -> "Adult"
}
fun cases(obj: Any) {
when (obj) {
1 -> print("one")
"hello" -> print("Greeting")
is Long -> print("Long")
!is Long -> print("Not a string")
else -> print("Unknown")
}
}

注:else是必须的,相当于switch里的default

for 循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun sum(a: IntArray): Int {
var sumValue=0;
for(i in a){
sumValue+=i
}
return sumValue
}
//或者
fun sum(a: IntArray): Int {
var sumValue=0;
for(i in a.indices){
sumValue+=a[i]
}
return sumValue
}

while do ..while 循环

while和do..while循环的语法与Java完全相同。

1
2
3
4
5
6
7
8
9
10
11
fun sum(a: IntArray): Int {
var sumValue=0;
var i=0
while(i<a.size){
sumValue+=a[i++]
}
return sumValue
}

范围表达式

在Kotlin中,范围创建只需要..操作符,例如:

1
2
val r1 = 1..5
//该范围包含数值1,2,3,4,5

如果创建一个降序范围,则需要使用downTo函数

1
2
val r2 = 5 downTo 1
//该范围包含数值5,4,3,2,1

默认范围的步长是1,可使用step方法修改步长:

1
2
val r3 = 5 downTo 1 step 2
//该范围包含数值5,3,1

可使用in操作符检查数值是否在某个范围内

1
2
if (x in 1..y-1)
print("OK")

范围外:

1
2
if (x !in 0..array.lastIndex)
print("Out")

Lambda

1
val sumLambda: (Int, Int) -> Int = {x,y -> x+y}

1.4 包

声明包

1
package foo.bar

如果没有指定包名,那这个文件的内容就从属于没有名字的 “default” 包。

import

1
2
3
4
5
6
import foo.Bar //Bar 现在可以不用条件就可以使用
//或者范围内的所有可用的内容 (包,类,对象,等等):
import foo.*/ /foo 中的所有都可以使用
//如果命名有冲突,我们可以使用 as 关键字局部重命名解决冲突
import foo.Bar // Bar 可以使用
import bar.Bar as bBar // bBar 代表 'bar.Bar'

1.5 控制流

if表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//传统用法
var max = a
if (a < b)
max = b
//带 else
var max: Int
if (a > b)
max = a
else
max = b
//作为表达式
val max = if (a > b) a else b
//if分之可以作为块,最后一个表达式是该块的值
val max = if (a > b){
print("Choose a")
a
}
else{
print("Choose b")
b
}

when 表达式

一般用法,用来替代 switch,需注意的是里面的条件可以重复,如果条件重复的话则按照最先匹配的条件处理

1
2
3
4
5
6
val x=2
when(x){
1->print("1")
2->print("2")
else->print("else")//文档里写else 是mandatory的,但是去掉也可以
}

如果多个条件的处理方式一样,可以写作下面方式:

1
2
3
4
when (x) {
0,1 -> print("x == 0 or x == 1")
else -> print("otherwise")
}

可以用任意表达式作为分支的条件

1
2
3
4
when (x) {
parseInt(s) -> print("s encode x")
else -> print("s does not encode x")
}

可以用 in 或者 !in 检查值是否值在一个集合中:

1
2
3
4
5
6
when (x) {
in 1..10 -> print("x is in the range")
in validNumbers -> print("x is valid")
!in 10..20 -> print("x is outside the range")
else -> print("none of the above")
}

可以使用 is 判断是否是某个类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//官方的这个例子写的很奇怪,感觉为了使用 when 来故意这么写的
val hasPrefix = when (x) {
is String -> x.startsWith("prefix")
else -> false
}
//上面的例子可以直接写成
val hasPrefix=x.startsWith("prefix")
//下面的例子可能更好一些
var a=B()
when(a){
is C->print("A")
is B->print("B")
is A->print("C")
}
open class A{
}
open class B:A(){
}
class C:B(){
}

when 也可以用来代替 if-else, 如果没有提供任何参数则分支的条件就是表达式

1
2
3
4
5
when {
x.isOdd() -> print("x is odd")
x.isEven() -> print("x is even")
else -> print("x is funny")
}

测试例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
fun main(args: Array<String>) {
var x=3
when(x){
1->println("1->1")
2->{
x=3
println("2->${x}")
}
3,4,5->println("3,4,5->")
test()->println("test()->${test()}")
in 7..10->println("6..10->${x}")
}
var a=B()
when(a){
is C->print("A")
is B->print("B")
is A->print("C")
}
}
open class A{
}
open class B:A(){
}
class C:B(){
}
fun test():Int{
println("=====test====")
return 6
}

for

1
2
3
4
5
for (item in collection)
print(item)
for (i in array.indices)
print(array[i])

while

1
//和 Java 一样

返回与跳转

##标签

标签相当于 C语言 中的 label,通过@结尾来表示,比如 abc@

1
2
3
loop@ for(i in 1..100){
//
}

声明了标签之后可以使用 break 或者 continue 进行跳转:

1
2
3
4
5
6
7
loop@ for (i in 1..10) {
for (j in 1..10) {
if (i == 5) {
continue@loop
}
}
}

2. 类

2.1 类

定义类

使用 class 关键字声明类

1
2
class Invoice{
}

如果没有类体可以省略大括号

1
class Empty

主构造方法(primary constructor)

Kotlin 可以包含一个主要构造方法和多个次要构造方法,主构造方法是class header 的一部分:跟在类名的后面(可选类型参数)

1
2
class Person constructor(firstName: String) {
}

如果主构造哦函数没有注解或可见性说明,则 constructor 关键字是可以省略:

1
2
class Person(firstName: String){
}

主构造不能包含任何代码。初始化代码可以放在初始化块(initializer blocks),初始化块前缀使用init关键字:

1
2
3
4
5
class Customer(name: String) {
init {
logger.info("Customer initialized with value ${name}")
}
}

注意,主构造方法的参数可以在初始化块中使用,也可以用在类的属性初始化声明处:

1
2
3
class Customer(name: String) {
val customerKey = name.toUpperCase()
}

属性的声明可以写在主构造方法中:

1
2
class Person(val firstName: String, val lastName: String, var age: Int){
}

主构造方法中的属性可以是valvar
如果构造函数有注解或可见性声明,则 constructor 关键字是不可少的,并且注解应该在前:

1
class Customer public inject constructor (name: String) {...}

次要构造方法(secondary constructors)

使用 constructor 关键字声明次构造方法

1
2
3
4
5
calss Person {
constructor(parent: Person) {
parent.children.add(this)
}
}

如果显示定义了主构造方法则次构造方法需要使用 this 关键字代理构造方法

1
2
3
4
5
class Person(val name: String) {
constructor (name: String, parent: Person) : this(name) {//此处需注意 次构造方法里的 name 和 this(name)的 name 是同一个,就是说必须调用主构造方法
parent.children.add(this)
}
}

如果一个非抽象类没有声明构造方法,它会产生一个默认的构造方法,默认是 public 的,如果不想有公共构造方法,需要显式声明一个private构造方法:

1
2
class DontCreateMe private constructor () {
}

属性和字段

使用var声明可变属性,使用val声明只读属性,属性必须初始化,若定义的时候没有初始化则必须在构造方法里进行初始化

Getters Setters

语法:

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

一般用法:

1
2
3
4
5
6
var allByDefault: Int? // 错误: 需要一个初始化语句, 默认实现了 getter 和 setter 方法
var initialized = 1 // 类型为 Int, 默认实现了 getter 和 setter
val simple: Int? // 类型为 Int ,默认实现 getter ,但必须在构造函数中初始化
val inferredType = 1 // 类型为 Int 类型,默认实现 getter

可以自定义getter和setter方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var stringRepresentation: String
get() = this.toString()
set (value) {
setDataFormString(value) // 格式化字符串,并且将值重新赋值给其他元素
}
///自定义getter方法,
private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
get() {
if (_table == null)
_table = HashMap() //参数类型是推导出来的
return _table ?: throw AssertionError("Set to null by another thread")
}

注:setter方法里的参数可以自定义名称

Backing Fileds

在Kotlin里没有字段,但提供了Backing Fields 机制,备用字段使用field关键字声明,field关键词只能用于属性的访问器

1
2
3
4
5
6
var counter = 0 //在初始化中直接写入备用字段
set(value) {
if (value >= 0)
field = value
}
//如果counter赋值给负数则counter值为0

属性延迟初始化

非空属性必须在定义的时候初始化,kotlin提供了一种可以延迟初始化的方案,使用lateinit关键字描述属性

1
2
3
4
5
6
7
8
9
10
11
class LazyProperty(val initializer: () -> Int) {
var value: Int? = null
val lazy: Int
get() {
if (value == null) {
value = initializer()
}
return value!!
}
}

方法

方法声明

使用关键字fun来声明成员方法:

1
2
3
fun double(x: Int): Int {
}

方法调用

1
2
3
4
5
//通过传统的方法调用函数
val result = double(2)
//通过.调用成员函数
Sample().foo() // 创建Sample类的实例,调用foo方法

中缀符号

成员方法或者扩展方法,只有一个参数使用infix关键词描述

1
2
3
4
5
6
7
/给 Int 定义一个扩展方法
infix fun Int.shl(x: Int): Int {
...
}
println(1 shl 2) //用中缀注解调用扩展函数
输出4

参数

参数基本格式name:type,参数之间使用逗号隔开,每个参数必须指明类型

1
2
3
fun powerOf(number: Int, exponent: Int) {
...
}

默认参数

参数可以设置默认值,参数被忽略时候会使用默认值,这样可以减少重载方法的定义

1
2
3
fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size() ) {
...
}

参数命名

调用参数的时候可以对参数命名

1
2
3
4
5
6
7
8
9
10
11
12
fun reformat(str: String, normalizeCase: Boolean = true,upperCaseFirstLetter: Boolean = true,
divideByCamelHumps: Boolean = false,
wordSeparator: Char = ' ') {
...
}
reformat(str,
normalizeCase = true,
uppercaseFirstLetter = true,
divideByCamelHumps = false,
wordSeparator = '_'
)

注意:命名参数语法不能用在调用Java方法中,因为Java字节码不能确保方法参数命名的不变性.

单表达式函数

当函数只包含单个表达式时,大括号可以省略并在=后面定义函数

1
2
fun double(x:Int):Int=x*2

在编译器可以推断出返回值类型的时候,返回值的类型可以省略:

1
fun double(x: Int) = x * 2

变长参数

函数的变长参数可以用vararg 关键字进行标识

1
2
3
4
5
6
7
8
9
fun vars(vararg v:Int){
for(vt in v){
print(vt)
}
}
//调用方法:
vars(1,2,3,4,5)//输出12345

注意 只有一个参数可以被标注为vararg ,如果可变长度参数不是最后一个参数,则后面的参数需要通过命名参数语法进行传值,如果参数是函数类型,则需要通过lambda传递.

*前缀操作符

调用变长参数的函数时,可以将传入一个同参数类型一致的数组来引用数组内容位可变参数内容

1
2
3
val a = array(1, 2, 3)
val list = asList(-1, 0, *a, 4)

局部方法

kotlin支持在一个函数中包含另一个函数(同JS)

1
2
3
4
5
6
7
8
9
10
fun dfs(graph: Graph) {
fun dfs(current: Vertex, visited: Set<Vertex>) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v, visited)
}
dfs(graph.vertices[0], HashSet())
}

局部方法可以访问外部方法的局部变量(类似java匿名内部类)

1
2
3
4
5
6
7
8
9
10
fun dfs(graph: Graph) {
val visited = HashSet<Vertex>()
fun dfs(current: Vertex) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v)
}
dfs(graph.vertices[0])
}

成员函数

成员函数是定义在一个类或者对象里的

1
2
3
class Sample() {
fun foo() { print("Foo") }
}

泛型函数

具体内容请看 泛型部分

尾递归函数

这个概念在ES6里有同样定义.一种避免栈溢出的方法,当函数被标记为tailRecursive时,编译器会优化递归,用循环代替

1
2
3
4
5
6
7
8
9
10
11
12
13
14
tailRecursive fun findFixPoint(x: Double = 1.0): Double
= if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))
//等效于下面的代码:
private fun findFixPoint(): Double {
var x = 1.0
while (true) {
val y = Math.cos(x)
if (x == y) return y
x = y
}
}

使用 tailrec 修饰符必须在最后一个操作中调用自己。在递归调用代码后面是不允许有其它代码的,并且也不可以在 try/catch/finall 块中进行使用。当前的尾递归只在 JVM 的后端中可以用

接口定义

接口使用 interface 关键字声明,可以包含抽象方法和方法的实现,这点类似于 Java 8的 Default interface

1
2
3
4
5
6
interface MyInterface {
fun bar()
fun foo() {
// optional body
}
}

泛型

接口继承

1
2
3
4
5
class Child : MyInterface {
override fun bar() {
// body
}
}

接口中的属性

可以在接口中定义属性,接口中的属性可以是抽象的也可以实现访问器,但是不能定义 backing field.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface MyInterface {
val property: Int // abstract
val propertyWithImplementation: String
get() = "foo"
fun foo() {
print(property)
}
}
class Child : MyInterface {
override val property: Int = 29
}

抽象类

使用abstract关键字来描述一个抽象类,抽象类里的方法如果不提供实现则需要用abstract关键字来描述抽象方法.抽象的方法默认是open的

1
2
3
4
5
6
7
8
9
10
11
12
abstract class A {
abstract fun f()
}
interface B {
open fun f() { print("B") }
}
class C() : A() , B {
//我们是必须重写 f() 方法
}

密封类(Sealed Classes)

概念比较难理解,和 C#中的概念不一样,详细的内容可以看这篇文档

声明一个密封类,使用sealed修饰类,密封类可以有子类,但是所有的子类都必须要内嵌在密封类中。

sealed 不能修饰 interface ,abstract class(会报 warning,但是不会出现编译错误)

官方例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
//PS 这例子...
sealed class Expr {
class Const(val number: Double) : Expr()
class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
}
fun eval(expr: Expr): Double = when(expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
// the `else` clause is not required because we've covered all the cases
}

###

继承

Kotlin里所有类都继承于父类Any,相当于Object

1
class Example

Kotlin 里类默认都是final的,如果声明的类需要被继承则需要使用open 关键字来描述类

1
2
3
open class Base(p: Ont)
class Derived(p: Int) : Base(p)

如果类没有主构造函数

1
2
3
4
5
6
7
class MyView : View {
constructor(ctx: Context) : super(ctx) {
}
constructor(ctx: Context, attrs: AttributeSet) : super(ctx,attrs) {
}
}

如果父类只存在默认构造方法

1
2
3
4
5
6
7
8
9
10
11
12
open class A{
}
//形式1
class B:A(){
}
//形式2
class C:A{
constructor():super();
}

重写方法

在子类里重写父类的方法需要使用override关键字描述方法

1
2
3
4
5
6
7
8
9
10
open class A{
open fun me(){}
fun me1(){}
}
class B:A(){
//override fun me1(){} Error: 'me' in 'A' is final and cannot be overridden
override fun me(){}
}

如果一个类从它的直接父类继承了同一个成员的多个实现,那么它必须重写这个成员并且提供自己的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
open class A {
open fun f () { print("A") }
fun a() { print("a") }
}
interface B {
fun f() { print("B") } //接口的成员变量默认是 open 的
fun b() { print("b") }
}
class C() : A() , B{
override fun f() {
super<A>.f()//调用 A.f()
super<B>.f()//调用 B.f()
}
}
`

如果类C中没有定义f() 编译器会报如下错误:
Error:(29, 0) Class 'C' must override public open fun f(): kotlin.Unit defined in A because it inherits many implementations of it

作用域修饰符

对象表达式

F#和JS都里有这个类似的定义

对象表达式是一个表达式,可用于创建基于现有的基类型、接口或接口集动态创建的匿名对象类型的新实例。

对象表达式

我们通过下面这样的方式创建继承自某种(或某些)匿名类的对象:

1
2
3
4
5
window.addMouseListener(object: MouseAdapter () {
override fun mouseClicked(e: MouseEvent) {
//...
}
})

如果父类有构造函数,则必须传递相应的构造函数。多个父类可以用逗号隔开,跟在冒号后面

1
2
3
4
5
6
7
8
9
open class A(x: Int) {
public open val y: Int = x
}
interface B { ... }
val ab = object : A(1), B {
override val y = 14
}

有时候我们只是需要一个没有父类的对象,我们可以这样写:

1
2
3
4
5
6
7
8
val adHoc = object {
var x: Int = 0
var y: Int = 0
}
print(adHoc.x + adHoc.y)

像 java 的匿名内部类一样,对象表达式可以访问闭合范围内的变量 (和 java 不一样的是,这些不用声明为 final)

1
2
3
4
5
6
7
8
9
10
11
12
fun countClicks(windows: JComponent) {
var clickCount = 0
var enterCount = 0
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
clickCount++//此处需注意,java里匿名内部类是无法修改方法栈里的变量
}
override fun mouseEntered(e: MouseEvent){
enterCount++
}
})
}

对象声明

声明单例模式:

1
2
3
4
5
6
7
object DataProviderManager {
fun registerDataProvider(provider: Dataprovider) {
//...
}
val allDataProviders : Collection<DataProvider>
get() = //...
}

Extensions

Kotlin可以在不继承父类的情况下对一个类增加一个新的方法,这种方法叫做Extensions.
Kotlin支持扩展方法和扩展属性

扩展方法

为了声明一个扩展函数,我们需要在函数名使用接收者类型作为前缀

1
2
3
4
5
fun MutableList<Int>.swap(x: Int, y: Int) {
val temp = this[x] // this 对应 list
this[x] = this[y]
this[y] = tmp
}

扩展方法中的this关键字对应被扩展对象

1
2
val l = mutableListOf(1, 2, 3)
l.swap(0, 2)

静态解析扩展

扩展方法是静态分发的,不会动态根据类型来进行方法调用.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
open class C
class D: C()
fun C.foo() = "c"
fun D.foo() = "d"
fun printFoo(c: C) {
println(c.foo())
}
printFoo(D())//此处的值为c
/////对比一下多态下的方法调用
open class A{
open fun foo():String{
return "a"
}
}
class B:A() {
override fun foo():String{
return "b"
}
}
fun printClass(a:A){
println(a.foo())
}
printClass(B())//此处输出b

如果扩展的方法和成员方法名称和参数一样,则按照成员方法调用,不会调用扩展方法

1
2
3
4
5
class C {
fun foo() { println("member") }
}
fun C.foo() { println("extension") }

扩展属性

语法结构如下 : val 类名.属性名[:属性类型] get() set()

扩展属性只能通过getter和setter方法来进行定义:

1
2
3
4
5
6
val <T> List<T>.lastIndex: Int
get() = size-1
///
val Foo.bar = 1 //error: initializers are not allowed for extension properties

创建类实例

1
2
val invoice = Invoice()
val customer = Customer("Joe Smith")

注意Kotlin没有new关键字