按照国际惯例,学习一门新的语言通常都是从“Hello World”开始的,在这里也不例外,先看下 Java 中的 main
方法如何用 Kotlin 来表示
fun main(args: Array<String>) { println("Hello World") } 复制代码
从这里可以看出 Kotlin 相比 Java 有几点不同
此外,Kotlin 的最新版本已经可以省略 main
方法的参数了
在 Java 中,大部分的变量是可变的,意味着任何可以访问到这个变量的代码都可以去修改它。而在 Kotlin 中,变量可以分为 可变变量(var) 和 不可变变量(val) 两类
声明变量的关键字有两个:
不可变变量在赋值之后就不能再去改变它的状态了,因此不可变变量可以说是线程安全的,因为它们无法改变,所有线程访问到的对象都是同一个,因此也不需要去做访问控制,开发者应当尽可能地使用不可变变量,这样可以让代码更加接近函数式编程风格
此外,在 Kotlin 中一切都是对象,没有像 Java 中那样的原始基本类型,但 byte、char、integer、float 或者 boolean 等类型仍然有保留,但是全部都作为对象存在
看以下例子
fun main(args: Array<String>) { //只读变量即赋值后不可以改变值的变量,用 val 声明 //声明一个整数类型的不可变变量 val intValue: Int = 100 //声明一个双精度类型的可变变量 var doubleValue: Double = 100.0 } 复制代码
在声明变量时我们通常不需要指明变量的类型,这可以由编译器根据上下文自动推导出来
fun main(args: Array<String>) { //在声明变量时我们通常不需要指明变量的类型,这可以由编译器根据上下文自动推导出来 val intValue = 100 var doubleValue = 100.0 //如果只读变量在声明时没有初始值,则必须指明变量类型 val intValue2: Int intValue2 = 10 } 复制代码
与 Java 不同,Kotlin 并不区分基本数据类型和它的包装类,在 Kotlin 中一切都是对象,可以在任何变量上调用其成员函数和属性
对于基本类型,Kotlin 相比 Java 有几点特殊的地方
//在 Kotlin 中,int、long、float 等类型仍然存在,但是是作为对象存在的 val intIndex: Int = 100 //等价于,编译器会自动进行类型推导 val intIndex = 100 //数字类型不会自动转型,必须要进行明确的类型转换 val doubleIndex: Double = intIndex.toDouble() //以下代码会提示错误,需要进行明确的类型转换 //val doubleIndex: Double = intIndex val intValue: Int = 1 val longValue: Long = 1 //以下代码会提示错误,因为两者的数据类型不一致,需要转换为同一类型后才能进行比较 //println(intValue == longValue) //Char 不能直接作为数字来处理,需要主动转换 val ch: Char = 'c' val charValue: Int = ch.toInt() //以下代码会提示错误 //val charValue: Int = ch //二进制 val value1 = 0b00101 //十六进制 val value2 = 0x123 复制代码
此外,Kotlin 的可空类型不能用 Java 的基本数据类型表示,因为 null 只能被存储在 Java 的引用类型的变量中,这意味着只要使用了基本数据类型的可空版本,它就会被编译成对应的包装类型
//基本数据类型 val intValue_1: Int = 200 //包装类 val intValue_2: Int? = intValue_1 val intValue_3: Int? = intValue_1 //== 比较的是数值相等性,因此结果是 true println(intValue_2 == intValue_3) //=== 比较的是引用是否相等,因此结果是 false println(intValue_2 === intValue_3) 复制代码
如果 intValue_1 的值是100,就会发现 intValue_2 === intValue_3 的比较结果是 true,这就涉及到 Java 对包装类对象的重复使用问题了
Any 类型是 Kotlin 所有非空类型的超类型,包括像 Int 这样的基本数据类型
如果把基本数据类型的值赋给 Any 类型的变量,则会自动装箱
val any: Any = 100 println(any.javaClass) //class java.lang.Integer 复制代码
如果想要使变量可以存储包括 null 在内的所有可能的值,则需要使用 Any?
val any: Any? = null 复制代码
Kotlin 中的 Unit 类型类似于 Java 中的 void,可以用于函数没有返回值时的情况
fun check(): Unit { } //如果返回值为 Unit,则可以省略该声明 fun check() { } 复制代码
Unit 是一个完备的类型,可以作为类型参数,但 void 不行
interface Test<T> { fun test(): T } class NoResultClass : Test<Unit> { //返回 Unit,但可以省略类型说明,函数也不需要显式地 return override fun test() { } } 复制代码
Nothing 类型没有任何值,只有被当做函数返回值使用,或者被当做泛型函数返回值的类型参数使用时才会有意义,可以用 Nothing 来表示一个函数不会被正常终止,从而帮助编译器对代码进行诊断
编译器知道返回值为 Nothing 类型的函数从不正常终止,所以编译器会把 name1 的类型推断为非空,因为 name1 在为 null 时的分支处理会始终抛出异常
data class User(val name: String?) fun fail(message: String): Nothing { throw IllegalStateException(message) } fun main(args: Array<String>) { val user = User("leavesC") val name = user.name ?: fail("no name") println(name) //leavesC val user1 = User(null) val name1 = user1.name ?: fail("no name") println(name1.length) //IllegalStateException } 复制代码
Kotlin 中的函数以关键字 fun 作为开头,函数名称紧随其后,再之后是用括号包裹起来的参数列表,如果函数有返回值,则再加上返回值类型,用一个冒号与参数列表隔开
//fun 用于表示声明一个函数,getNameLastChar 是函数名 //空括号表示该函数无传入参数,Char 表示函数的返回值类型是字符 fun getNameLastChar(): Char { return name.get(name.length - 1) } 复制代码
//带有两个不同类型的参数,一个是 String 类型,一个是 Int 类型 //返回值为 Int 类型 fun test1(str: String, int: Int): Int { return str.length + int } 复制代码
此外,表达式函数体的返回值类型可以省略,返回值类型可以自动推断。对于有返回值的代码块函数, 必须显式地 写出返回类型和 return 语句
//getNameLastChar 函数的返回值类型以及 return 关键字是可以省略的 //返回值类型可以由编译器根据上下文进行推导 //因此,函数可以简写为以下形式 fun getNameLastChar() = name.get(name.length - 1) 复制代码
如果无返回值,则可以声明 Unit ,也可以省略 Unit
以下三种写法都是等价的
fun test(str: String, int: Int): Unit { println(str.length + int) } fun test(str: String, int: Int) { println(str.length + int) } fun test(str: String, int: Int) = println(str.length + int) 复制代码