Kotlin

简介

  • Kotlin (科特林)是一个用于现代多平台应用的静态编程语言,由 JetBrains 开发,2016年推出 Kotlin V1.0。
  • Kotlin可以编译成Java字节码,也可以编译成JavaScript,方便在没有JVM的设备上运行。
  • 在Google I/O 2017中,Google 宣布 Kotlin 成为 Android 官方开发语言。
package start
fun main(args: Array<String>) {   
    println("Hello World!") 
}

基础语法

包声明

  • kotlin源文件不需要相匹配的目录和包,源文件可以放在任何文件目录。

定义常量与变量

// 可变变量定义:var 关键字
var <标识符> : <类型> = <初始化值>
// 不可变变量定义:val 关键字,只能赋值一次的变量(类似Javafinal修饰的变量)
val <标识符> : <类型> = <初始化值>
// 常量与变量都可以没有初始化值,但是在引用前必须初始化
  • 编译器支持自动类型判断,即声明时可以不指定类型,由编译器判断。

注释

// 这是一个单行注释
/* 这是一个多行的
 块注释。 */

? 可以为空(null)

  • Kotlin的空安全设计对于声明可为空的参数,在使用时要进行空判断处理,有两种处理方式,字段后加!!像Java一样抛出空异常。
  • 另一种字段后加?可不做处理返回值为 null或配合?:做空判断处理
//类型后面加?表示可为空
var age: String? = "23" 
//抛出空指针异常
val ages = age!!.toInt()
//不做处理返回 null
val ages1 = age?.toInt()
//age为空返回-1
val ages2 = age?.toInt() ?: -1
// 当一个引用可能为 null 值时, 对应的类型声明必须明确地标记为可为 null。

类型检测

  • is 运算符检测一个表达式是否某类型的一个实例(类似于Java中的instanceof关键字)。
fun getStringLength(obj: Any): Int? {
    if (obj is String) {
	// 做过类型判断以后,obj会被系统自动转换为String类型
	return obj.length 
    }

Int to String, String to Int

var a = "13"
var b = 13
println(b.toString())
println(a.toInt())

比较

  • 在 Kotlin 中,三个等号 = 表示比较对象地址,两个 == 表示比较两个值大小。

多行字符串

  • String 可以通过 trimMargin() 方法来删除多余的空白。
fun main(args: Array<String>) {
    val text = """
    |多行字符串
    |多行字符串
    """.trimMargin()
    println(text)    // 前置空格删除了
}
  • 默认 用作边界前缀,但你可以选择其他字符并作为参数传入,比如 trimMargin(“>”)。

字符串模板

var str = "World"
println("Hello${str}, and str can be ${str.replace("World","Duck")}")
  • 输出$
val price = """
${'$'}9.99
"""
println(price)  // 求值结果为 $9.99

函数

函数定义

fun 函数名(参数名:参数类型):返回值类型{
}

无返回值的函数(类似Java中的void):

fun printSum(a: Int, b: Int): Unit { 
    print(a + b)
}
  • 如果是返回 Unit类型,则可以省略(对于public方法也是这样):
public fun printSum(a: Int, b: Int) { 
    print(a + b)
}

可变长参数函数

  • 函数的变长参数可以用 vararg 关键字进行标识
fun vars(vararg v:Int){
    for(vt in v){
	print(vt)
    }
}

lambda(匿名函数)

fun main(args: Array<String>) {
    val sumLambda: (Int, Int) -> Int = {x,y -> x+y}
    println(sumLambda(1,2))  // 输出 3
}

函数的便捷写法

var i = {a:Int,b:Int -> a+b}
var j:(Int,Int) -> Int = {x,y -> x+y}
    println(i(2,3))
}

fun add(a: Int, b: Int): Int {
    return a + b
}

fun add2(a: Int, b: Int): Int = a + b

默认参数 & 具名参数

println(getCircleArea(r = 3.9f))

fun getCircleArea(PI:Float = 3.14f,r:Float):Float{
    return PI * r * r
}

流程控制

When 表达式

  • when 将它的参数和所有的分支条件顺序比较,直到某个分支满足条件。
  • when 既可以被当做表达式使用也可以被当做语句使用。如果它被当做表达式,符合条件的分支的值就是整个表达式的值,如果当做语句使用, 则忽略个别分支的值。
  • when 类似其他语言的 switch 操作符。其最简单的形式如下:
