kotlin详解

简述

  kotlin是一种在Java虚拟机上执行的静态类型编程语言,他可以和java语言互相转换,kotlin可以写前端、后端、移动端,目前在安卓开发中非常火热

  和java相比kotlin十分的简洁,例如一个data类就能表示java中带getter setter的JavaBean

kotlin的优点

  简洁、安全、可操作性强

/*
 使用一行代码创建一个包含 getters、 setters、 `equals()`、 `hashCode()`、 `toString()` 以及 `copy()` 的 POJO:
*/

data class Customer(val name: String, val email: String, val company: String)

// 或者使用 lambda 表达式来过滤列表:

val positiveNumbers = list.filter { it > 0 }

// 想要单例?创建一个 object 就可以了:

object ThisIsASingleton {
    val companyName: String = "JetBrains"
}

  让我们和烦人的空指针异常say拜拜

/*
 彻底告别那些烦人的 NullPointerException——著名的十亿美金的错误
*/

var output: String
output = null   // 编译错误

// Kotlin 可以保护你避免对可空类型进行误操作

val name: String? = null    // 可空类型
println(name.length())      // 编译错误

// 并且如果类型检测正确,编译器会为你做自动类型转换

fun calculateTotal(obj: Any) {
    if (obj is Invoice)
        obj.calculateTotal()
}

  可以使用jvm的所有库,兼容性强,java能用的kotlin都能用

Hello world

  学习一个语言从hello world开始

fun main(args: Array<String>) {
    println("Hello World!")
}

  使用对象的hello world

class Hello(
    private val name: String
) {
    fun sayHelloWorld() {
        println("hello world $name")
    }
}

fun main(args: Array<String>) {
    Hello("foo").sayHelloWorld();
}

基本数据类型

  Kotlin的基本类型包括 Byte、Short、Int、Long、Float、Double

类型名 大小(位)
Double 64  
Float 32
Long 64
Int 32
Short 16
Byte 8

  但在kotlin中,没有基本类型,你所定义的每一个变量,kotlin都会帮你封装成一个对象,这样则可以确保不会出现空指针异常

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

fun main(args: Array<String>) {
    val a: Int = 10000
    println(a === a) // true,值相等,对象地址相等

    //经过了装箱,创建了两个不同的对象
    val boxedA: Int? = a
    val anotherBoxedA: Int? = a

    //虽然经过了装箱,但是值是相等的,都是10000
    println(boxedA === anotherBoxedA) //  false,值相等,对象地址不一样
    println(boxedA == anotherBoxedA) // true,值相等
}

  在kotlin里每一种类型中,都有以下方法用于进行类型转换

toByte(): Byte
toShort(): Short
toInt(): Int
toLong(): Long
toFloat(): Float
toDouble(): Double
toChar(): Char

  例如

fun main(args: Array<String>) {
    val name1:String = "123"
    println(name1.toInt()) //输出123

    val name2:String = "123ab"
    println(name2.toInt()) //抛NumberFormatException
}

基本语法

变量和常量

  可变变量用var表示

var <标识符> : <类型> = <初始化值>

  不可变变量用val表示,类似java中的final

val <标识符> : <类型> = <初始化值>

函数定义

  kotlin的函数用fun表示,参数列表格式为 参数名:参数类型,参数列表的括号后写返回值类型

fun sum(a: Int, b: Int): Int {   // Int 参数,返回值 Int
    return a + b
}

  若函数体为表达式,可以用=表示,会自动推断类型

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

public fun sum(a: Int, b: Int): Int = a + b   // public 方法则必须明确写出返回类型

  无返回值的函数

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)
    }
}

// 测试
fun main(args: Array<String>) {
    vars(1,2,3,4,5)  // 输出12345
}

扩展函数

  Kotlin 能够对一个类或接口扩展新功能而无需继承该类或者使用像装饰者这样的设计模式

  我们可以使用扩展函数为一个你不能修改的、第三方库中的类或接口编写一个新的函数这个新增的函数就像那个原始类本来就有的函数一样,可以用寻常方式调用

  例如我们这里扩展了List<Student>类型的函数printName,然后我们就可以通过.的方式调用printName了

class Student(
    val name: String
)

fun List<Student>?.printName() {
    this?.forEach { println(it.name) }
}

fun main() {
    val students = listOf<Student>(Student("jack"), Student("foo"), Student("bar"))
    students.printName()
}

 

null检查机制

  一个变量或者常量在定义时就会设置是否为空,如果一个变量或常量的值可以为空,则在定义的时候在类型后门加上?,如果没有加则当为这个变量或常量赋空值的时候会编译失败

