第一章、背景
https://www.kotlincn.net/
第二章、环境搭建
AS中,Java项目增加Kotlin的支持
new - Activity - Empty Activity - 选择kotlin,然后就增加了kotlin的环境,AS会为我们做下面的2件事情:
1、然后在项目的build.gradle中增加 kotlin的插件
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
2、然后在app的build.gradle中增加插件
id 'kotlin-android'
Gradle目录
(windows电脑)C:\Users\93768\.gradle\wrapper\dists
第三章、内置类型
3.1、基本类型
定义
**
**
区别
1、Java中有基本类型和包装类型,Kotlin里面只有包装类型。
2、Java中表示一个长整形,后面增加 l 或者 L,kotlIn中只能有L(大写)。
3、Java中整形赋值给Long类型是可以的,但是在kotlin中会出现编译错误。
val e:Int = 10;
val f:Long = e;//编译错误,Google认为跨类型赋值是不被允许的
val g:Long = e.toLong() //采用这样的形式是可以的
4、和Java不同,kotlin有无符号类型
5、字符串输入形式不同
val str = "cdx"
Log.e("test", "你好$str")
6、引入了 === 和 ==,=== 比较的是 引用值,== 比较的是值。
val str1 = "Hello"
val str2 = String("Hello".toCharArray())
// 比较的是引用值
Log.e("cdx",(str1 === str2).toString()) // false
// 比较的是值
Log.e("cdx",(str1 == str2).toString()) //
7、按照写的方式输出
val str =
"""
轻轻的我走了
正如我轻轻的来
我轻轻的招手
""".trimIndent()
Log.e("cdx", str)
3.2、数组
数组的类型
**
**
注意:
Int有整形和整形装箱类型,字符串只有字符串装箱。
例子1:创建一个Int数组的4种方式
// 创建一个Int类型的数组
// 第一种方式
var arr1 = IntArray(5)
// 第二种方式
var arr2 = IntArray(5) { it }
// 第三种方式
var arr3 = intArrayOf(1, 2, 3, 4, 5)
// 第四种方式
var arr4 = Array<Int>(5) { it }
例子2:创建一个String类型的数组
val arr = arrayOf("Hello", "world")
Log.e(tag, arr.joinToString())
3.3、区间
闭区间的使用
// 闭区间例子
var arr1: IntRange = 1..10
Log.e("cdx", arr1.joinToString()) // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
var arr2: CharRange = 'a'..'z'
Log.e("cdx", arr2.joinToString())
var arr3: LongRange = 1L..100L
Log.e("cdx", arr3.joinToString())
开区间的使用
// 开区间(左必有开区间)
var arr4 = 1 until 10 //[1,10)
Log.e("cdx", arr4.joinToString()) //1, 2, 3, 4, 5, 6, 7, 8, 9
倒序区间(Progression:进展、类型)
// 倒序区间
var arr5: IntProgression = 10 downTo 1
Log.e("cdx", arr5.joinToString()) //10, 9, 8, 7, 6, 5, 4, 3, 2, 1
var arr6 = 'z' downTo 'a'
Log.e("cdx", arr6.joinToString())
区间的步长
// 区间的步长
var arr7 = 1..10 step 2
Log.e("cdx", arr7.joinToString()) // 1, 3, 5, 7, 9
1..10 和 1..10 step 2 的类型分别是什么?
**
**
3.4、集合框架
集合框架有几种类型
从可变性上分为:可变集合 + 不可变集合
从集合的性质上分为:List + Map + Set
区别:
Java的数据结构都是可变的,但是kotlin的数据结构是区分不可变或者可变的。
创建不可变List的2中方式
构造器 + 方法 的创建方法
// 不可变List使用
var list = List<String>(5) { "" }
//list[0] = "1" // 出现编译错误
var list2 = listOf<String>("Hello", "world")
//list2[0] = "1"
可变List的3种创建方式
// 可变List
// 第一种创建方式
val list1 = MutableList<Int>(5) { it }
// 第二种创建方式
var list2 = mutableListOf<Int>(1)
// 第三种创建的方式
var list3 = ArrayList<Int>()
list1[3] = 4
Log.e(tag, list1.joinToString("*"))
// 超出边界将阻碍下面的语句的执行
list1[8] = 8
list1.set(2, 5)
Log.e(tag, list1.joinToString("*"))
ArrayList使用
// 这个默认是:MutableList
val list3 = ArrayList<String>()
list3.add("a")
list3.add("b")
list3 += "c" //注意 += 就是add的操作
list3.remove("b") //
list3 -= "a" // -= 就是remove操作
Log.e("cdx", list3.joinToString()) //c
不可变Map创建的2种方式
to + Pair 方式
// 不可变Map
val map: Map<String, Any> = mapOf("name" to "xiaohong", "age" to 20)
val map = mapOf<String, String>(Pair("name", "张三"), Pair("age", "10"))
可变Map
// 可变Map
val map2: MutableMap<String, Any> = mutableMapOf("name" to "xiaohong", "age" to 20)
map2["name"] = 3
map2.put("weight", 160)
//遍历Map的key-value对,entris元素返回key-value对组成的Set
for (en in map2.entries) {
Log.e("cdx", "${en.key}->${en.value}")
}
Pair(对)
val pair = "Hello" to "kotlin"
val pair2 = Pair("Hello", "kotlin")
val first = pair2.first
val second = pair2.second
// 解构赋值
val (x, y) = pair
Log.e(
"cdx",
"first:$first,second:$second,x:$x,y:$y"
) // first:Hello,second:kotlin,x:Hello,y:kotlin
Triple(三倍的)
和Pair使用类似
3.5、函数
定义
类似于java中的方法。
一个函数没有返回值,返回类型是什么?
Unit类似于Java的void,可以省略。
private fun test4(): Unit {
}
函数VS方法
test方法外面有一个Test(类),那么test就是一个方法。
class Test {
fun test(): String {
return ""
}
}
函数的引用
函数的引用类似于C语言中的指针,可用于函数传递。
利用**::**来实现获取函数的引用。
方法函数的引用 和 引用的类型是什么
首先定义方法
private fun test(str: String): Unit {
}
然后获取方法的引用
// 定义fun1来接受方法的引用, (String) -> Unit表示函数的类型。
// 其中(String) -> Unit类型可以省略
val fun1: (String) -> Unit = ::test
类中方法的引用
package com.example.kotlindemo
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 接受一个方法的引用,这两个是等价的,表示括号里和哪些有关系
// 第一个表示有Receiver,第二个形式表示和Test有关系
val fun2: Test.() -> String = Test::test
val fun3: (Test) -> String = Test::test
// 对象取函数的引用,类型不一样了
val test = Test()
val fun4: () -> String = test::test
// 函数里面有函数,并且调用
test1(::test)
test1(fun1)
}
/**
* 这个test1函数的类型是一个函数,函数的参数是String类型,返回时是Unit类型,也就是void类型
*/
private fun test1(p: (String) -> Unit) {
p("Hello")
}
private fun test(str: String): Unit {
}
}
class Test {
// 这个就是方法
fun test(): String {
return ""
}
}
对象方法的引用
// 对象取函数的引用,类型不一样了
val test = Test()
val fun1: () -> Unit = test::test
变长参数
vararg 用来修饰参数,就变成了变长参数,类似于数组。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.e("cdx",test("1", "2"))
}
// 边长参数:类似于数组
// 使用关键字 vararg
fun test(vararg params: String): String {
return params.size.toString();
}
}
多返回值接受的两种方式(通常方式、解构赋值)
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
// 第一种接受方式
val test = test()
// 第二种接受方式:结构赋值
val (a,b,c) = test()
Log.e("cdx",test.toString())
}
fun test(): Triple<Int, Long, Double> {
return Triple(1, 2L, 3.0)
}
}
默认参数
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
test(1)
}
// 默认参数,建议放在最后,也可以放在前面
fun test(x: Int, y: Int = 0, z: String = "") {
Log.e("cdx", "x:$x y:$y z:$z")
}
}
默认参数建议放在哪
默认参数,建议放在最后,也可以放在前面
具名参数
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
// 具名参数:在传递的时候将键值对都放入这个方法中
// 使用场景:当函数的默认值不是最后的参数时,可以使用具名参数
test(y=1)
}
// 默认参数,必须放在最后
fun test(x: Int = 1, y: Int, z: String = "") {
Log.e("cdx", "x:$x y:$y z:$z")
}
}
第四章 类型初步
4.1、类和接口
类的定义
1、默认是public
2、如果类中没有内容,可以将{ } 省略
3、类中定义属性,必须要初始化。
4、类中定义构造函数,使用constructor关键字。
5、构造器分为主构造器和副构造器,要求其它所有的构造器都必须调用它。
构造函数的几种形式
只有主构造器、只有副构造器、主构造器 + 副构造器、主构造器省略constructor、主构造器简化
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
val test5 = Test5(1)
}
}
// 只有副构造器
class Test {
var x: Int = 0
constructor(x: Int) {
this.x = x
}
}
// 包含主构造器和副构造器
class Test2 constructor(x: Int) {
var x: Int = x
var y: Int = 0
constructor(x: Int, y: Int) {
this.x = x
this.y = y
}
}
// 只有主构造器
class Test3 constructor(x: Int) {
// 赋值
var x: Int = x
}
// 将主构造器进行省略
class Test4(x: Int) {
// 赋值
var x: Int = x
}
// 继续简化:如果在构造器中增加了var/val,那么相当于上面的构造函数
class Test5(var x: Int) {
}
类的初始化
不需要new关键字
接口定义
interface SimpleInter {
fun test()
}
接口的实现
// 使用:表示实现接口
class Test : SimpleInter {
override fun test() {
}
}
interface SimpleInter {
fun test()
}
接口中增加属性并且实现
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
val test = Test(0)
test.param = 4
Log.e("cdx", test.param.toString())
}
}
// 接口中定义了参数,这个参数不需要初始化
interface TestInterface {
var param: Int
fun test()
}
// 通过构造函数传递进来一个参数
class Test(var x: Int) : TestInterface {
// 必须要重写这个参数
override var param: Int
// 获取参数
get() {
return x
}
// 设置参数
set(value) {
this.x = value
}
override fun test() {
}
}
抽象类的定义
abstract class AbsTest {
// 表示抽象方法
abstract fun test1()
// 必须加上open方法,才能告诉编译器,可以被复写
open fun test2() {}
// 默认是不可复写
fun test3() {
}
}
抽闲类的实现
// 接口继承:必须要加上() 这个和接口的实现不一样,接口是不需要加()
class Test : AbsTest() {
override fun test1() {
}
override fun test2() {
super.test2()
}
}
继承一个普通的类要求
继承一个普通的类A,这个A类必须要是open修饰的。
一个类的方法不能被重写
一个类的方法不能被复写,这个方法增加final。
// 要想能够继承一个类,这个类必须是open的
class Test2 : Test() {
// 如果复写一个final修饰的方法,此时将会出现编译错误
// override fun test2() {
// super.test2()
// }
}
// 接口继承:必须要加上() 这个和接口的实现不一样,接口是不需要加()
open class Test : AbsTest() {
override fun test1() {
}
final override fun test2() {
super.test2()
}
}
abstract class AbsTest {
// 表示抽象方法
abstract fun test1()
// 必须加上open方法,才能告诉编译器,可以被复写
open fun test2() {}
// 默认是不可复写
fun test3() {
}
}
field使用
定义
在get或者set方法中,表示这个属性。
field使用
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
val person = Person(25, "张三")
person.age = 18
person.name = "小红"
Log.e("cdx", person.toString())
}
}
class Person(age: Int, name: String) {
var age: Int = age
get() {
// field就是指向age属性
return field
}
set(value) {
field = value
}
var name: String = name
override fun toString(): String {
return "$age,$name"
}
}
4.2、扩展方法
定义
不改变类的源码的情况下,为类增加自定义的方法。
如何实现
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
Log.e("cdx", "abc".times(4))
}
fun String.times(times: Int): String {
var builder = StringBuilder()
for (i in 0 until times) {
// 这里的this只得调用者本身
builder.append(this)
}
return builder.toString()
}
}
4.3、空类型安全
空类型访问属性
var str: String? = null
// 使用?.来安全访问
val length = str?.length;//null
Log.e("cdx", length.toString())//null
强制转换为不可空类型
通过两个!!将可空类型变为不可空类型
// 表示是一个空类型
var str: String? = "Hello"
// 通过两个!!强转化成不为空类型
val str1 = str!!;
Log.e("cdx", str1.length.toString())
elvis运算符
// 表示是一个空类型
var str: String? = null
// elvis运算符,如果str?.length 返回null的话,那么此时返回0
val length = str?.length ?: 0
Log.e("cdx", length.toString()) //0
空类型的继承关系
小范围(String)的可以给大范围(String?) 赋值,但是大范围的不能给小范围赋值。
// 不为空
var x: String = "a"
// 可以为空(范围更大)
var y: String? = "b"
// 大范围的赋值给小范围,出现编译错误
//x = y
// 小范围的赋值给大范围,没有问题
y = x
平台类型
**定义
**
1、在java中声明的类型,在kotlin里面会被称为平台类型。
2、这种类型的空检查会放宽。
例子
首先定义一个person类,这个是Java的类。
package com.example.kotlindemo;
public class Person {
public String name;
public String getTitle(){
return null;
}
}
title的类型居然是String!
var person = Person()
// 去使用title的时候,本质上使用的是Person的getTitle属性
val title = person.title
// 编译器不知道title是否是可空类型,所以要进行非空判断
var length = title?.length
4.4、智能类型转换
kotlin的类型转换
kotlin不需要强制转换为子类,就可以调用子类的属性。(这个和Java不太一样)
package com.example.kotlindemo
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
// 父类的引用指向子类的对象
var person = Student();
Log.e("cdx", "xxx" + person.name)
if (person is Student) {
// 不需要进行类型转换,自动调用子类的属性,下面的写法是精简的写法
Log.e("cdx", (person as Student).name)
Log.e("cdx", person.name)
}
}
}
interface Person {
fun say()
}
class Student : Person {
val name: String = "xh"
override fun say() {
}
}
作用范围
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
// 作用范围
var value: String? = null
// 这个是不行的,因为这个是可空的类型
// Log.e("cdx", value.length.toString());
if (value != null) {
// 在括号里面,由于有非空判断,所以编译器很智能的将value的类型变为了String类型,非空的类型。
Log.e("cdx", value.length.toString());
}
// 在这个地方,value的类型又变为了String可为空的类型
Log.e("cdx", value?.length.toString());
}
}
不支持智能转换的情况
class MainActivity2 : AppCompatActivity() {
var tag: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
// 这个地方理论上有非空判断,为什么不能有作用范围的概念呢?
// 因为tag是一个全局的字段,虽然判断不为空,
但是在判断为不为空的时候,
其它的线程可能将他的值修改为空,所以智能转换就不生效了
if (tag != null) {
// Log.e("cdx",tag.length.toString())
Log.e("cdx", tag?.length.toString())
}
}
}
类型的安全转换
**as ?
**
安全转换:
如果是person是Student类型,那么就转换
如果不是Student类型,那么返回null
// 父类的引用指向子类的对象
var person = Student();
// 安全转换:
// 如果是person是Student类型,那么就转换
// 如果不是Student类型,那么返回null
Log.e("cdx", (person as? Student)?.name.toString())
建议:
1、尽量使用val来申明不可变的引用,让程序的含义更加清晰确定。
5章、表达式
5.1、只读变量和变量和常量
变量表达
var 来表示变量
只读变量
val表示只读变量
只读变量VS常量
常量的意思是:永远不变。
但是请看下面的例子,Test中的b,它会随着Math的值不一样,获取到的b的值是不一样的,它的值是变化的,所以称为只读变量。
class Test {
val b: Int
get() {
return (Math.random() * 1000).toInt()
}
}
常量要求和表示
1、只能修饰基本类型
2、只能定义在全局范围
3、必须立即用字面量初始化
val b = 3
5.2、分支表达式
if - else表达式(和Java不同、三目运算符)
注意:if-else表达式是有返回值的。
var a: Int = 4
var c = if (a == 3) 4 else 5
Log.e("cdx", "c:$c")
when 表达式写法
**
**
when表达式 - 条件转移到分支、简写写法
条件转移到分支上的写法
val a: Any = 4
val c: Any
when {
a is String -> c = a.length
a == 3 -> c = 3
else -> c = 5
}
简化写法
val a: Any = 4
val c = when {
a is String -> a.length
a == 3 -> 3
else -> 5
}
try - catch 表达式
和Java不同的地方:try - catch表达式可以有返回值。
var a = 1
var b = 0
val c = try {
a / b
} catch (e: Exception) {
0
}
5.3、运算符与中缀表达式
运算符网站
https://kotlinlang.org/docs/operator-overloading.html
== 运算符 VS equal
val str1 = "Hello"
val str2 = "World"
// 下面的两种方式是完全等价的
val result1 = (str1 == str2)
var result2 = str1.equals(str2)
+ 运算符 VS plus
val str1 = "a"
val str2 = "b"
// 下面的两种方式是完全等价的
val result1 = (str1 + str2)
var result2 = str1.plus(str2)
Log.e("cdx", result1.toString())
Log.e("cdx", result2.toString())
in 运算符 VS contains
var list = listOf(1, 2, 3, 4)
var result = 2 in list
Log.e("cdx", result.toString())
Log.e("cdx", list.contains(2).toString())
[] 运算符 VS get
val map = mapOf("name" to "张三", "age" to 20)
// [] 和 get 的方式完全一样,编译器推荐使用第二种方式
Log.e("cdx", map.get("name").toString())
Log.e("cdx", map["name"].toString())
> VS compareTo
var result1 = 2 > 3
var result2 = 2.compareTo(3) > 0
Log.e("cdx", result1.toString())
Log.e("cdx", result2.toString())
() 与 invoke
表示 执行。
// 定义匿名函数,用变量去接受这个函数
var func = fun() {
Log.e("cdx", "方法执行了")
}
// 第一种调用函数方法
func()
// 第二种调用函数方法
func.invoke()
自定义运算符
第一步:定义运算符,相当于给Complex这个哥么增加了2个标签(比如幽默),比如 + 这个标签,那么两个Complex哥么通过标签就可以相互的认识。
plus和运算符+ 是对应的关系。
package com.example.kotlindemo
class Complex(var real: Double, var image: Double) {
override fun toString(): String {
return "$real + $image"
}
}
// 自定义操作符
// 相当于给Complex增加了plus操作符的功能,相当于增加了 + 的功能
operator fun Complex.plus(other: Complex): Complex {
return Complex(this.real + other.real, this.image + other.image)
}
// 自定义减的运算符
// 给Complex增加了减的运算符,增加了 - 的功能
operator fun Complex.minus(other: Complex): Complex {
return Complex(this.real - other.real, this.image - other.image)
}
第二步:然后在页面里面去调用
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
var c1 = Complex(1.0, 2.0)
var c2 = Complex(3.0, 2.0)
//
var result1 = c1 + c2
Log.e("cdx", result1.toString())
var result2 = c1 - c2
Log.e("cdx", result2.toString())
}
}
中缀表达式-infix的介绍
Kotlin允许在不使用括号和点号的情况下调用函数,那么这种函数被称为 infix函数。
中缀表达式要求和使用
1、只有一个参数。
2、增加infix参数,表示可以省略括号和点号。
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
2.to(3)
2 to 3
}
// infix:中缀表达式的的标识
// A,B 都是泛型
infix fun <A, B> A.to(b: B): Pair<A, B> {
return Pair(this, b)
}
}
5.4、Lambda表达式
匿名函数的传递
// func:是变量名
// 匿名函数赋值给了一个变量
var func = fun() {
Log.e("cdx", "test")
}
匿名函数的调用
// func:是变量名
// 匿名函数赋值给了一个变量
var func = fun() {
Log.e("cdx", "test")
}
// 通过()运算符去执行
func()
// 通过invoke()去执行
func.invoke()
Lambda表达式的类型
Lambda表达式就是匿名函数的简化。
// 没有参数的Lambda表达式
// 注意func1的类型 () -> Int
var func1: () -> Int = {
Log.e("cdx", "test")
}
// 有参数的Lambda表达式
// 注意func2的类型 (Int, Int) -> Int
var func2: (Int, Int) -> Int = { p1: Int, p2: Int ->
Log.e("cdx", "test,$p1 $p2")
}
Lambda表达式返回值
是由Lambda中的最后一行的语句决定。
5.5、为Person实现equals 和hashCode
package com.example.kotlindemo
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
val hashSet = HashSet<Person>()
hashSet.add(Person("张三", 15))
hashSet.add(Person("张三", 15))
Log.e("cdx", hashSet.size.toString())
}
}
class Person(var name: String, var age: Int) {
override fun equals(other: Any?): Boolean {
Log.e("cdx", "equals")
val person = (other as? Person) ?: return false
return person.name == name && person.age == age
}
override fun hashCode(): Int {
Log.e("cdx", "hashCode")
return name.hashCode() * 7 + age * 5;
}
}
5.6、为String实现四则运算
需要实现下面的效果,实现字符串的加减乘除。
分析:
1、没有办法修改String的源码,所以我们需要使用扩展方法。
2、借助于自定义运算符。
package com.example.kotlindemo
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import java.lang.StringBuilder
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
val str: String = "HelloWorld"
val result = str - "World"
Log.e("cdx", result)
val result2 = str * 5
Log.e("cdx", result2)
val result3 = str / "l"
Log.e("cdx", result3.toString())
}
}
operator fun String.minus(other: Any): String {
return this.replaceFirst(other.toString(), "")
}
operator fun String.times(time: Int): String {
var sb = StringBuilder()
for (i in 0 until time) {
sb.append(this)
}
return sb.toString()
}
operator fun String.div(str: String): Int {
// 这个东西很好玩
// String字符串上一点一点的划,划的长度是str.lenght,
然后间隔是1,表示一个一个的划
// 然后获取到划到的集合
return this.windowed(str.length, 1) {
it == str
}
// 然后数一数里面欧多少个满足条件的,it为true的就筛选出来
.count {
it
}
}
6章、函数进阶
6.1、高阶函数
定义
参数包含函数类型或者返回值为函数类型。
// 参数param是函数类型
fun test1(param: () -> Int) {
// 涉及函数的调用
// param()
param.invoke()
}
// 返回值是函数类型
fun test2(): () -> Int {
// 返回的是一个匿名函数,返回值是1
return { 1 }
}
// 定义一个数组
var arr = intArrayOf(1, 2, 3, 4)
// 遍历功能
arr.forEach {
Log.e(TAG, it.toString())
}
Log.e(TAG, arr.joinToString())
// 遍历,然后转成另外的元素,然后存到数组中
val map = arr.map {
it * it
}
Log.e(TAG, map.joinToString())
高阶函数的调用
// 定义一个数组
var arr = intArrayOf(1, 2, 3, 4)
// 高阶函数的引用 ::println表示对函数的引用
arr.forEach(::println)
高阶函数调用-简化的几个阶段
非常关键
// 定义一个数组
var arr = intArrayOf(1, 2, 3, 4)
// 第一种方式
arr.forEach({
println("Hello $it")
})
// 如果接受的高阶函数是最后一个参数,小括号可以移动到外面
arr.forEach() {
println("Hello $it")
}
// 如果括号中没有任何的参数,那么可以将小括号省略
arr.forEach {
println("Hello $it")
}
高阶函数应用 - 计算方法的时间
// 首先传入一个匿名函数
// const({
// (1..10000000).forEach { it * it }
// })
// 优化成下面的样式
// const(){
// (1..10000000).forEach { it * it }
// }
// 继续优化
const {
(1..10000000).forEach { it * it }
}
6.2、内联函数
定义
减少了函数的调用,性能优化。
分析:
我们可以看到Array.kt文件中forEach的定义,里面有一个关键字inline,它有什么作用呢?
public inline fun IntArray.forEach(action: (Int) -> Unit): Unit {
for (element in this) action(element)
}
我们在代码中调用格式如下:
// 定义一个数组
var arr = intArrayOf(1, 2, 3, 4)
// 第一种方式
arr.forEach({
println("Hello $it")
})
编译器最终转换为的是一个内联函数。
// 定义一个数组
var arr = intArrayOf(1, 2, 3, 4)
// 第一种方式
for(element in arr){
println(element.toString())
}
内联函数的优点
减少了函数的调用(主要是减少了函数的压栈、调用执行、出栈的操作),性能更好。
高阶函数与内联函数更加匹配
比如下面的函数
class MainActivity2 : AppCompatActivity() {
val TAG = "cdx"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
const({
print("1")
})
}
fun const(block: () -> Unit) {
var start = System.currentTimeMillis()
block()
var end = System.currentTimeMillis()
Log.e(TAG, (end - start).toString())
}
}
然后我们查看下编译后的代码(输入Show Kotlin Bytecode)
我们看下其中2个消耗时间的地方
1、调用了const方法。
2、创建了一个匿名函数。
然后我们优化const方法的定义,增加inline关键字,让他变成一个内联函数
inline fun const(block: () -> Unit) {
var start = System.currentTimeMillis()
block()
var end = System.currentTimeMillis()
Log.e(TAG, (end - start).toString())
}
然后我们查看下编译后的代码(输入Show Kotlin Bytecode)
我们可以看到,省去了函数的调用,性能更高。
内联函数return 之 local return(本地返回)
设想这样一个场景,当遇到3
时,不打印,如何在forEach()
中实现这一点呢?操作如下:
//Kotlin
var ints = intArrayOf(1, 2, 3, 4)
fun main() {
ints.forEach {
if (it == 3) return@forEach
println(it)
}
println("Dividing")
ints.forEach {
if (it == 3) return
println(it)
}
println("Ending")
}
控制台输出:
return@forEach和return的打印结果完全不同。
return@forEach将提前结束本次循环,相当于continue;
return将结束所在函数,此处即为main()函数,因为Ending没有打印。
像return@forEach这样的返回称为local return。(continue的功能的就是local-return )
内联函数return 之 non-local return
不是本地返回,就是全局返回。
上面的return就是non-local return。
内联函数local return 和 non-local return 的研究讨论
首先看下面的例子,将高阶函数定义为内联函数:
//Kotlin
inline fun nonLocalReturn(block: ()->Unit){
block()
}
fun main() {
println("Starting")
nonLocalReturn { return }
println("Ending")
}
控制台将打印:
Starting
我们看到执行了 non-local return,全局进行了返回。
然后我们看看下面的例子,如果将高阶函数定义成非内联函数:
这个地方不能执行return,就是不能执行non-local return
为什么会出现上面的情况呢?
对于第一个例子,由于是一个内联函数,内联函数没有压栈和出栈的操作,会将代码直接插入调用处,所以会在main函数内部调用return,所以会出现non-local retuan 全局返回。
crossinline
noinline
noline:禁止函数参与内联
内联属性
没有back-field的属性的getter和setter可以被内联,只需要在getter和setter上增加inline
class MainActivity2 : AppCompatActivity() {
val TAG = "cdx"
var pocket: Double = 0.0
// 内联属性
var money: Double
inline get() = pocket
inline set(value) {
pocket = value
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
money = 5.0
}
}
然后我们编译下
发现money属性已经没有了,money属性已经被替换了。
6.3、几个有用的高阶函数
let、run
let:当我们需要定义一个变量在特定的作用域内,let函数是一个不错的选择。
// 定义对象
var person = Person("张三", 25)
val name = person.let {
it.name
}
Log.e(TAG, name.toString())
run:和let的作用相似,只是一个是it,一个是this
// 定义对象
var person = Person("张三", 25)
var result = person.run {
this.age
}
Log.e(TAG, result.toString())
also、apply
// 定义对象
var person = Person("张三", 25)
// 利用also来修改对象属性
var person2 = person.also {
it.name = it.name + "V2"
}
Log.e(TAG, person2.toString())
var person3 = person.apply {
name = "xxx"
}
Log.e(TAG, person3.toString())
6.4、集合变换与序列
filter操作
筛选出来合适的,然后放到集合中
val list = intArrayOf(1, 2, 3, 4)
// 取出偶数的值,过滤
val result = list.filter { it % 2 == 0 }
Log.e(TAG, result.toString()) // [2, 4]
map操作
遍历数组,然后进行操作,然后放到集合中
val list = intArrayOf(1, 2, 3, 4)
val result = list.map { it * 2 }
Log.e(TAG, result.joinToString())
flatMap操作
flatMap操作的流程图
1)首先取出来元素
2)然后根据元素生成数组
3)然后在将这些数组进行拼接。
val list = intArrayOf(1, 4, 7)
// 初始化的值为StringBuilder()
// acc为拼接的值
list.fold(StringBuilder()) { acc, i ->
Log.e(TAG, "acc=$acc")
Log.e(TAG, i.toString())
acc.append(i)
}
懒序列懒在哪里?
通过调用asSequence(),然后只有调用forEach方法(fold、ruduce、sum)以后,
才会去执行上面的filter、map、flatMap方法
6.5、SAM(单一抽象方法转换)转换
SAM转换例子
如果匿名内部类内部只有一个方法,那么这个匿名内部类可以非常的简化,只有一个{ } 。
// SAM例子
var exe: ExecutorService? = null
// 匿名内部类
// exe?.submit(object : Runnable {
// override fun run() {
// Log.e(TAG,"任务执行的")
// }
// })
// 简化写法
// exe?.submit({
// Log.e(TAG, "任务执行的")
// })
// 继续简化
exe?.submit {
Log.e(TAG, "任务执行的")
}
kotlin的匿名内部类
// 匿名内部类的通常写法
object : Runnable {
override fun run() {
Log.e(TAG,"任务执行的")
}
}
// 匿名内部类的简写写法
Runnable {
Log.e(TAG,"任务执行的")
}