when (x) {
    1 -> print("x == 1")
    2,3 -> print("x == 2")
    in 4..9 -> print("x in 4-8")
    else -> { // 注意这个块
	      print("x 不是 1 ,也不是 2")
    }
}

返回和跳转

Kotlin 有三种结构化跳转表达式:

  • return。默认从最直接包围它的函数或者匿名函数返回。
  • break。终止最直接包围它的循环。
  • continue。继续下一次最直接包围它的循环。

Break 和 Continue 标签

在 Kotlin 中任何表达式都可以用标签(label)来标记。 标签的格式为标识符后跟 @ 符号,例如:abc@、fooBar@都是有效的标签。 要为一个表达式加标签,我们只要在其前加标签即可。

loop@ for (i in 1..100) {
    for (j in 1..100) {
	if (……) break@loop
    }
}

标签限制的 break 跳转到刚好位于该标签指定的循环后面的执行点。 continue 继续标签指定的循环的下一次迭代。

数据结构

定义列表

var nums = 1 until 100 // [1,100)
var nums == 1 .. 16
for (num in nums step 2){}
var num3 = nums.reversed()
num3.count()

var lists = listOf<String>("dog","egg","duck")
println(lists)

for (list in lists)
println(list)

for ((i,e) in lists.withIndex())
println("$i:$e")

for (i in 4 downTo 1 step 2) print(i) // 输出“42”

// 使用 until 函数排除结束元素
for (i in 1 until 10) {   // i in [1, 10) 排除了 10
    println(i)
}

数组

数组用类 Array 实现,并且还有一个 size 属性及 get 和 set 方法,由于使用 [] 重载了 get 和 set 方法.

  • 数组的创建两种方式:一种是使用函数arrayOf();另外一种是使用工厂函数。
fun main(args: Array<String>) {
    //[1,2,3]
    val a = arrayOf(1, 2, 3)
    //[0,2,4]
    val b = Array(3, { i -> (i * 2) })
    //读取数组内容
    println(a[0])    // 输出结果:1
    println(b[1])    // 输出结果:2
}

map使用

var map = TreeMap<String, String>()
map["l1"] = "good"
map["l2"] = "well"
println(map["l1"])

递归

println(fact(5))
println(fact(3))
println(fact(10))
println(fact(100))
println(factBig(BigInteger("30")))

fun fact(n:Int):Int = if(n == 1) 1 else n*fact(n-1)
fun factBig(n:BigInteger):BigInteger = if (n==BigInteger.ONE) BigInteger.ONE else n*factBig(n- BigInteger.ONE)

尾递归优化

fun ollAdd(n:Int):Int = if(n==1) 1 else n+ollAdd(n-1)
tailrec fun ollAdd2(n:Int,res:Int):Int = if (n ==1) 1 else ollAdd2(n-1,res+n)
tailrec fun ollAdd3(n:Int,res:Int):Int {
    println(res+n)
    if (n ==1) {
	return 1
    } else {
	return ollAdd3(n-1,res+n)
    }
}

异常

Exception 与 Java 类似

try {
    var num = "1a"!!.toInt()
    println(num)
} catch (e: Exception) {
    println("err")
}

面向对象

抽象类

抽象是面向对象编程的特征之一,类本身,或类中的部分成员,都可以声明为abstract的。抽象成员在类中不存在具体的实现。

注意:无需对抽象类或抽象成员标注open注解。

abstract class Food {
    abstract fun f()
}

类的初始化代码块

  • 主构造器中不能包含任何代码,初始化代码可以放在初始化代码段中,初始化代码段使用 init 关键字作为前缀。
class Person constructor(firstName: String) {
    init {
	println("FirstName is $firstName")
    }
}

内部类

  • 内部类使用 inner 关键字来表示。
  • 内部类会带有一个对外部类的对象的引用,所以内部类可以访问外部类成员属性和成员函数。
class Outer {
    private val bar: Int = 1
    var v = "成员属性"
    /**嵌套内部类**/
    inner class Inner {
	fun foo() = bar  // 访问外部类成员
	fun innerTest() {
	    var o = this@Outer //获取外部类的成员变量
	    println("内部类可以引用外部类的成员,例如:" + o.v)
	}
    }
}