var a: String = "abc" // 默认情况下,常规初始化意味着非空
a = null // 编译错误

  加上?后可赋空值

var b: String? = "abc" // 可以设置为空
b = null // 可以编译

  第一种情况下你可以安心地访问a的成员,因为他不可能是空

  第二种情况下不能直接访问,因为b可能是空,这样是不安全的,我们可以写if-else手动判空然后访问成员

  我们也可以使用kotlin提供的?运算符,如果一个环节是null,则整条调用链返回null

bob?.department?.head?.name

  同时可以配合let关键字使用,表示不为空的时候进行某些操作

val listWithNulls: List<String?> = listOf("Kotlin", null)
for (item in listWithNulls) {
    item?.let { println(it) } // 输出 Kotlin 并忽略 null
}

类型检测和自动类型转换

  我们可以使用 is 运算符检测一个表达式是否某类型的一个实例

fun getStringLength(obj: Any): Int? {
  if (obj is String) {
    // 做过类型判断以后,obj会被系统自动转换为String类型
    return obj.length 
  }
}

  is前面可以加上!,表示不是该类型实例

fun getStringLength(obj: Any): Int? {
  if (obj !is String)
    return null
  // 在这个分支中, `obj` 的类型会被自动转换为 `String`
  return obj.length
}

区间

  区间表示用..运算符连接,在循环中使用in关键字进行遍历,使用step关键字指定步长

  若从大到小遍历,则使用downTo关键字

ifor (i in 1..4) print(i) // 输出“1234”

for (i in 4..1) print(i) // 什么都不输出

if (i in 1..10) { // 等同于 1 <= i && i <= 10
    println(i)
}

// 使用 step 指定步长
for (i in 1..4 step 2) print(i) // 输出“13”

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)
}

when表达式

  when 将它的参数和所有的分支条件顺序比较,直到某个分支满足条件。

  when 既可以被当做表达式使用也可以被当做语句使用。

  如果它被当做表达式,符合条件的分支的值就是整个表达式的值,如果当做语句使用, 则忽略个别分支的值。

when (x) {
    1 -> print("x == 1")
    2 -> print("x == 2")
    else -> { // 注意这个块
        print("x 不是 1 ,也不是 2")
    }
}

  如果很多分支需要用相同的方式处理,则可以把多个分支条件放在一起,用逗号分隔

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

类和对象

  Kotlin 类可以包含:构造函数和初始化代码块、函数、属性、内部类、对象声明

class Student {  // 类名为 Student
    // 大括号内是类体构成
}

  类体里面可以定义属性和函数

class Student {
    var name: String = ……
    var url: String = ……
    var city: String = ……
    fun foo() { print("Foo") } // 成员函数
}

  和java一样类会自带一个默认构造函数,我们可以像使用普通函数那样使用构造函数创建类实例

val site = Runoob() // Kotlin 中没有 new 关键字

  kotlin类的构造函数分为主构造函数和次构造函数,主构造函数写在类名后门的小括号里

  init表示初始化代码段,可以理解为构造函数的函数体

class Person constructor(firstName: String) {
    init {
        println("FirstName is $firstName")
    }
}

  在小括号内的构造函数参数,会自动生成类的成员属性,可以通过主构造器来定义属性并初始化属性值

数据类data class

  对于一些只保存数据的类(贫血模型 笑),我们可以在class的定义前加上data关键字 

data class User(val name: String, val age: Int)

  数据类会自动生成equals()/hashCode()、toString()、componentN()、copy()方法

  copy函数用于改变该类其中的一些属性,其他类保持一致 

val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)

  componentN函数用于类的解构

val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age") // 输出 "Jane, 35 years of age"

密封类sealed 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是密封类,其子类为Const、Sum、NotANumber,使用密封类的好处在于使用when表达式时如果能够验证语句覆盖了所有情况,就不需要为该语句再添加一个else了

fun eval(expr: Expr): Double = when(expr) {
    is Const -> expr.number
    is Sum -> eval(expr.e1) + eval(expr.e2)
    NotANumber -> Double.NaN
    // 不再需要else,因为我们已经覆盖了所有的情况
}

kotlin的内置函数

  kotlin提供了很多内置函数,提升开发者的效率,常见的有let also with run apply

let函数

  let最大的作用就是配合?帮助我们简化对对象判空的操作,在let作用域内,该对象用it表示

// 作用1:使用it替代object对象去访问其公有的属性 & 方法
object.let{
   it.todo()
}

