参考:第一行代码第三版
目录
标准函数with,run,apply
with:接受两个参数,第一个参数是对象,第二个参数是一个Lambda表达式,默认对第一个参数进行操作,并把最后一行作为返回值
如下代码中,在{}中都是默认对StringBuilder进行操作
val fruitList = listOf("Apple", "banana", "Orange")
val result = with(StringBuilder()) {
append("Start eating fruits\n")
for (fruit in fruitList) {
append(fruit).append("\n")
}
append("finished")
toString()
}
println(result)
run: 和with几乎一样
val fruitList = listOf("Apple", "banana", "Orange")
val result = StringBuilder().run {
append("Start eating fruits\n")
for (fruit in fruitList) {
append(fruit).append("\n")
}
append("finished")
toString()
}
println(result)
apply :与之前的差距在于,会返回对象本身,无法指定返回值
val fruitList = listOf("Apple", "banana", "Orange")
val result = StringBuilder().apply {
append("Start eating fruits\n")
for (fruit in fruitList) {
append(fruit).append("\n")
}
append("finished")
}
println(result.toString())
静态方法
kotlin极度弱化了静态方法这个概念,提供了以下几种类似静态方法的方法
1.单例类
object Util {
fun getInstance(){
println("单例类")
}
}
2.想让某一个方法变为静态方法,本质是伴生类中的实例方法,但作用和静态方法相同
class Util {
companion object{
fun func(){
println("do something")
}
}
}
3.顶层方法
创建一个Kotlin的File类型的文件,写入一些方法,如
fun func1(){
println("do something")
}
则被默认为是一个顶层方法,会被编译成静态方法
在kotlin中调用,直接调用方法名
func1()
在java中调用,开头必须是大写,且最后会加上Kt,如文件名为Helper.kt,在java中调用就变为下面
public class test {
public static void main(String[] args) {
HelperKt.func1();
}
}
延迟初始化
lateinit关键字,使用这个就是告诉编译器说我会晚些对这个变量进行初始化,这样就不用一开始的时候将它赋值为null了
使用的风险是:如果忘记了对它进行初始化还是会报异常
private lateinit var rvAdapter: RvAdapter;
另外如果不想对一个变量进行重复的初始化,可以这样写,这样在初始化之前会判断一下是否已经初始化了,如果初始化了就不执行
if (!::rvAdapter.isInitialized) {
rvAdapter = RvAdapter(this, list)
}
密封类
当我们写接口时,而在when语句中,必须加上else这条语句
interface Result
class Success(val msg: String) : Result
class Failure(val error: Exception) : Result
fun getResultMsg(result: Result) = when (result) {
is Success -> result.msg
is Failure -> result.error.message
else -> throw IllegalArgumentException()
}
而使用密封类后,就可以把else去掉了,而且不会漏掉一些条件分支
sealed class Result
class Success(val msg: String) : Result()
class Failure(val error: Exception) : Result()
fun getResultMsg(result: Result) = when (result) {
is Success -> result.msg
is Failure -> result.error.message
}
扩展函数
相当于在String类当中加入了一个统计字母个数的函数方法
fun String.lettersCount() :Int{
var count = 0
for (char in this){
if (char.isLetter()){
count ++
}
}
return count
}
然后直接调用就可以
fun main(){
val count = "asd123".lettersCount()
println(count)
}
repeat函数和random函数
下面这个代码就是模拟王者荣耀中的水晶抽奖
repeat(30) {
val n = (1..365).random()
if (n <= 1) {
println("恭喜你获奖!!! $n")
} else {
println("很遗憾,您没有获奖,就差一点就获奖了,继续加油呀$n")
}
}
运算符重载
+就对应了.plus函数,minus为-,times为*,div为/
class Money(val value: Int) {
operator fun plus(money: Money): Money {
val sum = value + money.value
return Money(sum)
}
operator fun minus(money: Money):Money{
val sum = value - money.value;
return Money(sum)
}
operator fun times(money: Money):Money{
var sum = value * money.value
return Money(sum)
}
operator fun div(money: Money):Money{
var sum = value / money.value
return Money(sum)
}
}
fun main() {
val money = Money(4)
val money1 = Money(2)
val money2 = money + money1
println(money2.value)
val money3 = money - money1
println(money3.value)
val money4 = money * money1
println(money4.value)
val money5 = money / money1
println(money5.value)
}
扩展函数 + 重载运算符
operator fun String.times(n:Int):String{
val builder = StringBuilder()
repeat(n){
builder.append(this)
}
return builder.toString()
}
val s = "abc" * 3
println(s)
顺便说一下,kotlin中已经提供了一个重复的方法
val repeat = "abc".repeat((1..20).random())
println(repeat)
高阶函数
如果一个函数接收另一个函数作为参数,或者返回值的类型为另一个函数,那么该函数就是高阶函数
fun num1AndNum2(num1: Int, num2: Int, func: (Int, Int) -> Int): Int {
var result = func(num1, num2)
return result
}
fun add(num1: Int,num2: Int): Int {
return num1 + num2
}
fun main() {
val num1AndNum2 = num1AndNum2(1, 2, ::add)
println(num1AndNum2)
}
如上:num1Andnum2的第三个参数接收一个函数类型,所以我们去定义一个函数与其接收的参数类型相同,并传入,且函数引用的写法为::函数名
也可以用Lambda写法代替add函数
可以看到lambda的可以完整地表达一个函数的参数声明和返回值声明,最后一行代码会自动作为返回值
fun num1AndNum2(num1: Int, num2: Int, func: (Int, Int) -> Int): Int {
var result = func(num1, num2)
return result
}
fun main() {
val num1AndNum2 = num1AndNum2(2,3) { n1, n2 ->
n1 + n2
}
println(num1AndNum2)
}
如下:又定义了一个高阶函数,StringBuilder.表示这个函数类型是定义在StringBuilder类中的
这样传入的Lambda表达式就会自动拥有StringBuilder的上下文
这样build就实现了和上面讲得apply函数一样的功能,只不过目前只适用于StringBuilder这个类
fun StringBuilder.build(func: StringBuilder.() -> Unit): StringBuilder {
func()
return this
}
fun main() {
val list = listOf("Apple", "Pear", "Peach", "Banana")
val string = StringBuilder().build {
append("fruits list : \n")
for (fruit in list) {
append("$fruit ")
}
}
println(string)
}
高阶函数的实现原理
第三个参数就是一个接口,Lambda表达式转换为一个匿名类的实现方式,每调用一次Lambda表达式,就会创建一个新的匿名类实例,这会造成额外的内存和性能开销,这时Kotlin引入了内联函数
内联函数
只需要在高阶函数之前加上inline即可,内联的函数类型参数在编译的时候会被进行代码替换,这样就不存在运行时的开销了
//.()表示在StringBuilder作用域下的函数
inline fun StringBuilder.build(func: StringBuilder.() -> Unit): StringBuilder {
func()
return this
}
noinline
如果我们一个高阶函数中有多个函数类型的参数,那么如果我们给函数加上了inline关键字,那么kotlin编译器会自动将所有引用的Lambda表达式全部进行内联,这时我们如果不想让某一个函数被内联,就可以在前面加上noinline
inline fun inlineTest(block1: () -> Unit, noinline block2: () -> Unit) {
}
内联函数和非内联函数的区别:
1.内联的函数类型参数在编译的时候会被进行代码替换,因此它没有真正的参数类型,非内联的函数类型参数可以自由地传递给其他任何函数,而内联的函数类型参数只允许传递给另外一个内联函数,这就是它最大的局限性.
2.内联函数可以用return直接返回,即退出main,而非内联函数只能局部返回,即从函数退回到main函数
当非内联函数:
fun printString(str: String, func: (String) -> Unit) {
func(str)
}
fun main() {
println("main start")
val str = ""
printString(str){
println("lambda start")
if (it.isEmpty()) return@printString
println(it)
println("lambda end")
}
println("main end")
}
运行结果:
当为内联函数
inline fun printString(str: String, func: (String) -> Unit) {
func(str)
}
fun main() {
println("main start")
val str = ""
printString(str){
println("lambda start")
if (it.isEmpty()) return
println(it)
println("lambda end")
}
println("main end")
}
运行结果
从两次的结果来看,return可以直接返回main函数,这是因为lambda中的代码会被替代为函数中的实现,所以相当于在main写了实现方法
crossinline
有一种情况 : 当我们在内联的高阶函数中写Lambda函数时,需要在参数前加上crossinline,
如果不加的话,因为我们知道lambda表达式是一个匿名内部类,而匿名类只能对函数进行返回,无法对外层进行函数返回,所以内联函数的Lambda表达式中允许使用return关键字和高阶函数的匿名类中不允许使用return关键字造成了冲突,所以crossinline就像是一个保证,保证内联函数中Lambda表达式一定不会使用return关键字
inline fun printString(str: String, crossinline func: (String) -> Unit) {
val runnable = Runnable {
func(str)
}
runnable.run()
}
高阶函数的应用
1.简化SharePreferences
普通的SharePreferences的写法
val editor = getSharedPreferences("data", Context.MODE_PRIVATE).edit()
editor.putString("key1", "value1")
editor.putString("key2", "value2")
editor.apply()
用高阶函数去写一个扩展函数
fun SharedPreferences.build(block: SharedPreferences.Editor.() -> Unit){
val editor = edit()
editor.block()
editor.apply()
}
这个是高阶函数标准写法,如果之前对高阶函数的定义清楚了,这个其实也好理解,解释如下:
1.首先我们知道SharePreferences.edit()可以获取一个Editor对象
2.我们是通过Editor的实例editor来存储值
3.可以看到build是在SharePreferences的一个方法,所以在函数里面的上下文是SharedPreferences的
4.参数是一个SharePreferences.Editor类型的,所以传入putString这些返回值为Editor的方法
我们去使用它
getSharedPreferences("data", Context.MODE_PRIVATE).build {
putString("key1","aaa")
putInt("key2",123)
}
解释:首先getSharePreferences这个方法是返回一个SharedPreferences,所以可以用build这个扩展函数,传入一些返回值为Editor的方法,然后最后apply已经在高阶函数中写过了,这里就不用写了
验证一些数据是否成功存储进去
val sharedPreferences = getSharedPreferences("data", Context.MODE_PRIVATE)
val string = sharedPreferences.getString("key1", "")
Log.e(TAG, "initListener: " + string)
可以看到数据成功获取到了
顺便一说,其实在KTX扩展库中已经有了这个简化写法
getSharedPreferences("data", Context.MODE_PRIVATE).edit {
putString("key1","aaa")
putInt("key2",123)
}
2.简化ContentValues
在写SQLite数据库时,我们很容易想到这么写
val values1 = ContentValues().apply {
put("name", "Tian Memoir")
put("author", "Tian")
put("pages", 123)
put("price", 199.99)
}
db.insert("Book", null, values1)
但其实还可以更简单
我们知道kotlin有一个mapOf方法,里面的参数可以这么写
val mapOf = mapOf("key1" to "value1")
.这个里面的这个参数,其实是一个pair类型的数据
然后我们回到简化,我们先创建一个ContentValues.kt,里面一个顶层方法
fun cvOf(vararg pairs : Pair<String,Any?>) : ContentValues {
val cv = ContentValues()
for (pair in pairs) {
val key = pair.first
val value = pair.second
when(value){
is Int -> cv.put(key,value)
is Short -> cv.put(key,value)
is Byte -> cv.put(key,value)
is ByteArray -> cv.put(key,value)
is String -> cv.put(key,value)
is Long -> cv.put(key,value)
is Float -> cv.put(key,value)
is Double -> cv.put(key,value)
is Boolean -> cv.put(key,value)
null -> cv.putNull(value)
}
}
return cv
}
解释:参数中的vararg是一个可变的参数个数,也就是允许传入多个参数都为pair类型,然后因为我们的contentValues的key都为String,所以我们只需要对第二个类型做判断就可以了,最后返回值是一个ContentValues
接下里我们就可以使用了,顶层方法可以直接写方法名就可以
val values3 = cvOf("name" to "aaa", "page" to 123, "price" to 19.99)
db.insert("Book",null,values3)
当然我们也可以进一步简化一下cvOf
fun cvOf(vararg pairs : Pair<String,Any?>) = ContentValues().apply {
for (pair in pairs) {
val key = pair.first
val value = pair.second
when(value){
is Int -> put(key,value)
is Short -> put(key,value)
is Byte -> put(key,value)
is ByteArray -> put(key,value)
is String -> put(key,value)
is Long -> put(key,value)
is Float -> put(key,value)
is Double -> put(key,value)
is Boolean -> put(key,value)
null -> putNull(value)
}
}
}
而且其实KTX也给我们写好了
val values3 = contentValuesOf("name" to "aaa", "page" to 123, "price" to 19.99)
db.insert("Book",null,values3)
泛型
1.定义泛型类:
class MyClass<T> {
fun method(param: T) = param
}
调用
val param = MyClass<String>().method("123x")
2.定义泛型方法
class MyClass {
fun <T> method(param: T) = param
}
调用
val param = MyClass().method("123")
还可以对泛型的类型进行限制
class MyClass {
fun <T : Number> method(param: T) = param
}
这样我们就可以自己去实现高阶函数中的apply了
fun <T> T.myapply(block :T.() -> Unit) : T{
block()
return this
}
调用
val stringBuilder = StringBuilder().myapply {
append("123")
append("456")
}
这部分我们把高阶函数和泛型结合起来,就适用更多的场景了
委托
委托是一种设计模式,操作对象自己不会去处理某段逻辑,而是会把工作委托给另外一个辅助对象去处理,分为类委托和委托属性
class MySet<T>(val helperSet: HashSet<T>) : Set<T> {
override val size: Int
get() = helperSet.size
override fun contains(element: T): Boolean = helperSet.contains(element)
override fun containsAll(elements: Collection<T>): Boolean = helperSet.containsAll(elements)
override fun isEmpty(): Boolean = helperSet.isEmpty()
override fun iterator(): Iterator<T> = helperSet.iterator()
}
这里的helperSet就相当于一个辅助对象,我们所有的方法实现,都调用了辅助对象中的相应的方法
这个例子体现不了委托的好处,但是如果我们只是让大部分的方法实现辅助对象中的方法,少部分的方法由自己来重写,或者加入一些自己的方法,而且不需要我们自己去写,只需要一个by关键字即可
其中的两个方法我们自己去重写一下
class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet {
fun hello() = println("Hello World")
override fun isEmpty(): Boolean = true
}
然后去调用
val hashSet = HashSet<String>()
hashSet.add("123")
hashSet.add("436")
val mySet = MySet<String>(hashSet)
Log.e(TAG, "initView: " + mySet.hello())
val empty = mySet.isEmpty()
Log.e(TAG, "initView: $empty")
这里返回true是因为我们已经对方法进行了重写,所以一直为true
2.委托属性
将一个属性的具体实现委托给另一个类去完成
class MyClass {
//委托属性,将p属性的具体实现委托给了Delegate类去实现
var p by Delegate()
}
以上代码意味着将p属性的具体实现委托给了Delegate类去完成,当调用p属性的时候会自动调用Delegate类中的getValue方法
class Delegate {
var propValue: Any? = null
operator fun getValue(myClass: MyClass, prop: KProperty<*>): Any? {
return propValue
}
operator fun setValue(myClass: MyClass, prop: KProperty<*>, value: Any?) {
propValue = value
}
}
在Delegate类中我们必须实现getValue和setValue方法,且都要使用operator这个关键字
其中的参数说明 :
第一个参数:该Delegate类的委托功能可以在什么类中使用
第二个参数:是一个属性操作类,用于获取各种属性的值(kotlin自带的),其中<*>是说我们不关心什么类型
用委托属性实现一个简单的lazy,创建一个Later.kt
class Later<T>(val block: () -> T) {
var value: Any? = null
operator fun getValue(any: Any?,prop:KProperty<*>) : T{
if (value == null){
value = block()
}
return value as T
}
}
//定义为顶层方法
fun <T> later(block: () -> T) = Later(block)
这样就实现了当使用的时候才去实现它
val uriMatcher by later {
val matcher = UriMatcher(UriMatcher.NO_MATCH)
matcher.addURI("com.example.databasetest.provider","book",0)
matcher
}