类的修饰符

  • 类的修饰符包括 classModifier 和accessModifier_:
classModifier: 类属性修饰符,标示类本身特性。

abstract    // 抽象类  
final       // 类不可继承,默认属性
enum        // 枚举类
open        // 类可继承,类默认是final的
annotation  // 注解类
accessModifier: 访问权限修饰符

private    // 仅在同一个文件中可见
protected  // 同一个文件中或子类可见
public     // 所有调用的地方都可见
internal   // 同一个模块中可见

Any 类

  • Kotlin 中所有类都继承该 Any 类,它是所有类的超类,对于没有超类型声明的类是默认超类:
class Example // 从 Any 隐式继承

//  Any 默认提供了三个函数:
equals()
hashCode()
toString()

//注意:Any 不是 java.lang.Object。

泛型

泛型,即 “参数化类型”,将类型参数化,可以用在类,接口,方法上。

  • 声明一个泛型类:
class Box<T>(t: T) {
    var value = t
}
// 创建类的实例时我们需要指定类型参数:

val box: Box<Int> = Box<Int>(1)
// 或者
val box = Box(1) // 编译器会进行类型推断,1 类型 Int,所以编译器知道我们说的是 Box<Int>。

  • 定义泛型类型变量,可以完整地写明类型参数,如果编译器可以自动推定类型参数,也可以省略类型参数。

    Kotlin 泛型函数的声明与 Java 相同,类型参数要放在函数名的前面:

fun <T> boxIn(value: T) = Box(value)

// 以下都是合法语句
val box4 = boxIn<Int>(1)
val box5 = boxIn(1)     // 编译器会进行类型推断

super的泛型使用

  • 如果有多个相同的方法(继承或者实现自其他类,如A、B类),则必须要重写该方法,使用super范型去选择性地调用父类的实现。
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()
    }
}

fun main(args: Array<String>) {
    val c =  C()
    c.f();
}

C 继承自 a() 或 b(), C 不仅可以从 A 或则 B 中继承函数,而且 C 可以继承 A()、B() 中共有的函数。此时该函数在中只有一个实现,为了消除歧义,该函数必须调用A()和B()中该函数的实现,并提供自己的实现。

  • 输出结果为: AB

接口

Kotlin 接口与 Java 8 类似,使用 interface 关键字定义接口,允许方法有默认实现:

interface MyInterface {
    fun foo() {  //已实现
		 // 可选的方法体
		 println("foo")
    }
}

//一个类或者对象可以实现一个或多个接口。
class Child : MyInterface {
    override fun bar() {
	// 方法体
    }
}

扩展

  • Kotlin 可以对一个类的属性和方法进行扩展,且不需要继承或使用 Decorator 模式。
  • 扩展是一种静态行为,对被扩展的类代码本身不会造成任何影响。
  1. 扩展函数

    • 扩展函数可以在已有类中添加新的方法,不会对原类做修改,扩展函数定义形式:
    • 若扩展函数和成员函数一致,则使用该函数时,会优先使用成员函数。
    class C {
        fun foo() { println("成员函数") }
    }
        
    fun C.foo() { println("扩展函数") }
        
    fun main(arg:Array<String>){
        var c = C()
        c.foo()
    }
    
    • 实例执行输出结果为:成员函数

单例模式

  • Kotlin 使用 object 关键字来声明一个对象。
  • Kotlin 中我们可以方便的通过对象声明来获得一个单例。

接口代理,委托

  • 通过关键字 by 指定委托方

Sealed Class

子类类型有限的类型

枚举类

  • 枚举类最基本的用法是实现一个类型安全的枚举。
  • 枚举常量用逗号分隔,每个枚举常量都是一个对象。
fun main() {
    println(Week.星期一)
    println(Week.星期一.ordinal)
}

enum class Week{
    星期一,星期二
}

enum class Color{
    RED,BLACK,BLUE,GREEN,WHITE
}

//每一个枚举都是枚举类的实例,它们可以被初始化:
enum class Color(val rgb: Int) {
    RED(0xFF0000),GREEN(0x00FF00),BLUE(0x0000FF)
}

//默认名称为枚举字符名,值从0开始。若需要指定值,则可以使用其构造函数:
enum class Shape(value:Int){
    circle(2),rectangle(200)
}