// 作用2:判断object为null的操作
object?.let{//表示object不为null的条件下,才会去执行let函数体
   it.todo()
}

// 注:返回值 = 最后一行 / return的表达式

  和java相比,let配合?能让我们避免写if判空的同时确定了变量的作用范围

// 使用Java
if( mVar != null ){
    mVar.function1();
    mVar.function2();
    mVar.function3();
}

// 使用kotlin(无使用let函数)
mVar?.function1()
mVar?.function2()
mVar?.function3()

// 使用kotlin(使用let函数)
// 方便了统一判空的处理 & 确定了mVar变量的作用域
mVar?.let {
       it.function1()
       it.function2()
       it.function3()
}

also函数

  和let类似,区别在于let函数返回值是最后一行,also是返回传入的对象本身

// let函数
var result = mVar.let {
               it.function1()
               it.function2()
               it.function3()
               999
}
// 最终结果 = 返回999给变量result

// also函数
var result = mVar.also {
               it.function1()
               it.function2()
               it.function3()
               999
}
// 最终结果 = 返回一个mVar对象给变量result

with函数

  用with函数可以在调用同一个对象的多个方法或属性时,可以省去对象名重复,直接调用方法名或属性

  返回值是函数的最后一行

val people = People("carson", 25)
with(people) {
println("my name is $name, I am $age years old")
}

// Java
User peole = new People("carson", 25);
String var1 = "my name is " + peole.name + ", I am " + peole.age + " years old";
System.out.println(var1);

run函数

  run可以看成是let+with,可以在判空的同时省去写对象名

// 此处要调用people的name 和 age属性,且要判空
// kotlin
val people = People("carson", 25)
people?.run{
    println("my name is $name, I am $age years old")
}

// Java
User peole = new People("carson", 25);
String var1 = "my name is " + peole.name + ", I am " + peole.age + " years old";
System.out.println(var1);

apply函数

  apply函数和run函数类似,区别在于返回值是对象本身

// run函数
val people = People("carson", 25)
val result = people?.run{
    println("my name is $name, I am $age years old")
    999
}
// 最终结果 = 返回999给变量result

// Java
val people = People("carson", 25)
val result = people?.run{
    println("my name is $name, I am $age years old")
    999
}
// 最终结果 = 返回一个people对象给变量result

kotlin的lambda表达式

  在kotlin中lambda的完整语法如下

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

  我们可以把能去的类型声明都去掉,可以简写如下

val sum = { x: Int, y: Int -> x + y }

  在kotlin有个约定,如果一个函数的最后一个参数是函数,我们就可以把该参数以lambda表达式的形式放在圆括号外

val product = items.fold(1) { acc, e -> acc * e }

  如果前面的参数不传递或没有,连圆括号都可省略

run { println("...") }

compaion object伴生对象

  我们可以将伴生对象简单理解为代替java中静态变量和静态方法的一个东西

  在类里我们可以用compaion object定义一个伴生对,然后我们可以通过类名来调用这个伴生对象的方法 

class Person {
    companion object {
        fun callMe() = println("I'm called.")
    }
}

fun main(args: Array<String>) {
    Person.callMe()
}

顶层声明

  在java中,变量和方法都要声明在类里面,而在kotlin里,变量和方法可以声明在顶层

  顶层就是类的外面,你的变量和方法都会被放到该文件所属的包下 

var a = "123"

fun didi() {
    println("didi $a")
}

  像这样声明变量和方法,不归属于任何一个类,没有指定修饰符的话默认是public修饰符,即到处都能用你的变量和方法,如果为private,则只可以在声明的文件中使用,如果为internal,则会在相同模块内随处可见

const关键字

  const只能修饰kotlin的顶层属性或者object对象中的属性,只能修饰字符串和基本数据类型

  在kotlin中可变变量用var表示,只读变量用val表示,这里注意val是只读而不是不可变

  const只能放在val前面,被const val修饰的变量在编译成java时会转化成static final,在调用时可以直接通过.访问属性,而val修饰的变量则通过get方法进行访问

references

https://www.kotlincn.net/

https://www.runoob.com/kotlin/kotlin-tutorial.html

https://www.kotlincn.net/docs/reference/null-safety.html

https://cloud.tencent.com/developer/article/1591238

https://www.kotlincn.net/docs/reference/lambdas.html

https://www.jianshu.com/p/cd3a7c189e02

posted @ 2023-03-08 01:05  艾尔夏尔-Layton  阅读(176)  评论(0编辑  收藏  举报