最近了解一下Kotlin,毕竟Google力挺,网上对于Kotlin褒贬不一,有吹捧的,也有贬低的,在拿不定主意的时,不如自己动手试试,看看Kotlin会带来哪些新的体验,本文主要针对Java的开发者,着重总结两者区别。
Kotlin的优势
好好了解一下,不然被别人问为什么使用Kotlin,会被问的哑口无言。
- 全面支持Lambda表达式
- 数据类(Data classes)
- 函数字面量和内联函数(Function literals & inline functions)
- 函数扩展(Extension function)
- 空安全(Null safety)
- 智能转换(Smart casts)
- 字符串模版(String templates)
- 主构造函数(Primary constructors)
- 类委托(Class delegation)
- 类型判断(Type inference)
- 单例(Singletons)
- 声明点变量(Declaration-site variance)
- 区间表达式 (Range expressions)
Kotlin的基础类型
1. 分隔符
Kotlin每行语句可以不以分号结束,如果一行写多条独立的语句时需要使用分号。
fun main(args: Array<String>) {
println("Hello World")
println("Hello");println("World")
}
2. 注释
多行注释可以嵌套使用;单行注释和文档注释与Java一致。
fun main(args: Array<String>) {
/*println("Hello world")
/*println("first")*/
println("second")*/
println("third")
}
简书的 Kotlin 代码高亮有点问题,放到编译器中的效果是正常的。
3. 变量
- 声明变量
var
和val
,val
是只读数据类型,只能赋值一次 - 类型推断,可以不显式声明变量类型
fun main(args: Array<String>) {
var i: Int // 显式声明变量i的类型,Java中的int a;
var name: String = "Kotlin" // 显式声明变量并赋值,Java中的String name = "Kotlin";
var my_name = "JamFF" // 上面的简写,编译器推断Sting类型
name = "Java" // 重新赋值,Java中的name = "Java";
my_name = 18 // my_name是String类型,不能赋值为Int
val a = false // 不可以重新赋值,Java中的final boolean b = false;
val b: Int // 不同于Java,可以先声明,不赋值
b = 1 // 只要在使用之前赋值即可,只能赋值一次
}
4. 整型
-
Byte
,Java的byte
和Byte
,8bit。 -
Short
,Java的short
和Short
,16bit。 -
Int
,Java的int
和Integer
,32bit。 -
Long
,Java的long
和Long
,64bit。
-
整型会默认类型推断为
Int
;如果超过Int
取值范围,会推断为Long
;如果需要定义为Long
类型,除了显式声明数据类型外,还可以在结尾处使用大写的L
来表示,而在 Java 大小写的L
均可。fun main(args: Array<String>) { var a: Int = 18 a = 100_000_000_000 // 不能赋值,超过Int范围 var b = 1// 整型默认推断为Int,Byte,Short需要显式声明数据类型 // var b: Byte = 1 // var b: Short = 1 // var b = 1L// Long可以不声明数据类型,在尾部使用L表示 var c = 100_000_000_000 // 如果超过Int范围,会自动推断类型为Long }
-
Kotlin是空安全的语言,所以上述四种数据类型均不能接受
null
值,如果要存储null
值需要使用Byte?
、Short?
、Int?
、Long?
类型。fun main(args: Array<String>) { val a: Int = null // 报错 val b: Int? = null // 正确 }
-
Kotlin将不带
?
类型映射为Java的基本类型,将带?
类型映射为Java的引用类型。fun main(args: Array<String>) { val a: Int = 666 val b: Int = 666 // ===比较的是地址,==比较的是值 println(a === b) // true,基本类型比较 val c: Int? = 666 val d: Int? = 666 println(c === d) // false,引用类型比较 }
-
Kotlin支持二进制、十进制和十六进制,不支持八进制。
fun main(args: Array<String>) { val a = 0b10101 val b = 0xab // a的值为21,b的值为171,a+b的值为192 println("a的值为$a,b的值为$b,a+b的值为${a + b}") }
5. 浮点型
与Java一致。
-
Float
:32bit,需要在尾部添加F
或f
。 -
Double
:64bit,类型推断的默认值。
fun main(args: Array<String>) {
var a = 3.14 // 默认Double
a = 2.0f // 报错,不能赋值给Float
var b = 1.5f // Float
b = 3.14 // 报错,不能赋值给Double
}
6. 字符型
与Java不同,Kotlin的Char
型变量不能当成整数值使用。
fun main(args: Array<String>) {
var a: Char = 'a' // 字符a
a = '\n' // 换行符
a = '\u8888' // 汉字'驾'
a = 1 // 报错
}
7. 数值型之间的类型转换
-
Kotlin不支持取值范围小的数据类型隐式转换为取值范围大的类型,需要显式调用。
- toByte()
- toShort()
- toInt()
- toLong()
- toFloat()
- toDouble()
- toChar()
fun main(args: Array<String>) { var a: Byte = 1 var b: Short = 2 b = a // 报错,Java允许 a = b.toByte() // 可以,注意溢出 b = a.toShort() // 可以 var c = 1.0f var d = 2.2 d = c // 报错,Java允许 c = d.toFloat() // 可以,注意溢出 d = c.toDouble() // 可以 }
-
与Java一致,虽然Kotlin中缺乏隐式转换,但在表达式中可以自动转换。通过
javaclass
查看数据类型。fun main(args: Array<String>) { val a: Byte = 1 val b: Short = 2 val c = 1.0 val total = a + b // javaClass属性来自Any类型,是Kotlin所有类型但根父类 println(total.javaClass) // int val total2 = a.toLong() + b.toByte() println(total2.javaClass) // long val total3 = a + b + c println(total3.javaClass) // double }
与Java一致,将浮点型强转为整数型,小数部分会被截断;将整数型强转为浮点型没有问题。
-
Kotlin中
Char
型虽然不能当成整数进行算术运算,但是可以Char
型值加、减一个整数值,也可以两个Char
相减,但不能相加。fun main(args: Array<String>) { val a = 'a' val b = 'b' println(a + 2) // c println(b + 2) // d println(b - a) // 1 println(a + b) // 报错 }
8. Boolean类型
与Java一致。
9. 空安全
-
非空类型和可空类型
fun main(args: Array<String>) { val str = "abc" val num: Int = str.toIntOrNull() // 报错 val num2: Int? = str.toIntOrNull() // 通过 val num3 = str.toIntOrNull() // 通过,类型推断为Int?类型 }
那么Kotlin是如何保证空安全呢
fun main(args: Array<String>) { val a: String = "abc" val b: String? = "abc" println(a.length) // a非null,不可能出现空指针 println(b.length) // b可以为null,但是如果不判断非空,编译报错 }
-
先判断后使用
可空类型的变量不允许直接调用方法或属性,必须判断是否为null。fun main(args: Array<String>) { val a: String? = "abc" // 可以使用安全调用和Elvis简化 val len = if (a != null) a.length else -1 if (a != null && a.isNotEmpty()) { println(a.length) } else { println("空字符串") } }
-
安全调用
- 可以使用
?.
进行安全调用,避免出现空指针。
fun main(args: Array<String>) { var a: String? = "abc" println(a?.length) // 打印3 a = null println(a?.length) // 打印null }
- 与Spring EL类似,Kotlin的安全调用也支持链式调用。
fun main(args: Array<String>) { // 安全的获取user的dog的name,如果user或者user.dog为null,整个表达式返回null user?.dog?.name }
- 安全调用还可与
let
全局函数结合使用,例如下面只打印非空元素。
fun main(args: Array<String>) { val arr: Array<String?> = arrayOf("abc", "JamFF", "Tom", null, "Tony") for (s in arr) { s?.let { println(it) } } }
- 可以使用
-
Elvis运算
?:
运算符就是Elvis,如果?:
左边不为null
返回左边表达式的值,否则返回?:
右边表达式的值。fun main(args: Array<String>) { val a: String? = "abc" val len = if (a != null) a.length else -1 val len2 = a?.length ?: -1 // 上面语句的简写 if (a != null && a.isNotEmpty()) { println(a.length) } else { println("空字符串") } }
Kotlin的
return
、throw
都属于表达式,可以灵活使用。 -
强制调用
!!.
不管变量是否为null
强制调用,可以编译通过,运行可能引发空指针,谨慎使用。fun main(args: Array<String>) { var a: String? = "abc" println(a!!.length) // 输出3 a = null println(a!!.length) // 空指针 val arr: Array<String?> = arrayOf("abc", "JamFF", "Tom", null, "Tony") for (s in arr) { s!!.let { println(it) } // 空指针 } }
10. 字符串
-
遍历字符串中每一个字符
fun main(args: Array<String>) { val str = "JamFF" for (c in str){ println(c) } }
-
字符串分类
- 转义字符串,可以包含转义字符,类似Java中的字符串。
- 原始字符串,可以包含换行符和任意文本,需要三个引号包裹。
fun main(args: Array<String>) { val str = "abc" // 转移字符串 // 原始字符串 val txt = """ 但行好事, 勿问前程。 """ println(txt) // trimIndent去除字符串前面的缩进 val txt2 = """ 但行好事, 勿问前程。 """.trimIndent() println(txt2) val txt3 = """ |但行好事, |勿问前程。 """ println(txt3) // trimMargin去除边界符,Kotlin默认是"|", val txt4 = """ |但行好事, |勿问前程。 """.trimMargin() println(txt4) // 去除自定义边界符 val txt5 = """ ^但行好事, ^勿问前程。 """.trimMargin("^") println(txt5) }
输出结果
-
字符串模版
Kotlin允许在字符串(转移字符串、原始字符串)中嵌入变量或表达式,只要放入${}
中即可。fun main(args: Array<String>) { val a = 2018 var s = "今年是${a}年" println(s) s = "随机数:${java.util.Random().nextInt(10)}" println(s) }
Kotlin字符串的方法
Kotlin的String
和Java的不是同一个类,有更多便捷的API。
11. 类型别名
- 类似于C语言的
typedef
的功能,Kotlin可以使用typealias
定义类型别名。fun main(args: Array<String>) { // Kotlin: Nested and local type aliases are not supported typealias StringSet = Set<String> // 报错,别名不能写在方法中 val set: StringSet var table: FileTable<String> } // 正确定义位置 typealias StringSet = Set<String> typealias FileTable<K> = MutableMap<K, MutableList<File>>
- 也可以给内部类定义别名。
class A { inner class Inner } class B { inner class Inner } typealias AInner = A.Inner typealias BInner = B.Inner fun main(args: Array<String>) { val a: AInner = A().Inner() val b = B().Inner() println(a.javaClass) // 输出class A$Inner println(b.javaClass) // 输出class B$Inner }
- Kotlin的
Lambda
表达式的类型直接就是函数类型,而Java的是函数是接口,因此Kotlin也允许为Lambda
表达式的类型指定别名。// 为(T) -> Boolean类型指定别名Predicate<T> typealias Predicate<T> = (T) -> Boolean fun main(args: Array<String>) { // 使用Predicate<String>定义变量,该变量的值是一个Lambda表达式 val p: Predicate<String> = { it.length > 4 } // 为filter()方法传入p参数,只保留长度大于4的字符串 println(arrayOf("Java", "PHP", "Python", "Go", "Kotlin").filter(p)) // 输出[Python, Kotlin] }
重点
- 每行语句建议不以分号结束,换行即可。
- 可变变量
var
和不可变变量val
。 - 基本数据类型
- 空安全
- 字符串和字符串模版
- 类型别名