带你尝尝 Kotlin 新式语法糖

前言

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。JavaKotlin 都是 Android 的官方开发语言,但是 Kotlin 已经上升为第一开发语言,有过之而无不及。

如今,Java 17 都已经出来了,自 Java 8 之后语法改进变化很大,这些改变无不体现着新式语法,很多语法糖层出不穷,从初看别扭、不熟悉到熟练运用之后发现真香,这个掌握新式语法过程并不困难,因为语言都是万变不离其宗的,只要掌握一门,在学另外一门时会有连锁联想,理解起来会更快。

今天就来聊聊这两种语言和区别与联系,并带领大家一起尝尝 Kotlin 新式语法糖,学习它不仅能帮助你领略这门语言本身的魅力,还能透过它在学习 Java 新特性时举一反三。

联系与区别

联系

  • Kotlin 在底层与 java 完全兼容
  • 编译的产物也是 javaclass 文件
  • 都可通过虚拟机运行

image.png

Kotlin 简直是一体两面、无缝结合啊!

区别

  • Kotlin 编写的程序可以做到不依赖虚拟机运行,称为 Native(原生)方式
  • 比在虚拟机上运行速度更快,对移动设备来说意义重大
  • Kotlin 代表未来的开发方向也不算夸张,17 年推出站在了前人的肩膀上(比如 TS),和现在新出的编程语言的语法规则几乎完全一样

新式语法特征

不用分号

System.out.println("Hello World!");
复制代码
println("Hello World!")
复制代码

只有想在一行内写多条语句时,才需要用分号将每个语句隔开,见下:

println("Hello World!");println("HUALEI");println("Hansome boy!")
复制代码

变量和常量用关键字去区分开来,并且数据类型放在 : 冒号的后面

// 定义一个常量,类型根据赋值自动推断
val param = 1
// 定义一个变量
var param:Int = 2
复制代码
  • 方法或函数的返回值类型放在 : 冒号的后面,例如:
fun onOptions():Boolean{
        return true
} // 其中boolean就是函数的返回值类型
复制代码

不再完全忠诚于面向对象

支持全局函数,既可以在类外面定义函数, 也可以把函数保存在变量中,还可以定义函数类型,跟 C/C++ 一样

支持 lambda 表达式,语法精简再精简

// 三个整数相加
val threeSum:(Int,Int,Int) -> Int = {a,b,c -> a+b+c}
println(threeSum(1,2,3)) // 6
复制代码

定义变量时可以省略数据类型,编译器能够自动识别数据类型

注意:

  • 编译器可以根据其他内容推导出来,否则不能省略哦~

可以在字符串中嵌入表达式(使用 ${} 的形式嵌入表达式),比字符串的格式化函数更方便

  • java 中使用字符串格式化函数:
String name = "帅哥";
String hello = String.format("Hi %s!",name) // Hi 帅哥!
复制代码
  • kotlin 中则是:
val name = "帅哥"
val hello = "Hi ${name}!" // Hi 帅哥!
复制代码

将可为空和不可为空作为两种数据类型对待,Kotlin 中所有参数变量都是不可为空的,除非你在这个类型/变量后面加上 ?

// 类型后加一个 ? 表示可以为空
val age:String ?= null
// 如果不加?直接赋予null值会报错:Error:(28, 23) Kotlin: Null can not be a value of a non-null type String
println(age) // null

// !! -> 抛出空指针异常(!! -> 表示确实不为空,为空的话自己承担后果,会抛出空指针异常)
val ages = age!!.toInt()   //Exception in thread "main" kotlin.KotlinNullPointerException

// age 为空 返回 -1
val agess= age?.toInt() ?: -1   // 这里的 ?. 表示该对象如果不为空,就执行里面的方法
println(agess)  // 这个地方 age?.toInt() 因为 age为空 所以不执行 Int 对象中的方法,根据 ?: 运算符->为空返回:后面的值

// ? 在变量后 -> 不做处理返回 null
val agesss = age?.toInt()
println(agesss) // null
复制代码

具有表示范围的语法(用“..”表示范围,step 表示步长)

for(i in 1..10 step 2){ ... }

i110 闭区间之内,每次循环一圈,i 根据 step 步长为 2 变化直到超出规定定范围跳出循环

所有类型都是对象,没有 java 中的 intlongchar等基本数据类型,只有装好箱的 IntLongChar等包装类型

val by:Byte;  // 占用 1 个字节
val sh:Short; // 占用 2 个字节
val c:Int;    // 占用 4 个字节
var l:Long;   // 占用 8 个字节

// Error: Unresolved reference: int
val ss:int;
复制代码

增加了 === 操作符,用于确定两个变量是不是引用同一个对象,== 判断两个对象的值是否相等,相当于调用了 equals()

val a:Int = 1000
val b:Int = 1000

println(a == b)         // 值相等,返回true

println(a === b)        // 对象地址相等,返回true

val boxA:Int? = a
val boxB:Int? = b

