kotlin 的基本使用

基础

1. 对象的定义

kotlin 在定义对象时,涉及到两个关键字:val 和 var。

val 定义的是常量(只能一次赋值),var 定义的是变量(可多次赋值)。

1
2
3
4
5
6
7
8
val i : Int = 1     //立即赋值
val j = 2 //可省略对象类型,自动推断为Int类型(通常写法)

val k : Int //先不赋值,此时要声明类型
k = 3 //现在赋值

var v = 5
v = 6 //可以改变var变量的值

2. 方法的定义

方法要加上 fun 关键字来标识,返回类型写在后面,例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 有返回值类型的函数
*/
fun add(a : Int, b : Int) : Int{
return a + b
}

/**
* 无返回值的函数(或者说函数返回无意义的值:Unit。Unit可以省略)
*/
fun printSome(a : Int, b : Int){
println("sum of $a + $b is ${a + b}") //可以用($ + 参数)表示该参数的值
}

3. 循环

循环也是分为 while 和 for,while 循环没什么特别的。主要看下 for 循环:

  • 循环集合:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    val items = listOf("car", "bus", "plain")
    //循环元素
    for (item in items) {
    println(item)
    }
    //循环元素索引
    for(index in items.indices) {
    println("item at $index is ${items[index]}")
    }
  • 循环数字

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    //遍历1到10(输出1 2 3 4 5 6 7 8 9 10 )
    for (index in 1..10) {
    print("$index ")
    }
    println()

    //倒序遍历1到10(输出10 9 8 7 6 5 4 3 2 1 )
    for (index in 10 downTo 1) {
    print("$index ")
    }
    println()

    //以2为间隔输出(输出1 3 5 7 9 )
    for (index in 1..10 step 2) {
    print("$index ")
    }
    println()

    //输出不包含末尾元素(输出1 2 3 4 5 6 7 8 9 )
    for (index in 1 until 10) {
    print("$index ")
    }

when(相当于 switch)

下面来看一下 when 的用法,直接上例子:

1
2
3
4
5
6
7
8
9
10
11
fun printWhen(x : Any) {
// when相当于C和java中的switch
when (x) {
0 -> println("x is 0") //可以是普通的单个值
1, 2 -> println("x is 1 or 2") //可以匹配多个值
parseInt("3") -> println("x is 3") //可以是表达式
in 10..20 -> println("x is between 10 and 20") //可以是区间
is String -> println("x is String") //可以用is 判断
else -> println("x is unknown") //相当于default
}
}

类的修饰符

  1. 类属性修饰符(标示类本身特性)
  • abstract // 抽象类
  • final // 类不可继承,默认属性
  • open // 类可继承
  • enum // 枚举类
  • annotation // 注解类
  1. 访问权限修饰符
  • private // 仅在同一个类可见
  • protected // 在同一个类或子类可见
  • internal // 同一个模块可见
  • public // 所以调用的地方都可见

构造函数

类的构造函数分为两种:主构造函数次构造函数,主构造函数只有一个,而次构造函数可以有多个

  1. 主构造函数

主构造函数是类头的一部分,位于类名称后面:

1
2
3
4
5
6
class Person constructor(
name : String,
age : Int
) {
// ...
}

其中 name 和 age 是构造函数的参数。

如果主构造函数没有任何注解,也没有任何可见度修饰符,那么可以省略 constructor 关键字;同时也可以为参数指定默认值,当没有显示传入实参时就会使用该默认值。具体如下:

1
2
3
4
5
6
class Person (
name : String = "小明",
age : Int = 18
) {
// ...
}

类头中只能指定主构造函数的传入参数,如果要对这些参数进行初始化操作,要在类中的 init 块执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person (
name : String = "小明",
age : Int = 18
) {
var name : String
var age : Int

// 主构造函数初始化块
init {
this.name = name
this.age = age
println("调用主构造函数")
}

// ...
}

  1. 次构造函数

次构造函数必须加上 constructor 关键字,一个类可以有多个次构造函数,传入参数不同即可。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Person (
name : String = "小明",
age : Int = 18
) {
var name : String
var age : Int

// 主构造函数初始化块
init {
this.name = name
this.age = age
println("调用主构造函数")
}

// 次构造函数
constructor(age: Int) : this("default name", age) {
println("调用次构造函数")
}

// ...
}

其中 this 表示调用主构造函数来进行初始化。这是必须的,因为:

如果类有主构造函数,那么每个次构造函数都要直接或间接地通过另一个次构造函数来调用主构造函数来初始化。

例如运行上述 Person 类:

1
2
3
4
fun main(args: Array<String>) {
val person = Person(10) // 调用次构造函数
person.print()
}

它的运行结果如下:

1
2
3
调用主构造函数
调用次构造函数
name = default name, age = 10

可以看到,这里在调用次构造函数时,先后运行了主构造函数和次构造函数。

