一、Hello World
按照国际惯例,学习一门新的语言通常都是从“Hello World”开始的,在这里也不例外,先看下 Java 中的 main
方法如何用 Kotlin 来表示
fun main(args: Array<String>) {
println("Hello World")
}
从这里可以看出 Kotlin 相比 Java 有几点不同
- 用关键字 fun 来声明一个函数
- 参数类型写在变量名之后
- 函数可以定义在文件的最外层,不需要把它放在类中
- 数组就是类。和 Java 不同,Kotlin 没有声明数组类型的特殊语法
- 使用 println 代替了 System.out.println ,这是 Kotlin 标准库提供的对 Java 标准库函数的封装
- 可以省略代码结尾的分号
此外,Kotlin 的最新版本已经可以省略 main
方法的参数了
二、变量
在 Java 中,大部分的变量是可变的,意味着任何可以访问到这个变量的代码都可以去修改它。而在 Kotlin 中,变量可以分为 可变变量(var) 和 不可变变量(val) 两类
声明变量的关键字有两个:
- val(value)——不可变引用。使用 val 声明的变量不能在初始化之后再次赋值,对应的是 Java 中的 final 变量
- var(variable)——可变引用。var 变量的值可以被改变,对应的是 Java 中的非 final 变量
不可变变量在赋值之后就不能再去改变它的状态了,因此不可变变量可以说是线程安全的,因为它们无法改变,所有线程访问到的对象都是同一个,因此也不需要去做访问控制,开发者应当尽可能地使用不可变变量,这样可以让代码更加接近函数式编程风格
此外,在 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
}
三、数据类型
3.1、基本数据类型
与 Java 不同,Kotlin 并不区分基本数据类型和它的包装类,在 Kotlin 中一切都是对象,可以在任何变量上调用其成员函数和属性
对于基本类型,Kotlin 相比 Java 有几点特殊的地方
- 数字、字符和布尔值可以在运行时表示为原生类型值,但对开发者来说,它们看起来就像普通的类
- Kotlin 对于数字没有隐式拓宽转换,而在 Java 中 int 可以隐式转换为 long
- 在 Kotlin 中字符不能视为数字
- Kotlin 不支持八进制
//在 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 对包装类对象的重复使用问题了
3.2、Any 和 Any?
Any 类型是 Kotlin 所有非空类型的超类型,包括像 Int 这样的基本数据类型
如果把基本数据类型的值赋给 Any 类型的变量,则会自动装箱
val any: Any = 100
println(any.javaClass) //class java.lang.Integer
如果想要使变量可以存储包括 null 在内的所有可能的值,则需要使用 Any?
val any: Any? = null
3.3、Unit
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() {
}
}
3.4、Nothing
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)