Kotlin基础 - 第三章基础语法语法(下)

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.)
	}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值