继承 & 重写

  • 单继承,只能有一个父类
  • 使用 “:” 继承,默认情况下类不允许继承,想要让类可继承,需使用 open 关键字标识。例如下面 Man 继承 Person:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // Person 类,用 open 标识,标识为可被继承
    open class Person (
    name : String = "小明",
    age : Int = 18
    ) {
    // ...
    }

    // Man 类,继承 Person 类
    class Man : Person() {
    // ...
    }
  • 在 kotlin 中,方法默认是不能重写的,若子类想要重写父类的方法,父类的方法需加上 open 关键字,然后子类重写的方法前加上 override 关键字:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    open class Person (
    name : String = "小明",
    age : Int = 18
    ) {
    // ...

    // 父类的方法:加上 open 关键字标识,子类才可重写该方法
    open fun print() {
    println("name = $name, age = $age")
    }
    }

    class Man : Person() {
    // 子类重写该方法
    override fun print() {
    println("Man: name = $name, age = $age")
    }
    }

getter & setter

在 kotlin 中,get 和 set 有默认的实现,如果不需要对 get 和 set 做特殊的处理,就不用重写。需要注意的是,val 变量没有 set 方法,也不予许重写 set 方法。

下面来看下重写 get 和 set 的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
open class Person (
name : String = "小明",
age : Int = 18
) {
// 重写 name 的 get,在获取 name 的时候,获取到的是其值的大写形式
var name : String
get() = field.toUpperCase()
// 重写 age 的 set,在设置 age 的时候,若传入的值大于 10,则对 10 取余
var age : Int = 18
set(value) {
if (value > 10) {
field = value % 10
}
}

open fun print() {
println("name = $name, age = $age")
}
}

创建一个 Person 实例,并打印其 name 和 age:

1
2
3
4
fun main(args: Array<String>) {
val person = Person("lily", 16)
person.print()
}

打印结果如下:

1
name = LILY, age = 6

特殊类

嵌套类和内部类

嵌套类和内部类都是在一个类的里面创建另一个类,通过例子来看下它们的区别,先看嵌套类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Outer {
val i = 10

// 嵌套类
class Nested {
fun m1() {
println("调用嵌套类的 m1 方法,嵌套类不能引用外部类的成员变量")
}
}
}

fun main(args: Array<String>) {
val nestClass = Outer.Nested()
nestClass.m1() // 输出:调用嵌套类的 m1 方法,嵌套类不能引用外部类的成员变量
}

嵌套类不能引用外部类的成员变量,例如这里的嵌套类不能引用外部类的 i 变量。再看下内部类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Outer {
val i = 10
// 内部类
inner class Inner {
fun m2() {
println("内部类可以引用外部类的成员变量,例如引用 i, i = $i")
}
}
}

fun main(args: Array<String>) {
val innerClass = Outer().Inner()
innerClass.m2() // 输出:内部类可以引用外部类的成员变量,例如引用 i, i = 10
}

内部类需要用关键字 inner 标识,和嵌套类不同,它可以引用外部类的成员变量。

抽象类和接口

抽象类用关键字 abstract 标识,不可以实例化,不需要用 open 关键字标识即可被继承。来看下例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 抽象类
abstract class Abs{
abstract fun m1()
}

// 实现类
class Impl : Abs() {
override fun m1() {
println("重写了抽象类的 m1 方法")
}
}

fun main(args: Array<String>) {
val impl = Impl()
impl.m1() // 输出:重写了抽象类的 m1 方法
}

接口用关键字 interface 标识,和 java 不同,在 kotlin 中,接口可以有具体方法,实现类可以不重写这些方法。下面看示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
interface A {
fun m1() {
println("接口 A 中的具体方法 m1")
}

fun m2()
}

class AImpl : A {
override fun m2() {
println("实现接口 A 的 m2 方法")
}
}

fun main(args: Array<String>) {
val a : A = AImpl()
a.m1()
a.m2()
}

运行结果:

1
2
接口 A 中的具体方法 m1
实现接口 A 的 m2 方法

匿名内部类

匿名内部类常用于接口回调,下面就以此为例,看下 kotlin 的匿名内部类实现:

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
// 持有接口实例的类
class B {
private lateinit var inter : TestInter

fun setC(inter : TestInter) {
this.inter = inter
}

fun b1() {
inter.m1("类 B 回调的信息")
}
}

// 接口
interface TestInter {
fun m1(str : String)
}

fun main(args: Array<String>) {
val b = B()
// 传入一个匿名内部类(接口实例)给 B
b.setC(object : TestInter{
override fun m1(str: String) {
println("打印回调信息:$str")
}
})
// 调用 B 的方法
b.b1()
}

程序运行结果如下:

1
打印回调信息:类 B 回调的信息

其他

空安全

  1. 在 kotlin 中,引用可以分为两种:可以容纳 null(可空引用)和不可容纳 null(非空引用)。例如,String 类型变量不能容纳 null,若要允许 String 为空,可以在声明变量的时候,在类型后面加一个问号“?”。例如:

    1
    2
    3
    val str : String?
    str = null
    println(str) // 输出:null
  2. 安全调用操作符,它的使用方法是在对象调用方法时,在对象后加一个问号“?”,表示要对象不为 null 时才继续调用。例如:

    1
    2
    3
    4
    5
    val str : String?
    str = null

    val len = str?.length
    println("len = $len") // 输出:len = null

参考

-------------    本文到此结束  感谢您的阅读    -------------
0%