Kotlin中语法

#### [kotlin官方文档 https://www.kotlincn.net/docs/reference/](https://www.kotlincn.net/docs/reference/) ####
和java程序一样,kotlin程序经过编译器编译完成之后也是成字节码文件,我们可以通过 Androidstudio的 Tools -> Kotlin -> Show Kotlin Bytecode 查看对应的字节码,当然还可以点击 Decompile转换成对应的java代码

kotlin中的常量和变量
常量
- val [变量名] = [value] ,值类型
- 类似于java中的final,不可以重复赋值
- 运行时常量 : val x = getX() ,可以通过反射技术修改变量的值
- 编译期常量 : const val x =2 ,编译后的字节码直接使用常量代替,编译后无法修改,更安全
变量
- var [变量名] = [value]
- var x = 154
- 再次赋值 x=200
类型推导
- 编译器可以推到量的类型
- val = “中国” //推到为String类型
- val = 5 //推到为Int类型
- val = “age” + 5 //推到为字符串类型
函数
开发中函数应当符合单一原则
函数声明
kotlin中声明函数的格式是:
Kotlin 中的函数使用 fun 关键字声明:
fun 函数名称(参数) :返回值{}
-
一、有返回值的函数声明
/** * kotlin中函数声明方法 * fun 声明函数的固定写法 * getSum 函数的名字,这个可以任意写,但请不要随意写 * a:Int 函数中的参数,其中a为参数名字,即所谓的形参,Int为形参类型,:为分隔符 * b:Int 解释同a:Int * 参数后面的:Int,函数的返回值类型 * 大括弧中间的即为函数体 */ fun getSum(a:Int,b:Int):Int{ return a+b }
-
二、无返回值的函数声明
/** * kotlin中函数声明方法 * fun 声明函数的固定写法 * getSum 函数的名字,这个可以任意写,但请不要随意写 * a:Int 函数中的参数,其中a为参数名字,即所谓的形参,Int为形参类型,:为分隔符 * b:Int 解释同a:Int * 参数后面的:Unit,代表没有返回值,相当于java中的void * 大括弧中间的即为函数体 */ fun getSum(a:Int,b:Int):Unit{ }
注意如果函数没有返回值,默认返回值为
Unit
当没有返回值的时候,有关返回值的部分可以不写,所以你也可以简写为:
fun getSum(a:Int,b:Int){
}
- 备注:
kotlin中所有的函数默认都是 public final
修饰的,也就是说默认不能被重写
在上面的函数中,其实函数是这样的
//public fianl编译器会默认加上
public final fun getSum(a:Int,b:Int):Int{
return a+b
}
如果我们想声明一个私有函数呢?
私有函数的声明
private fun getSum(a:Int,b:Int):Int{
return a+b
}
在fun之前加上权限修饰符即可
以表达式作为函数体
fun getSum(a:Int=1,b:Int=2) = a+b
fun getSum(a:Int=1,b:Int=2) = "大王让我来巡山"
返回值类型可以根据返回值自动推导
匿名函数
函数没有名称直接赋值给变量的形式,成为匿名函数
基本格式
var 变量名 = fun(参数1:类型1,参数2:类型2):[返回值类型]{
函数体
}
程序举例
//定义函数赋值给变量
var sum = fun(a: Int, b: Int): Int {
return a + b
}
//使用时调用变量并传参
val a = 10
val b = 20
println("$a + $b = ${sum(a,b)}")
运行结果
10 + 20 = 30
函数用法
调用函数使用传统的方法:
val result = double(2)
调用成员函数使用点表示法
Stream().read() // 创建类 Stream 实例并调用 read()
参数
函数参数使用 Pascal 表示法定义,即 name: type。参数用逗号隔开。每个参数必须有显式类型:
fun powerOf(number: Int, exponent: Int) { /*……*/ }
默认参数
-
函数参数可以有默认值,当省略相应的参数时使用默认值。与其他语言相比,这可以减少重载数量:
fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size) { /*……*/ }
默认值通过类型后面的 = 及给出的值来定义。
-
覆盖方法总是使用与基类型方法相同的默认参数值。 当覆盖一个带有默认参数值的方法时,必须从签名中省略默认参数值:
open class A { open fun foo(i: Int = 10) { /*……*/ } } class B : A() { override fun foo(i: Int) { /*……*/ } // 不能有默认值 }
-
如果一个默认参数在一个无默认值的参数之前,那么该默认值只能通过使用具名参数调用该函数来使用:
fun foo(bar: Int = 0, baz: Int) { /*……*/ } foo(baz = 1) // 使用默认值 bar = 0
-
如果在默认参数之后的最后一个参数是 lambda 表达式,那么它既可以作为具名参数在括号内传入,也可以在括号外传入:
fun foo(bar: Int = 0, baz: Int = 1, qux: () -> Unit) { /*……*/ } foo(1) { println("hello") } // 使用默认值 baz = 1 foo(qux = { println("hello") }) // 使用两个默认值 bar = 0 与 baz = 1 //如果调用不传函数的参数 () 可以省略 foo { println("hello") } // 使用两个默认值 bar = 0 与 baz = 1
具名参数
可以在调用函数时使用具名的函数参数。当一个函数有大量的参数或默认参数时这会非常方便。
给定以下函数:
fun reformat(str: String,
normalizeCase: Boolean = true,
upperCaseFirstLetter: Boolean = true,
divideByCamelHumps: Boolean = false,
wordSeparator: Char = ' ') {
/*……*/
}
我们可以使用默认参数来调用它:
reformat(str)
然而,当使用非默认参数调用它时,该调用看起来就像:
reformat(str, true, true, false, '_')
-
使用具名参数我们可以使代码更具有可读性:
reformat(str, normalizeCase = true, upperCaseFirstLetter = true, divideByCamelHumps = false, wordSeparator = '_' )
并且如果我们不需要所有的参数:
reformat(str, wordSeparator = '_')
当一个函数调用混用位置参数与具名参数时,所有位置参数都要放在第一个具名参数之前。例如,允许调用 f(1, y = 2)
但不允许 f(x = 1, 2)
。
可以通过使用星号操作符将可变数量参数(vararg) 以具名形式传入:
fun foo(vararg strings: String) { /*……*/ }
foo(strings = *arrayOf("a", "b", "c"))
对于 JVM 平台:在调用 Java 函数时不能使用具名参数语法,因为 Java 字节码并不总是保留函数参数的名称。
返回 Unit 的函数
如果一个函数不返回任何有用的值,它的返回类型是 Unit。Unit 是一种只有一个值——Unit 的类型。这个值不需要显式返回:
fun printHello(name: String?): Unit {
if (name != null)
println("Hello ${name}")
else
println("Hi there!")
// `return Unit` 或者 `return` 是可选的
}
Unit 返回类型声明也是可选的。上面的代码等同于:
fun printHello(name: String?) { …… }
- 单表达式函数
当函数返回单个表达式时,可以省略花括号并且在 = 符号之后指定代码体即可:
fun double(x: Int): Int = x * 2
当返回值类型可由编译器推断时,显式声明返回类型是可选的:
fun double(x: Int) = x * 2
- 显式返回类型
具有块代码体的函数必须始终显式指定返回类型,除非他们旨在返回 Unit,在这种情况下它是可选的。 Kotlin 不推断具有块代码体的函数的返回类型,因为这样的函数在代码体中可能有复杂的控制流,并且返回类型对于读者(有时甚至对于编译器)是不明显的。
可变数量的参数(Varargs)
函数的参数(通常是最后一个)可以用 vararg 修饰符标记:
fun <T> asList(vararg ts: T): List<T> {
val result = ArrayList<T>()
for (t in ts) // ts is an Array
result.add(t)
return result
}
println(asList("a","c","c"))
运行结果
[a, c, c]
允许将可变数量的参数传递给函数:
val list = asList(1, 2, 3)
在函数内部,类型 T 的 vararg 参数的可见方式是作为 T 数组,即上例中的 ts 变量具有类型 Array 。
只有一个参数可以标注为 vararg。如果 vararg 参数不是列表中的最后一个参数, 可以使用具名参数语法传递其后的参数的值,或者,如果参数具有函数类型,则通过在括号外部传一个 lambda。
当我们调用 vararg-函数时,我们可以一个接一个地传参,例如 asList(1, 2, 3),或者,如果我们已经有一个数组并希望将其内容传给该函数,我们使用伸展(spread)操作符(在数组前面加 *):
val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4)
中缀表示法
-
标有 infix 关键字的函数也可以使用中缀表示法(忽略该调用的点与圆括号)调用。中缀函数必须满足以下要求:
- 它们必须是成员函数或扩展函数;
- 它们必须只有一个参数;
- 其参数不得接受可变数量的参数且不能有默认值。
示例
infix fun Int.shl(x: Int): Int { …… }
// 用中缀表示法调用该函数
1 shl 2
// 等同于这样
1.shl(2)
中缀函数调用的优先级低于算术操作符、类型转换以及 rangeTo 操作符。 以下表达式是等价的:
1 shl 2 + 3 等价于 1 shl (2 + 3)
0 until n * 2 等价于 0 until (n * 2)
xs union ys as Set<*> 等价于 xs union (ys as Set<*>)
另一方面,中缀函数调用的优先级高于布尔操作符 && 与 ||、is- 与 in- 检测以及其他一些操作符。这些表达式也是等价的:
a && b xor c 等价于 a && (b xor c)
a xor b in c 等价于 (a xor b) in c
完整的优先级层次结构请参见其语法参考。
请注意,中缀函数总是要求指定接收者与参数。当使用中缀表示法在当前接收者上调用方法时,需要显式使用 this;不能像常规方法调用那样省略。这是确保非模糊解析所必需的。
示例代码
class MyStringCollection {
var list = ArrayList<String>()
fun build():MyStringCollection {
this add "abc" // 正确
add("abc") // 正确
//add "abc" // 错误:必须指定接收者
println(2 sh2 2)
return this
}
infix fun Int.sh2(x: Int): Int {
println(this)
return this * x
}
infix fun add(s: String) {
list.add(s)
}
fun getList2():ArrayList<String>{
return list
}
}
//调用
println(MyStringCollection().build().getList2())
运行结果
4
[abc, abc]
函数作用域
在 Kotlin 中函数可以在文件顶层声明,这意味着你不需要像一些语言如 Java、C# 或 Scala 那样需要创建一个类来保存一个函数。此外除了顶层函数,Kotlin 中函数也可以声明在局部作用域、作为成员函数以及扩展函数。
1.局部函数
Kotlin 支持局部函数,即一个函数在另一个函数内部:
fun printName(name: String) {
fun printName(name: String, age: Int) {
println("姓名:$name , 年龄 : $age")
}
printName(name, 24)
}
运行结果
姓名:王刚 , 年龄 : 24
2.局部函数可以访问外部函数(即闭包)的局部变量,所以在上例中,visited 可以是局部变量:
fun dfs(graph: Graph) {
val visited = HashSet<Vertex>()
fun dfs(current: Vertex) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v)
}
dfs(graph.vertices[0])
}
成员函数
成员函数是在类或对象内部定义的函数:
class Sample() {
fun foo() { print("Foo") }
}
成员函数以点表示法调用:
Sample().foo() // 创建类 Sample 实例并调用 foo
泛型函数
函数可以有泛型参数,通过在函数名前使用尖括号指定:
fun <T> singletonList(item: T): List<T> { /*……*/ }
内联函数
内联函数在 这里 讲述。
扩展函数
扩展函数在其自有章节讲述。
高阶函数和 Lambda 表达式
高阶函数和 Lambda 表达式在其自有章节讲述。
尾递归函数
Kotlin 支持一种称为尾递归的函数式编程风格。 这允许一些通常用循环写的算法改用递归函数来写,而无堆栈溢出的风险。 当一个函数用 tailrec 修饰符标记并满足所需的形式时,编译器会优化该递归,留下一个快速而高效的基于循环的版本:
val eps = 1E-10 // "good enough", could be 10^-15
tailrec fun findFixPoint(x: Double = 1.0): Double
= if (Math.abs(x - Math.cos(x)) < eps) x else findFixPoint(Math.cos(x))
这段代码计算余弦的不动点(fixpoint of cosine),这是一个数学常数。 它只是重复地从 1.0 开始调用 Math.cos,直到结果不再改变,对于这里指定的 eps 精度会产生 0.7390851332151611 的结果。最终代码相当于这种更传统风格的代码:
val eps = 1E-10 // "good enough", could be 10^-15
private fun findFixPoint(): Double {
var x = 1.0
while (true) {
val y = Math.cos(x)
if (Math.abs(x - y) < eps) return x
x = Math.cos(x)
}
}
要符合 tailrec 修饰符的条件的话,函数必须将其自身调用作为它执行的最后一个操作。在递归调用后有更多代码时,不能使用尾递归,并且不能用在 try/catch/finally 块中。目前在 Kotlin for JVM 与 Kotlin/Native 中支持尾递归。
Lambda 表达式与匿名函数
lambda 表达式与匿名函数是“函数字面值”,即未声明的函数, 但立即做为表达式传递。考虑下面的例子:
max(strings, { a, b -> a.length < b.length })
函数 max 是一个高阶函数,它接受一个函数作为第二个参数。 其第二个参数是一个表达式,它本身是一个函数,即函数字面值,它等价于以下具名函数:
fun compare(a: String, b: String): Boolean = a.length < b.length
Lambda 表达式语法
Lambda 表达式的完整语法形式如下:
val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
lambda 表达式总是括在花括号中, 完整语法形式的参数声明放在花括号内,并有可选的类型标注, 函数体跟在一个 -> 符号之后。如果推断出的该 lambda 的返回类型不是 Unit,那么该 lambda 主体中的最后一个(或可能是单个)表达式会视为返回值。
如果我们把所有可选标注都留下,看起来如下:
val sum = { x, y -> x + y }
- 传递末尾的 lambda 表达式
在 Kotlin 中有一个约定:如果函数的最后一个参数是函数,那么作为相应参数传入的 lambda 表达式可以放在圆括号之外:
val product = items.fold(1) { acc, e -> acc * e }
这种语法也称为拖尾 lambda 表达式。
如果该 lambda 表达式是调用时唯一的参数,那么圆括号可以完全省略:
run { println("...") }
it:单个参数的隐式名称
一个 lambda 表达式只有一个参数是很常见的。
如果编译器自己可以识别出签名,也可以不用声明唯一的参数并忽略 ->。 该参数会隐式声明为 it:
ints.filter { it > 0 } // 这个字面值是“(it: Int) -> Boolean”类型的
- 从 lambda 表达式中返回一个值
我们可以使用限定的返回语法从 lambda 显式返回一个值。 否则,将隐式返回最后一个表达式的值。
因此,以下两个片段是等价的:
ints.filter {
val shouldFilter = it > 0
shouldFilter
}
ints.filter {
val shouldFilter = it > 0
return@filter shouldFilter
}
这一约定连同在圆括号外传递 lambda 表达式一起支持 LINQ-风格 的代码:
strings.filter { it.length == 5 }.sortedBy { it }.map { it.toUpperCase() }
如果 lambda 表达式的参数未使用,那么可以用下划线取代其名称:
下划线用于未使用的变量(自 1.1 起)
map.forEach { _, value -> println("$value!") }
在 lambda 表达式中解构(自 1.1 起)
在 lambda 表达式中解构是作为解构声明的一部分描述的。
下划线用于未使用的变量(自 1.1 起)
在 lambda 表达式中解构是作为解构声明的一部分描述的。
匿名函数
上面提供的 lambda 表达式语法缺少的一个东西是指定函数的返回类型的能力。在大多数情况下,这是不必要的。因为返回类型可以自动推断出来。然而,如果确实需要显式指定,可以使用另一种语法: 匿名函数 。
fun(x: Int, y: Int): Int = x + y
匿名函数除了其名称省略了,看起来非常像一个常规函数声明。其函数体可以是表达式(如上所示)或代码块:
fun(x: Int, y: Int): Int {
return x + y
}
参数和返回类型的指定方式与常规函数相同,匿名函数中能够从上下文推断出的参数类型可以省略:
ints.filter(fun(item) = item > 0)
匿名函数的返回类型推断机制与正常函数一样:对于具有表达式函数体的匿名函数将自动推断返回类型,而具有代码块函数体的返回类型必须显式指定(或者已假定为 Unit)。
请注意,匿名函数参数总是在括号内传递。 允许将函数留在圆括号外的简写语法仅适用于 lambda 表达式。
Lambda表达式与匿名函数之间的另一个区别是非局部返回的行为。一个不带标签的 return 语句总是在用 fun 关键字声明的函数中返回。这意味着 lambda 表达式中的 return 将从包含它的函数返回,而匿名函数中的 return 将从匿名函数自身返回。
闭包
Lambda 表达式或者匿名函数(以及局部函数和对象表达式) 可以访问其 闭包 ,即在外部作用域中声明的变量。 在 lambda 表达式中可以修改闭包中捕获的变量:
var count = 0
var list = listOf(10, 12, 13, 154)
list.filter { it > 12 }.forEach {
if (it % 2 == 0) {
count += it
}
}
println(count);
带有接收者的函数字面值
带有接收者的函数类型,例如 A.(B) -> C,可以用特殊形式的函数字面值实例化—— 带有接收者的函数字面值。
如上所述,Kotlin 提供了调用带有接收者(提供接收者对象)的函数类型实例的能力。
在这样的函数字面值内部,传给调用的接收者对象成为隐式的this,以便访问接收者对象的成员而无需任何额外的限定符,亦可使用 this 表达式 访问接收者对象。
这种行为与扩展函数类似,扩展函数也允许在函数体内部访问接收者对象的成员。
这里有一个带有接收者的函数字面值及其类型的示例,其中在接收者对象上调用了 plus :
var sum2: Int.(Int) -> Int = { othor -> plus(othor) }
var sum3: Int.(Int) -> Int = { othor -> this + othor }
//这个就是上面我们介绍的中缀函数 和 lambda表达式的结合
println(3.sum2(2))
println(3.sum3(2))
匿名函数语法允许你直接指定函数字面值的接收者类型。 如果你需要使用带接收者的函数类型声明一个变量,并在之后使用它,这将非常有用。
比如我们这里显示的指定接收参数为 other 并在后面直接使用这个变量
val sum = fun Int.(other: Int): Int = this + other
当接收者类型可以从上下文推断时,lambda 表达式可以用作带接收者的函数字面值。 One of the most important examples of their usage is type-safe builders:
class HTML {
fun body() { …… }
}
fun html(init: HTML.() -> Unit): HTML {
val html = HTML() // 创建接收者对象
html.init() // 将该接收者对象传给该 lambda
return html
}
html { // 带接收者的 lambda 由此开始
body() // 调用该接收者对象的一个方法
}
关系型数据库 非关系型数据库
成员方法、成员变量
一、首先看一个简单类和方法的实现
package net.println.kotlin.chapters
/**
* @author:wangdong
* @description:
*/
/**定义一个女生类*/
class girl(var character: String, var appearance: String, var age: Int){
fun sing(songName: String){
//具体实现
println("正在唱歌:$songName")
}
fun dance(danceName: String){
//具体实现
println("正在跳舞:$danceName")
}
}
fun main(args: Array<String>) {
val linda = girl("温柔","漂亮",18)
linda.sing("北京欢迎你")
linda.dance("天鹅湖")
}
二、类和成员变量、成员方法
先看一下Java中的类和成员变量、方法
package net.println.kotlin.chapters;
/**
* @author:wangdong
* @description: Java中的类与成员变量
*/
public class JavaPerson {
private String name;
private Integer age;
//java中没有初始化就是null
private String school;
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
//如果只允许它的子类才能设置,需要将public改为protected
protected void setName(String name) {
this.name = name;
}
}
再看一下Kotlin中的类和成员变量
package net.println.kotlin.chapters
/**
* @author:wangdong
* @description:kotlin中的类和成员变量
*/
class Girl{
var age = 18
get() {
println("我今年 $age 岁")
return field
}
//加protected只允许子类设置
protected set(value) {
println("我今年 $value 岁")
}
}
三、kontlin的类相关成员变量、方法的实例
package net.println.kotlin.chapters
/**
* @author:wangdong
* @description:kotlin中的类和成员变量
*/
/**类成员*/
/**
* 属性:或者说成员变量,类范围内的变量
* 方法:或者说成员函数,类范围内的函数
*/
/**函数和方法的区别*/
/**
* 函数强调功能本身,不考虑从属
* 方法的称呼通常是从类的本身角度出发的
*/
/**定义方法*/
/**
* class Hello{
* fun sayHello(name: String) = println("Hello,$name")
* }
*/
/**定义属性*/
/**
* 在构造方法参数中 var和val修饰的都是属性
* 类内部也可以定义属性
*
* class Hello(val aField: Int,bField: Int){
* var anotherField: Float = 3f
* }
*/
/**属性访问控制*/
/**
* val a: Int = 0
* get() = field
* 不能定义set,因为val是不可变的
*
* var b: Float = of
* set(value){
* field = value
* }
*/
/**属性的初始化步骤*/
/**
* 属性的初始化,尽量在构造方法中完成
* 无法在构造方法中进行初始化,尝试降级为局部变量
* var 用lateinit延迟初始化,val用lazy延迟
* 可空类型,不建议使用null
*/
/**
* class Hello
*/
class X
class Girl{
var b = 0
//延迟初始化lateinit,只能放在var中
lateinit var c: String
lateinit var d: X
//常量值可以使用委托代理方式初始化
val e: X by lazy {
println("init X")
X()
}
var cc: String? = null
}
fun main(args: Array<String>) {
println("开始")
val a = Girl()
println("初始化")
println(a.b)
println(a.e)
//延迟初始化,用的时候没有初始化访问会报错
//kotlin.UninitializedPropertyAccessException: lateinit property c has not been initialized
//println(a.c)
//初始化后就不会报错了
a.d = X()
println(a.d)
//输出null
println(a.cc)
//用的时候编译器就不允许了,a.cc.xxx后面为灰色不能使用
//println(a.cc.)
}