Kotlin 3 属性和函数

624 阅读7分钟

注:当前文档为Kotlin自学总结,若什么地方含义模糊不清,敬请指教,谢谢:-)。

目录与关键词:

	- 属性
		- Getter & Setter 幕后字段field
		- 分类
			- 幕后属性  - 不符合幕后字段要求的
			- 编译期常量(const) - 顶层,对象表达式/对象声明中,没有setter,可用于注解
			- 扩展属性 - 指定类型调用,伴生对象亦可.
			- 延迟属性(lateinit) - var,无自定义访问器,非空且不能为原生类型
			- 覆盖属性(open → override)
			- 委托属性(链接:https://www.kotlincn.net/docs/reference/delegated-properties.html#委托属性)
			- 内联属性
	- 函数/方法
		- 普通函数
			- 参数数量可变函数
		- 中缀函数(infix)
		- 扩展函数 ▷
		- 高阶函数 ▷
		- 匿名函数
		- 内联函数 ▷(https://www.kotlincn.net/docs/reference/inline-functions.html#内联函数)
		- Lambda函数
			- Lambda函数
			- Lambda解构

属性 与 Getter&Setter访问器

非空类型属性必须在类构造中初始化(延迟初始化 lateinit 除外).

	[open/override/const/lateinit]	[<访问修饰符>] var/val <属性名称> [: <属性类型>] [ = <默认属性值>]
	[<属性的getter访问器>]	// var / val 时均存在
	[<属性的setter访问器>]	// val 时不存在,只能在构造函数中初始化

1. Getter & Setter 与 幕后字段field

  1. 系统自动定义访问器除非自定义;

  2. 访问器访问修饰符可以自定义;

  3. 幕后字段在自定义访问器中,不会造成调用开销;

     var param1 : String = "1"
     	get() = this.toString()
     	set(value) {
     		setDataFromString(value) // 解析字符串并赋值给其他属性
     		field = value //field为幕后字段,只能用在属性的访问器内
     	}		
    
     val param2 : Int = 4
     	get() = this.toString()
    

2. 属性分类

1. 幕后属性(前提 - 需求不符合“隐式的幕后字段”方案)

通过默认 getter 和 setter 访问私有属性会被优化,所以不会引入函数调用开销。

private var _table: Map<String, Int>? = null //私有属性
public val table: Map<String, Int>
    get() {
        if (_table == null) {
            _table = HashMap() // 类型参数已推断出
        }
        return _table ?: throw AssertionError("Set to null by another thread")
    }

2. 编译时常量(const)

const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
  1. 该属性位于顶层或Object成员;

  2. 使用String或其他原生类型值初始化;

  3. 没有getter;

  4. 可以用在注解中;

     @Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { …… }
    

3. 覆盖属性(override)

  1. 继承(override)属性自基类open属性
  2. 只能使用var 覆盖 val,反之不可,不能重复声明setter函数.

4. 延迟属性(lateinit)

  1. 不想在构造中初始化而且想要避免空检查时使用;
  2. 要求:必须是可变var,没有自定义getter/setter,不能为空,且不能是原生类型;
  3. 未初始化访问时会抛出固定异常;

5. 扩展属性 - 指定类型可调用的,类似于静态的属性

val <T> List<T>.lastIndex: Int
    get() = size - 1
  1. 扩展属性没有初始化器,只能由getter,setter来提供

     例如: 
     val Foo.bar:Int
     	get() = 2
    
     //不能使用 val Foo.bar = 1
    
  2. 伴生对象也可以有扩展属性.调用方式 = (java) 静态函数和属性的调用

     class MyClass {
         companion object { }  // 将被称为 "Companion"
     }
     
     fun MyClass.Companion.foo() {//伴生对象函数
         // ……
     }
    
     val MyClass.Companion.bar:Int
     		get() = 2
    
     //伴生对象扩展函数和属性的调用
     MyClass.foo()
     MyClass.bar
    

6. 委托属性

将属性的setter,getter委托出去去设置或获取值.

语法:

	val/var <属性名>: <类型> by <表达式>

内联属性

内联属性或其访问器使用inline修饰符,表示将访问器标记为内联访问器,与内联函数使用相同.



函数/方法

  • 函数定义时,形参表示法为Pascal表示法;

  • Unit 可作为返回类型存在,不需要显式返回,也可以什么都不用返回;

  • 单行函数体可以不使用"{}"包围,也可以省略返回值类型显式;

      fun double(x: Int): Int = x * 2
      fun double(x: Int) = x * 2
    
  • 函数可以互相嵌套,内部函数称为局部函数;

  • 函数重写:子类重写父类**open** 函数必须省略形参默认值;

  • 函数可以写作属性格式,其数据类型与传入参数类型和返回值类型有关,例如:(Int,String)->Boolean;

  • 函数可以使用泛型;

      fun <T> singletonList(item: T): List<T> {
      	// ……
      }
    

普通函数

1. 参数数量可变函数(vararg)

  1. 定义:

     	java:
    
     		public <T> List<T> listOf(T ... params){
     			//T ... params ->  params[N]
     			...
     		}
    
    
     	kotlin:
    
     		fun <T> listOf(vararg ts: T): List<T> {
     			//ts is a Array
     			//vararg = variable number of arguments
     			...
     		}				
    
  2. 使用

    • 传入相同类型的参数或参数数组整合给此数组使用伸展操作符*;

        val list = listOf(1,2,3)
        val list2 = listOf(*list,5,6,7,.....)				
        //将list中的内容整合给list2.
      

中缀函数(infix

使用**infix修饰,只有一个参数成员函数或扩展函数**,使用时可以使用中缀表示法来表示.

	//定义
	infix fun Int.shl(x: Int): Int {
		……
	}


	//使用
	val a = 1
	//val b = a.shl(0)
	val b = a shl 0 

扩展函数

  • 指定调用类型的函数,指定类型可以为空来做判空处理,函数内部的this默认调用函数的值;

      //指定类型可以为空
      fun Any?.swap(index1:Int,index2:Int){ 
      	if (this == null){
      		 ....
      	}
      }
    
      fun MutableList<Int>.swap(index1: Int, index2: Int) {
          val tmp = this[index1] // “this”对应该列表
          this[index1] = this[index2]
          this[index2] = tmp
      }
      val l = mutableListOf(1, 2, 3)
      l.swap(0, 2) // “swap()”内部的“this”得到“l”的值	
    
  • 优先级低于同种类型的普通函数;

  • 扩展函数也可以因继承open而被重写。

  • 扩展函数是静态分发的,即他们不是根据接收者类型的虚方法。 这意味着调用的扩展函数是由函数调用所在的表达式的类型来决定的, 而不是由表达式运行时求值结果决定的。

      open class D {}
      class D1 : D() {}
      
      open class C {
      	//D和D1为扩展接收者
          open fun D.foo() {println("D.foo in C")}
          open fun D1.foo() {println("D1.foo in C")}
    
      	// 调用扩展函数
          fun caller(d: D) {d.foo()}//设置的调用类型是D,所以无论传入什么都选择的是D的扩展函数.
      }
      
      class C1 : C() {
      	//D和D1为扩展接收者
          override fun D.foo() {println("D.foo in C1")}
          override fun D1.foo() {println("D1.foo in C1")}
      }
      
    
      //C和C1为分发接收者
      C().caller(D())   // 输出 "D.foo in C"
      C1().caller(D())  // 输出 "D.foo in C1" —— 分发接收者虚拟解析
      C().caller(D1())  // 输出 "D.foo in C" —— 扩展接收者静态解析	
    
    • 扩展函数所在类对象叫做分发接收者,虚拟解析;

    • 扩展函数的指定类对象叫做扩展接收者,静态解析;

  • 伴生对象也可以有扩展函数,调用时使用类名作为限定符去直接调用;

      class MyClass {
          companion object { }  // 将被称为 "Companion"
      }
      
      fun MyClass.Companion.foo() {
          // ……
      }
      
      //调用时
      MyClass.foo()
    

高阶函数

  1. 定义:

    • 高阶函数调用的函数(Lambda函数或匿名函数)可以访问/改变 闭包(外部作用域)的变量

    • 高阶函数内部可以以 函数返回值名称+"()" 使用引用的函数并使用高阶函数内参数.

    • 优点:可以减少函数定义和函数嵌套,不受泛型类型判断影响;

    • 缺点:匿名函数多次编写造成代码冗余.

        	//max()循环调用compare()
        	fun compare(a: String, b: String): Boolean{
        		return  a.length < b.length
        	}	
      
        	fun max(collection: Collection<String>): String? {
        	    var max: String? = null
        	    for (it in collection)
        	        if (max == null || compare(max, it))
        	            max = it
        	    return max
        	}
        	
      
        	↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓更改后↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
      
        	//compare()返回值为Boolean,可以引用函数max():
      
        	//简化函数解构
        	fun compare(a: String, b: String): Boolean = a.length < b.length
      
      
        	//Lambda高阶函数
        	fun <T> max(collection: Collection<T>, less: (T, T) -> Boolean): T? {
        	    var max: T? = null
        	    for (it in collection)
        	        if (max == null || less(max, it))
        	            max = it
        	    return max
        	}
      
        	↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓继续简化↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
      
        	fun <T> compare(a: T, b: T): Boolean{
      
        		//return a.length < b.length 受函数传参类型限制
        		return true...
      
        	}
      
        	//Lambda高阶函数
        	fun <T> max(collection: Collection<T>, less: (T, T) -> Boolean): T? {
        	    var max: T? = null
        	    for (it in collection)
        	        if (max == null || less(max, it))
        	            max = it
        	    return max
        	}
      
        	//使用时,根据泛型自行纠正即可,不受非匿名函数传参类型限制
        	max(strings, { a, b -> a.length < b.length })
      
  2. 使用

    • 调用成员函数,使用::和函数名一起使用;

        fun toBeSynchronized() = sharedResource.operation()
        
        val result = lock(lock, ::toBeSynchronized)
      
    • 传入Lambda表达式

        lock(lock,{sharedResource -> sharedResource.operation()})
        max(strings, { a, b -> a.length < b.length })
      
    • 最后一个参数是函数也可以这么表示

        lock(lock) {sharedResource -> sharedResource.operation()}
        max(strings) {a, b -> a.length < b.length }
      
    • Lambda表达式中传入参数但是没有使用时使用"_"

        map.forEach { _, value -> println("$value!") }
      
    • 高阶函数只有一个函数参数,调用中的圆括号可以完全省略。

        val doubled = ints.map { value -> value * 2 }
      
    • 高阶函数只有一个函数参数,并且该参数只有一个参数时省略声明及->,只使用it字段:

        val doubled = ints.map { it * 2 }
      

匿名函数

	fun [<指定接收(调用)者类型>.](<参数名1>:<类型>,<参数名2>:<类型>,....):<返回值类型>{
			//函数体
	}
		
	fun [<指定接收(调用)者类型>.](<参数名1>:<类型>,<参数名2>:<类型>,....):<返回值类型> = <短小函数体>

内联函数(高阶函数的变形)

高阶函数使用时,将每个函数均作为一个对象引用,会生成一个闭包,调用的函数内部可以访问到变量,变量的内存分配和虚拟调用会引入运行时间开销.

函数内联

  • 通过内联化Lambda函数表达式可以消除开销.编译器没有为参数创建一个函数对象并生成一个调用.使用inline修饰符标记函数内联化Lambda函数表达式即内联到调用处.

  • 内联可能导致生成的代码增加,但是如果我们使用得当(不内联大函数),它将在性能上有所提升,尤其是在循环中的“超多态(megamorphic)”调用处。

      inline fun lock<T>(lock: Lock, body: () -> T): T {
          // ……
      }
    

禁止内联

  • 内联高阶函数传入多个函数参数时,存在只有一些不希望被内联时,用 noinline修饰符标记;

  • 内联函数内没有内联的参数函数时,会产生警告;

  • Lambda表达式内部裸return不被允许,除非Lambda表达式传给的函数是内联的,此时return返回称为非局部返回.

      fun foo() {
          ordinaryFunction {
              return // 错误:不能使 `foo` 在此处返回
          }
      }
    
      //如果 lambda 表达式传给的函数是内联的,该 return 也可以内联,所以它是允许的
      fun foo() {
          inlineFunction {
              return // OK:该 lambda 表达式是内联的
          }
      }
    
  • 一些内联函数可能调用传给它们的不是直接来自函数体、而是来自另一个执行上下文的 lambda 表达式参数,例如来自局部对象或嵌套函数。在这种情况下,该 lambda 表达式中也不允许非局部控制流。为了标识这种情况,该 lambda 表达式参数需要用 crossinline 修饰符标记:

      inline fun f(crossinline body: () -> Unit) {
          val f = object: Runnable {
              override fun run() = body()
          }
          // ……
      }
    

具体化的类型参数


Lambda函数表达式

1. Lambda函数表达式

有返回值的函数可以采用Lambda表达式表示,以被高阶函数使用.

在高阶函数中函数作为参数引用,就要用属性方式表示.

示例1:

fun <T> max(collection: Collection<T>, less: (T, T) -> Boolean): T? {
    var max: T? = null
    for (it in collection)
        if (max == null || less(max, it))
            max = it
    return max
}

//原函数:
fun less(x : Int,y : Int):Boolean{
	return x < y
}
↓↓↓↓↓↓↓↓↓↓↓↓↓变形↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
fun less(x : Int,y : Int):Boolean = x < y	
↓↓↓↓↓↓↓↓↓↓↓↓↓匿名↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
{ x: Int, y: Int -> x < y }
↓↓↓↓↓↓↓↓↓↓↓↓↓做参数↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
val less = { x: Int, y: Int -> x < y }
↓↓↓↓↓↓↓↓↓↓↓↓↓提类型↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
val less:(Int , Int) -> Boolean = { x, y -> x < y }

(自总结) 高阶函数只有一个函数参数,使用时可以省略函数参数的"()"包裹.

示例2(反拆):

ints.filter {
    val shouldFilter = it > 0 //使用it并且是单参数,可以直接看出是函数
    shouldFilter
}

↓↓↓↓↓↓↓↓↓↓↓↓↓ + 类型 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
ints.filter {
    val shouldFilter:(Int) -> Boolean  = it > 0 
    return@filter shouldFilter
}
↓↓↓↓↓↓↓↓↓↓↓↓↓ 提 Lambda ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
ints.filter {
    val shouldFilter:(Int) -> Boolean  ={ it -> it > 0 }
    return@filter shouldFilter
}

↓↓↓↓↓↓↓↓↓↓↓↓↓ 改函数 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
ints.filter {//当前高阶函数仅有一个函数参数
    fun shouldFilter(it : Int):Boolean{ //内部函数? 属性内部没有内部函数
		return it > 0 
	}
    return@filter shouldFilter
}

↓↓↓↓↓↓↓↓↓↓↓↓↓ 函数还原 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
fun List<Int>.filter( method:(Int) -> Boolean ):Boolean{

	....

}

Lambda表达式中,高阶函数的函数参数中有参数未使用可以用"_"取代名称.

map.forEach { _, value -> println("$value!") }

2. Lambda 解构

你可以对 lambda 表达式参数使用解构声明语法。 如果 lambda 表达式具有 Pair 类型(或者 Map.Entry 或任何其他具有相应 componentN 函数的类型)的参数,那么可以通过将它们放在括号中来引入多个新参数来取代单个新参数:

	map.mapValues { entry -> "${entry.value}!" }
	map.mapValues { (key, value) -> "$value!" }

注意声明两个参数和声明一个解构对来取代单个参数之间的区别:

	{ a //-> …… } // 一个参数
	{ a, b //-> …… } // 两个参数
	{ (a, b) //-> …… } // 一个解构对
	{ (a, b), c //-> …… } // 一个解构对以及其他参数

如果解构的参数中的一个组件未使用,那么可以将其替换为下划线,以避免编造其名称:

	map.mapValues { (_, value) -> "$value!" }

你可以指定整个解构的参数的类型或者分别指定特定组件的类型:

	map.mapValues { (_, value): Map.Entry<Int, String> -> "$value!" }
	
	map.mapValues { (_, value: String) -> "$value!" }

本文参考自 Kotlin语言中心站