// 经过装箱,创建了两个不同的对象,对象地址不同,则返回 false
println(boxA === boxB)  // false
println(boxA === a)     // false
println(boxB == b)      // true
复制代码

创建对象时不再用 new ,而是直接调用构造方法

// 创建一个对象
val person = Person()

// 对象属性赋值
person.name = "Jackson"
person.sex = "女"
person.age = 20
person.habby = "sing jump rap and basketball"
复制代码

if 语句可以有返回值

  • 作为返回值给变量赋值
val max = if (a > b ) {
        println("a is bigger than b")
        a
}else {
        println("b is bigger than a")
        b
}
复制代码
  • 作为函数的返回值
fun smallerNum(num1:Int,num2:Int): Int{
    return if (num1 < num2)
        num1
    else
        num2
}

// 简化
fun smallerNum(num1:Int,num2:Int)=if (num1 < num2) num1 else num2
复制代码

when 代替 switch…case,不仅可以判断具体情况,还能判断目标变量值是否在一个范围内

// when 表达式相当于C、java中的switch语句
fun description(month:Int):Unit {
    when (month) {
        3,4,5 -> println("春天到了,夏天还会远吗?")
        6,7,8 -> println("夏天到了,秋天还会远吗?")
        9,10,11 -> println("秋天到了,冬天还会远吗?")
        12,1,2 -> println("冬天到了,秋天还会远吗?")
        else -> println("没有${month}月,输入错误!") // 这里的 else 等同于 default
    }
}
复制代码
  • 判断一个值在(in)或者不在(!in)一个区间或者集合中
fun description(month:Int) {
    when (month) {
        in 3..5 -> println("${month}月,是春天!")
        in 6..8 -> println("${month}月,是夏天!")
        in 9..11 -> println("${month}月,是秋天!")
        12,1,2 -> println("${month}月,是冬天!")
        !in 1..12 -> println("没有${month}月,输入错误!")
    }
}
复制代码

在类中定义的成员变量其实是属性,而不是字段

class Message {
        // 标题
        var title:String ?= null
        // 内容
        var content:String ?= null
        // 时间戳
        var timestamp:Long = 0
}
复制代码

此类中有三个属性,它们有着对应的 gettersetter,但是不能在属性的 getter 或者 setter 代码中使用到属性本身,这样会引起无限递归调用

要解决这个问题,就必须要知道每个属性都有一个不可见的字段存储属性的值,这个字段可以用 field 进行访问:

class Message {
        var title: String = "通知"
                get() = field + ":"  // 将变量 title 赋值并在 getter 时在字符串末尾添冒号
                 
        var content: String ?= "从明天起,开始春节放假。"
                set(value) {
                        field = value
                }
                
        var timestamp: Long = 0
}
复制代码

类型转换使用 as 关键字

val a:Any = "abc"
// 如若不使用 as 进行类型转换,a.length 报错
a as String
println(a.length)
复制代码
  • 问题:

首先,声明变量 aAny 数据类型,但是 kotlin 在编译的时候会认为 a 引用的是 Any 类型对象,所以就不能用 a.length

  • 解决:

需要使用 as,将 a 引用的类型对象转换成 String 类型,就可以调用 length 方法了

当连续调用某个对象的多个方法时,可以使用 with 关键字,让对象只出现一次

没学 with 关键字用法前:

// langs is a list
val builder = StringBuilder()
builder.append("开始我的编程之路啦!").append("\n")
langs.forEach {
    builder.append("我学了").append(it)
    builder.append("\n")
}
builder.append("我学完了,头发也掉光了!")
println(builder.toString())
复制代码

精通后:

// langs is a list
val result = with(StringBuilder()) {
    append("开始我的编程之路啦!").append("\n")
    langs.forEachIndexed{ index,item ->
        append("我学了").append(item).append("\n")
        if(index == fruits.lastIndex) {
            append("我学完了,头发也掉光了!")
        }
    }
    toString()
}
println(result)
复制代码

运行结果都是:

image.png

但是 Kotlin 提供的 withapply 语法糖就很舒服,在项目中运用得当能省不少代码呢!

函数的形参列表中个数不定时,可以使用 vararg 定义,在函数内以数组对待

// 可变长参数函数,使用 vararg 关键字声明
fun varArgs(vararg i : Int){
    for (vi in i){
        print(vi)
    }
    print('\n')
}
复制代码

新式语法糖总结

  1. 能偷懒尽量偷懒,提高开发效率

  2. 减少人为错误,比如力所能及地支持自动类型推导

  3. 在语法层面减少空指针异常,这本来是调试中才能发现的错误,但通过把同一类型可为空和不可为空作为两种不同的类型,在编译的时候就可以发现更多逻辑上的错误

  4. 支持函数式编程

  5. 可以扩展已存的类的功能,而不必从它派生

结尾

撰文不易,欢迎大家点赞、评论,你的关注、点赞是我坚持的不懈动力,感谢大家能够看到这里!Peace & Love。

猜你喜欢

转载自juejin.im/post/7022505800039940126