前言
小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
Java
和Kotlin
都是Android
的官方开发语言,但是Kotlin
已经上升为第一开发语言,有过之而无不及。
如今,Java 17
都已经出来了,自 Java 8
之后语法改进变化很大,这些改变无不体现着新式语法,很多语法糖层出不穷,从初看别扭、不熟悉到熟练运用之后发现真香,这个掌握新式语法过程并不困难,因为语言都是万变不离其宗的,只要掌握一门,在学另外一门时会有连锁联想,理解起来会更快。
今天就来聊聊这两种语言和区别与联系,并带领大家一起尝尝 Kotlin
新式语法糖,学习它不仅能帮助你领略这门语言本身的魅力,还能透过它在学习 Java
新特性时举一反三。
联系与区别
联系
Kotlin
在底层与java
完全兼容- 编译的产物也是
java
的class
文件 - 都可通过虚拟机运行
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){ ... }
i
在 1
到 10
闭区间之内,每次循环一圈,i
根据 step
步长为 2
变化直到超出规定定范围跳出循环
所有类型都是对象,没有 java
中的 int
、long
、char
等基本数据类型,只有装好箱的 Int
、Long
、Char
等包装类型
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
}
复制代码
此类中有三个属性,它们有着对应的 getter
和 setter
,但是不能在属性的 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)
复制代码
- 问题:
首先,声明变量 a
为 Any
数据类型,但是 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)
复制代码
运行结果都是:
但是 Kotlin
提供的 with
、apply
语法糖就很舒服,在项目中运用得当能省不少代码呢!
函数的形参列表中个数不定时,可以使用 vararg
定义,在函数内以数组对待
// 可变长参数函数,使用 vararg 关键字声明
fun varArgs(vararg i : Int){
for (vi in i){
print(vi)
}
print('\n')
}
复制代码
新式语法糖总结
-
能偷懒尽量偷懒,提高开发效率
-
减少人为错误,比如力所能及地支持自动类型推导
-
在语法层面减少空指针异常,这本来是调试中才能发现的错误,但通过把同一类型可为空和不可为空作为两种不同的类型,在编译的时候就可以发现更多逻辑上的错误
-
支持函数式编程
-
可以扩展已存的类的功能,而不必从它派生
结尾
撰文不易,欢迎大家点赞、评论,你的关注、点赞是我坚持的不懈动力,感谢大家能够看到这里!Peace & Love。