println("(intValue + 100) value is ${intValue + 100}") //(intValue + 100) value is 200
如果你需要在原始字符串中表示字面值($)字符(它不支持反斜杠转义),可以用下列语法:
val price = “KaTeX parse error: Expected '}', got 'EOF' at end of input: {'’}100.99”
println(price) //$100.99
3.3、数组
kotlin 中的数组是带有类型参数的类,其元素类型被指定为相应的类型参数,使用 Array 类来表示, Array 类定义了 get 与 set 函数(按照运算符重载约定这会转变为 [ ] )以及 size 属性等
创建数组的方法有以下几种:
- 用 arrayOf 函数创建一个数组,包含的元素是指定为该函数的实参
- 用 arrayOfNulls 创建一个给定大小的数组,包含的元素均为 null,只能用来创建包含元素类型可空的数组
- 调用 Array 类的构造方法,传递数组的大小和一个 lambda 表达式,调用 lambda 表达式来创建每一个数组元素
//包含给定元素的字符串数组
val array1 = arrayOf(“leavesC”, “叶”, “https://github.com/leavesC”)
array1[0] = “leavesC”
println(array1[1])
println(array1.size)
//初始元素均为 null ,大小为 10 的字符数组
val array2 = arrayOfNulls(10)
//创建从 “a” 到 “z” 的字符串数组
val array3 = Array(26) { i -> (‘a’ + i).toString() }
需要注意的是,数组类型的类型参数始终会变成对象类型,因此声明 Array< Int > 将是一个包含装箱类型(java.lang.Integer)的数组。如果想要创建没有装箱的基本数据类型的数组,必须使用一个基本数据类型数组的特殊类
为了表示基本数据类型的数组,kotlin 为每一种基本数据类型都提供了若干相应的类并做了特殊的优化。例如,有 IntArray、ByteArray、BooleanArray 等类型,这些类型都会被编译成普通的 Java 基本数据类型数组,比如 int[]、byte[]、boolean!
[] 等,这些数组中的值存储时没有进行装箱,而是使用了可能的最高效的方式。需要注意,IntArray 等并不是 Array 的子类
要创建一个基本数据类型的数组,有以下几种方式:
- 向对应类型的类(如 IntArray)的构造函数传递数组大小,这将返回一个使用对应基本数据类型默认值初始化好的数组
- 向对应类型的类(如 IntArray)的构造函数传递数组大小以及用来初始化每个元素的 lambda
- 向工厂函数(如 charArrayOf)传递变长参数的值,从而得到指定元素值的数组
//指定数组大小,包含的元素将是对应基本数据类型的默认值(int 的默认值是 0)
val intArray = IntArray(5)
//指定数组大小以及用于初始化每个元素的 lambda
val doubleArray = DoubleArray(5) { Random().nextDouble() }
//接收变长参数的值来创建存储这些值的数组
val charArray = charArrayOf(‘H’, ‘e’, ‘l’, ‘l’, ‘o’)
3.4、Any 和 Any?
Any 类型是 kotlin 所有非空类型的超类型,包括像 Int 这样的基本数据类型
如果把基本数据类型的值赋给 Any 类型的变量,则会自动装箱
val any: Any = 100
println(any.javaClass) //class java.lang.Integer
如果想要使变量可以存储包括 null 在内的所有可能的值,则需要使用 Any?
val any: Any? = null
3.5、Unit
kotlin 中的 Unit 类型类似于 Java 中的 void,可以用于函数没有返回值时的情况
fun check(): Unit {
}
//如果返回值为 Unit,则可以省略该声明
fun check() {
}
Unit 是一个完备的类型,可以作为类型参数,但 void 不行
interface Test {
fun test(): T
}
class NoResultClass : Test {
//返回 Unit,但可以省略类型说明,函数也不需要显式地 return
override fun test() {
}
}
3.6、Nothing
Nothing 类型没有任何值,只有被当做函数返回值使用,或者被当做泛型函数返回值的类型参数使用时才会有意义,可以用 Nothing 来表示一个函数不会被正常终止,从而帮助编译器对代码进行诊断
编译器知道返回值为 Nothing 类型的函数从不正常终止,所以编译器会把 name1 的类型推断为非空,因为 name1 在为 null 时的分支处理会始终抛出异常
data class User(val name: String?)
fun fail(message: String): Nothing {
throw IllegalStateException(message)
}
fun main() {
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)
4.1、命名参数
为了增强代码的可读性,kotlin 允许我们使用命名参数,即在调用某函数的时候,可以将函数参数名一起标明,从而明确地表达该参数的含义与作用,但是在指定了一个参数的名称后,之后的所有参数都需要标明名称
fun main() {
//错误,在指定了一个参数的名称后,之后的所有参数都需要标明名称
//compute(index = 110, “leavesC”)
compute(index = 120, value = “leavesC”)
compute(130, value = “leavesC”)
}
fun compute(index: Int, value: String) {
}
4.2、默认参数值
可以在声明函数的时候指定参数的默认值,从而避免创建重载的函数
fun main() {
compute(24)
compute(24, “leavesC”)
}
fun compute(age: Int, name: String = “leavesC”) {
}
对于以上这个例子,如果按照常规的调用语法时,必须按照函数声明定义的参数顺序来给定参数,可以省略的只有排在末尾的参数
fun main() {
//错误,不能省略参数 name
// compute(24)
// compute(24,100)
//可以省略参数 value
compute(“leavesC”, 24)
}
fun compute(name: String = “leavesC”, age: Int, value: Int = 100) {}
如果使用命名参数,可以省略任何有默认值的参数,而且也可以按照任意顺序传入需要的参数
fun main() {
compute(age = 24)
compute(age = 24, name = “leavesC”)
compute(age = 24, value = 90, name = “leavesC”)
compute(value = 90, age = 24, name = “leavesC”)
}
fun compute(name: String = “leavesC”, age: Int, value: Int = 100) {
}
4.3、可变参数
可变参数可以让我们把任意个数的参数打包到数组中传给函数,kotlin 的语法相比 Java 有所不同,改为通过使用 varage 关键字声明可变参数
例如,以下的几种函数调用方式都是正确的
fun main() {
compute()
compute(“leavesC”)
compute(“leavesC”, “叶应是叶”)
compute(“leavesC”, “叶应是叶”, “叶”)
}
fun compute(vararg name: String) {
name.forEach { println(it) }
}
在 Java 中,可以直接将数组传递给可变参数,而 kotlin 要求显式地解包数组,以便每个数组元素在函数中能够作为单独的参数来调用,这个功能被称为展开运算符,使用方式就是在数组参数前加一个 *
fun main() {
val names = arrayOf(“leavesC”, “叶应是叶”, “叶”)
compute(* names)
}
fun compute(vararg name: String) {
name.forEach { println(it) }
}
4.4、局部函数
kotlin 支持在函数中嵌套函数,被嵌套的函数称为局部函数
fun main() {
compute(“leavesC”, “country”)
}
fun compute(name: String, country: String) {
fun check(string: String) {
if (string.isEmpty()) {
throw IllegalArgumentException(“参数错误”)
}
}
check(name)
check(country)
}
五、表达式和条件循环
5.1、语句和表达式
这里需要先区分“语句”和“表达式”这两个概念。语句是可以单独执行,能够产生实际效果的代码,表现为赋值逻辑、打印操作、流程控制等形式,Java 中的流程控制(if,while,for)等都是语句。表达式可以是一个值、变量、常量、操作符、或它们之间的组合,表达式可以看做是包含返回值的语句
例如,以下的赋值操作、流程控制、打印输出都是语句,其是作为一个整体存在的,且不包含返回值
val a = 10
for (i in 0…a step 2) {
println(i)
}
再看几个表达式的例子
1 //字面表达式,返回 1
++1 //自增,返回 2
//与 Java 不同,kotlin 中的 if 是作为表达式存在的,其可以拥有返回值
fun getLength(str: String?): Int {
return if (str.isNullOrBlank()) 0 else str.length
}
5.2、If 表达式
if 的分支可以是代码块,最后的表达式作为该块的返回值
val maxValue = if (20 > 10) {
println(“maxValue is 20”)
20
} else {
println(“maxValue is 10”)
10
}
println(maxValue) //20
以下代码可以显示地看出 if 的返回值,完全可以用来替代 Java 中的三元运算符,因此 kotlin 并没有三元运算符
val list = listOf(1, 4, 10, 4, 10, 30)
val value = if (list.size > 0) list.size else null
println(value) //6
如果 if 表达式分支是用于执行某个命令,那么此时的返回值类型就是 Unit ,此时的 if 语句就看起来和 Java 的一样了
val value1 = if (list.size > 0) println(“1”) else println(“2”)
println(value1.javaClass) //class kotlin.Unit
如果将 if 作为表达式而不是语句(例如:返回它的值或者把它赋给变量),该表达式需要有 else 分支