参数默认值与函数重载
Kotlin允许对函数参数设置默认值,调用函数时默认从后到前逐个省略,可通过指定参数名明确传递的是哪个参数。
fun test(param1: Int = 123, param2: String = "") {}
test()//允许,省略2个参数
test(456)//允许,省略第二个参数
test("abc")//不允许,按照从后到前逐个省略的规则,此时相当于省略了第二个参数,所以类型不匹配
test(param2 = "abc")//允许,通过指定参数名只传递第二个参数
Kotlin中如果对函数参数设置了默认值,则会自动生成一个<方法名>$default的函数,所有省略参数传递的调用都会通过此方法补全参数再去调用原函数。
/***
* var0:执行对象,如果为静态函数则没有这个参数;
* var1:函数的第一个参数;
* var2:函数的第二个参数;
* var3:二进制数字,记录需要补全的参数位;
* var4:目前一直为null;
*/
public static void test$default(int var1, String var2, int var3, Object var4) {
if ((var3 & 1) != 0) {//第一位参数是否使用默认值
var1 = 123;
}
if ((var3 & 2) != 0) {//第二位参数是否使用默认值
var2 = "";
}
test(var1, var2);
}
//test()实际调用如下
test$default(0, (String)null, 3, (Object)null);
在Kotlin中设置了参数默认值的函数与Java的函数重载达到类似效果,但从Java环境调用此Kotlin函数时仍然需要传递所有参数,如果需要达到与Java中函数重载一致的效果,需要添加@JvmOverloads注解。添加此注解后,编译器会从后到前逐个省略有默认值的参数,自动生成多个方法。
@JvmOverloads
public static final void test(int param1, @NotNull String param2) {
Intrinsics.checkNotNullParameter(param2, "param2");
}
public static void test$default(int var0, String var1, int var2, Object var3) { ... }
@JvmOverloads
public static final void test(int param1) {
test$default(param1, (String)null, 2, (Object)null);
}
@JvmOverloads
public static final void test() {
test$default(0, (String)null, 3, (Object)null);
}
扩展函数
Kotlin能够在不继承的情况下为一个类型添加函数,使用该类型对象的点表达式调用此函数,这种机制称为扩展函数。
fun String.id(): String {...}
val aid = "a".id()
扩展并没有修改被扩展的类,只是添加了一个对应的静态方法,此静态方法的第一个参数为该类型对象,后续参数为扩展方法里的其他参数。
public static final String id(@Nullable String $this$id) {...}
扩展函数支持null的点表达式调用,只需声明被扩展的类型可为空即可。
fun String?.id(): String {...}
val aid = null.id()
Kotlin中对属性也支持扩展,扩展属性必须显示提供getter/setter方法。扩展属性与扩展函数的原理一致,并没有在被扩展类型中新增一个属性,而是在生成一个对应的静态方法,此静态方法的第一个参数为该类型对象,后续参数为getter/setter的其他参数。
var strId: String = ""//被扩展类型中没有新增属性,所有要声明一个承接属性
var String.id: String
get() {
return strId
}
set(value) {
strId = value
}
val aid = "a".id
//编译后生成
@NotNull
public static final String getId(@NotNull String $this$id) {...}
public static final void setId(@NotNull String $this$id, @NotNull String value) {...}
解构函数
<val/var> (<变量1>,<变量名2>…) = <对象>
此方法会创建多个变量,并将对象内的属性值按照声明顺序赋值给对应变量,此过程需要调用对象的componentN函数,或者通过下标查询。
<val/var> <变量1> = <对象>[0]/<对象>.get(0)/<对象>.component1()
<val/var> <变量2> = <对象>[1]/<对象>.get(1)/<对象>.component2()
……
<val/var> <变量n> = <对象>[n-1]/<对象>.get(n-1)/<对象>.componentN()
data class Student(//data class默认创建componentN函数
val name: String,
var age: Int,
var school: String
)
class Example(private val student: Student) {
fun test() {
val (s1, s2, s3) = student//通过componentN函数赋值
val (a1, a2, a3) = arrayOf("1", "2", "3")//通过下标赋值
val (it1, it2, it3, it4) = arrayListOf("1", "2", "3")//通过get方法复制
//解构声明也可以用在for循环中:
for ((a, b) in collection) { …… }
}
}
kotlin的解构函数严格按照顺序赋值,无需使用的变量使用_跳过;声明componentN函数时需要用operator关键字标记;
Lambda表达式
- lambda表达式总是括在花括号中。
- 完整语法形式的参数声明放在花括号内,并有可选的类型标注。
- 函数体跟在⼀个
->之后。 - 如果推断出的该lambda的返回类型不是
Unit,那么该lambda主体中的最后⼀个(或可能是单个)表达式会视为返回值。
val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
⼀个不带标签 return语句总是在⽤fun关键字声明的函数中返回。这意味着 lambda 表达式中的return将从包含它的函数返回,需要使⽤限定的返回语法从lambda显式返回⼀个值。
ints.filter { val shouldFilter = it > 0 return@filter shouldFilter }
在Kotlin中,如果函数的最后⼀个参数是函数,那么作为相应参数传⼊的lambda表达式可以放在圆括号之外:
val product = items.fold(1) { acc, e -> acc * e }
这种语法也称为拖尾lambda表达式。如果该lambda表达式是调⽤时唯⼀的参数,那么圆括号可以完全省略。
当lambda表达式中只有一个参数时,该参数会隐式声明为it:
ints.filter { it > 0 } // 这个字⾯值是“(it: Int) -> Boolean”类型的
如果lambda表达式的参数未使⽤,那么可以⽤下划线_取代其名称:
map.forEach { _, value -> println("$value!") }
高阶函数
Kotlin支持使用函数作为参数或者返回值,这里作为参数或返回值的函数即高阶函数,以高阶函数为参数或返回值的函数也是高阶函数。
当使用函数作为参数或返回值时,需要声明函数类型:(参数类型1,参数类型2,...)->返回类型;
fun sum(param: () -> Int): (Int) -> Int {
return fun(num: Int): Int {
return param() + num
}
}
也可以使用类型别名来表示函数类型:
typealias numberOne = () -> Int
typealias numberTwo = (Int) -> Int
fun sum(param: numberOne): numberTwo {
return fun(num: Int): Int {
return param() + num
}
}
如果匿名函数是在某个函数上定义的最后一个参数,则您可以在用于调用该函数的圆括号之外传递它:
sum {
return@sum 123
}
获得函数类型的实例的几种方式:
- lambda 表达式:
{ a, b -> a + b } - 匿名函数:
fun(s: String): Int { return s.toIntOrNull() ?: 0 } - 使用已有声明的可调用引用:
<类/实例对象>::<内部函数/构造函数/扩展函数>:String::toInt、"123"::toInt<类/实例对象>::<内部属性扩展属性>(Kotlin代理):String::length、"123"::length
内联函数
与Java中的内联一样,通过增加代码到引用处提升运行性能,但也会导致生成的代码增加。inline表明函数内联,并且会将传递给此函数的高阶函数也内联到调用处,如果不希望所有传给内联函数的高阶函数类型的参数也都内联,那么可以⽤noinline修饰符标记不希望内联的函数参数:
inline fun foo(
inlined: () -> Unit, //此函数内联
noinline notInlined: () -> Unit //此函数不内联
) { …… }
内联函数⽀持具体化的泛型类型参数:
inline fun TreeNode.findParentOfType(): T? {
var p = parent
while (p != null && p !is T) {
p = p.parent
}
return p as T?
}
当内联函数的可见性导致起可被外部module调用时(public或protected),此函数被认为是⼀个模块级的公有API。如果此时修改了此公有API内联函数的相关代码,并重新编译,而调用此内联函数的其他module没有重新编译,则其他module调用的仍是旧代码,此时就会产生兼容问题。为了消除这种由⾮公有API变更引⼊的不兼容的⻛险,公有API内联函数体内不允许使⽤⾮公有声明。使用internal修饰可见性的方法可通过添加@PublishedApi注解当作公有API使用。
inline fun foo() {
test1() //编译报错
test2() //编译报错
test3() //编译报错
test4() //编译通过
test5() //编译通过
}
internal fun test1(){ …… }
private fun test2(){ …… }
protected fun test3(){ …… }
fun test4(){ …… }
@PublishedApi
internal fun test5(){ …… }
委托函数
在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。Kotlin中通过关键字by实现委托。
类委托
<接口的子类定义> by <接口实例对象>
interface Base {
fun print(msg: String)
}
class BaseImpl : Base {
override fun print(msg: String) {
System.out.println(msg)
}
}
class Test1 : Base by BaseImpl()
class Test2(impl: Base) : Base by impl
编译后,被委托类的内接口方法将转为调用委托接口实例对象内的对应方法。
public final class Test1 implements Base {
private final BaseImpl $$delegate_0 = new BaseImpl();
public void print(@NotNull String msg) {
Intrinsics.checkNotNullParameter(msg, "msg");
this.$$delegate_0.print(msg);
}
}
public final class Test2 implements Base {
private final Base $$delegate_0;
public Test2(@NotNull Base impl) {
Intrinsics.checkNotNullParameter(impl, "impl");
super();
this.$$delegate_0 = impl;
}
public void print(@NotNull String msg) {
Intrinsics.checkNotNullParameter(msg, "msg");
this.$$delegate_0.print(msg);
}
}
属性委托
<var/val> <属性名>:<类型> by <委托对象>
被委托的属性的get方法以及set方法将被委托给这个对象的对应的get方法或set方法。属性委托不必实现任何接口,实现方式大致有以下几种:
1、声明委托对象的类型,其内部必须含有使用operator修饰的getValue和setValue方法(val修饰的属性可省略setValue方法):
class Example {
var name: String by Delegate()
}
class Delegate {
//thisRef:被委托属性所在类的实例
//property:被委托属性的基本信息
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, 这里委托了 ${property.name} 属性"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$thisRef 的 ${property.name} 属性赋值为 $value")
}
}
亦可使用kotlin中提供的ReadOnlyProperty或ReadWriteProperty接口声明委托代理对象的类型。
2、声明委托代理对象的类型,其内部必须含有使用operator修饰的provideDelegate方法,此方法返回实际委托对象:
class Example {
var name: String by ProvideDelegate()
}
class Delegate {.....}//上面的class
class ProvideDelegate {
operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): Delegate {
System.out.println("propertyName=" + property.name)
return Delegate()
}
}
由于此方法与方法1的差别主要在通过provideDelegate方法创建委托对象,可在在此过程中进行预处理。亦可直接使用kotlin中提供的PropertyDelegateProvider接口。
3、使用Lazy创建延迟初始化的不可修改的属性委托:
<val> <属性名>:<类型> by lazy <Lambda表达式>
val id: Int by lazy {
999
}
lazy是一个函数, 接受一个Lambda表达式作为参数, 返回一个Lazy <T>实例的函数,返回的实例可以作为实现延迟属性的委托。
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
//SynchronizedLazyImpl通过volatile和synchronized关键字双重检验保证初始化时线程安全
private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
private var initializer: (() -> T)? = initializer
@Volatile private var _value: Any? = UNINITIALIZED_VALUE
// final field is required to enable safe publication of constructed instance
private val lock = lock ?: this
override val value: T
get() {
val _v1 = _value
if (_v1 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST")
return _v1 as T
}
return synchronized(lock) {
val _v2 = _value
if (_v2 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST") (_v2 as T)
} else {
val typedValue = initializer!!()
_value = typedValue
initializer = null
typedValue
}
}
}
override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."
private fun writeReplace(): Any = InitializedLazyImpl(value)
}
lazy还有其他2个同名方法,可配置加锁对象以及是否使用synchronized加锁。
4、使用kotlin.properties.Delegates类内的方法创建委托对象
notNull:被委托属性不可为nullobservable:监听被委托属性修改后的状态vetoable:在被委托属性修改前进行校验、拦截
5、把属性储存在映射(Map)中
class User(val map: Map) {
val name: String by map//调用name的get方法时相当于调用map.get("name").toString()
val age: Int by map//调用age的get方法时相当于调用map.get("age").toInt()
}
这也适用于var属性,如果把只读的Map换成MutableMap。
5、委托给另一属性
一个属性可以把它的get与set方法委托给另一个属性,这种委托对于顶层和类的属性(成员和扩展)都可用:
<var/val> <属性名>:<类型> by <委托对象/this(可省略)>::<属性名>
class Delegate {
val key: String = "delegate"
var value: Int = 0
}
class Example(private val delegate: Delegate) {
val name: String by delegate::key
val id: String by this::name
var score: Int by delegate::value
}
此种委托方式会将被委托属性编译成KProperty0(PropertyReference0Impl)或KMutableProperty0(MutablePropertyReference0Impl)对象,被委托属性的set、get方法将会调用对应Impl的set、get方法。
6、局部委托属性
可以在局部代码块内使用属性委托,局部被委托属性不允许作为委托属性。
class Example(private val delegate: Delegate) {
fun test() {
val name: String by delegate::key//编译通过
var score: Int by delegate::value//编译通过
val id: String by ::name//编译报错
}
}
中缀表示法
使用infix关键字修饰的函数可以使⽤中缀表示法(忽略该调⽤的点与圆括号)调⽤。
infix fun Int.max(x: Int): Int {
return Math.max(this, x)
}
fun test() {
1 max 2 // ⽤中缀表示法调⽤该函数
1.max(2) // 等同于这样
}
中缀函数必须满⾜以下要求:
- 它们必须是成员函数或扩展函数。
- 它们必须只有⼀个参数。
- 其参数不得接受可变数量的参数且不能有默认值。
中缀函数调⽤的优先级低于算术操作符、类型转换以及rangeTo操作符。
尾递归函数
如果某个函数的末尾又调用了函数自身,此函数称为尾递归函数。Koltin中通过在fun前使用tailrec修饰符显示声明此函数为尾递归函数。如果被标注的函数满足所需的形式条件时,编译器会优化使用循环的方式替代递归。
- 在递归调⽤后有更多代码时,不能使⽤尾递归;
- 不能⽤在
try/catch/finally块中; - 也不能⽤于
open的函数。