Kotlin完全抛弃了Java中的基本数据类型,全部使用了对象数据类型。在Java中int是整型变量的关键字,而在Kotlin中Int变成了一个类,它拥有自己的方法和继承结构。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kZqWOS6H-1590142300347)(E:\Notes\Kotlin\assets\《代码》笔记.assets\1589798994515.png)]
val(value的简写的简写)用来声明一个不可变的变量,这种变量在初始赋值之后就再也不能重新赋值,对应Java中的final变量。
var(variable的简写的简写)用来声明一个可变的变量,这种变量在初始赋值之后仍然可以再被重新赋值复制,对应Java中的非final变量。
fun main() {
val a = 10//不能重新赋值
var b = 5
b = b + 3
println("a = " + a)
println("b = " + b)
}
fun methodName(param1: Int, param2: Int): Int {
return max(p1,p2)
}
fun methodName(param1: Int, param2: Int) = max(p1,p2)
使用这种写法,可以直接将唯一的一行代码写在函数定义的尾部,中间用等号连接即可。
return关键字也可以省略,等号足以表达返回值的意思。
Kotlin还拥有出色的类型推导机制,可以自动推导出返回值的类型。
Kotlin中的if语句相比于Java有一个额外的功能:它是可以有返回值的,返回值就是if语句每一个条件中最后一行代码的返回值。
fun largerNumber(num1: Int, num2: Int): Int {
val value = if (num1 > num2) {
num1
} else {
num2
}
return value
}
仔细观察上述代码,你会发现value其实是一个多余的变量,我们可以直接将if语句返回,这样代码将会变得更加精简,如下所示:
fun largerNumber(num1: Int, num2: Int): Int {
return if (num1 > num2) {
num1
} else {
num2
}
}
当一个函数只有一行代码时,可以省略函数体部分,直接将这一行代码使用等号串连在函数定义的尾部。
fun largerNumber(num1: Int, num2: Int) = if (num1 > num2) {
num1
} else {
num2
}
或者
fun largerNumber(num1: Int, num2: Int) = if (num1 > num2) num1 else num2
fun getScore(name: String) = when (name) {
"Tom" -> 86
"Jim" -> 77
"Jack" -> 95
"Lily" -> 100
else -> 0
}
除了精确匹配之外,when语句还允许进行类型匹配。
fun checkNumber(num: Number) {
when (num) {
is Int -> println("number is Int")
is Double -> println("number is Double")
else -> println("number not support")
}
}
可以通过for-in循环来遍历这个区间,它的数学表达方式是[0, 10),不包含10,一般创建一个10为大小的数组也不包含10这个下标:
fun main() {
for (i in 0 until 10) {
println(i)
}
}
如果你想跳过其中的一些元素,可以使用step关键字:
fun main() {
for (i in 0 until 10 step 2) {
println(i)
}
}
如果你想创建一个降序的区间,可以使用downTo关键字:
fun main() {
for (i in 10 downTo 1) {
println(i)
}
}
class Person {
var name = ""
var age = 0
fun eat() {
println(name + " is eating. He is " + age + " years old.")
}
}
fun main() {
val p = Person()
p.name = "Jack"
p.age = 19
p.eat()
}
class Student : Person() {
var sno = ""
var grade = 0
}
open class Person(val name: String, val age: Int) {
fun eat() {
println(name + " is eating. He is " + age + " years old.")
}
}
class Student(val sno: String = "", val grade: Int = 0, name: String = "", age: Int = 0) : Person(name, age), Study {
override fun readBooks() {
println(name + " is reading.")
}
//init代码块
init {
println(sno)
println(grade)
}
}
class Student(val sno: String = "", val grade: Int = 0, name: String = "", age: Int = 0) : Person(name, age), Study {
//副构造函数,this调用了主构造函数
constructor(name:String,age:Int):this("",0,name,age){
}
//this调用了第一个副构造函数
constructor():this("",0){
}
}
//没有在这里调用父类的主构造函数,所以Person后面没有括号
class Student : Person {
//副构造函数,super调用了父类的主构造函数
constructor(name:String,age:Int):super(name,age){
}
}
interface Study {
fun readBooks()
fun doHomework()
}
class Student(val name: String, val age: Int) : Study {
override fun readBooks() {
println(name + " is reading.")
}
override fun doHomework() {
println(name + " is doing homework.")
}
}
data class Cellphone(val brand: String, val price: Double)
object Singleton {
fun singletonTest() {
println("singletonTest is called.")
}
}
Singleton.singletonTest()
使用如下代码可以初始化一个List集合:
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
遍历list
for (fruit in list) {
println(fruit)
}
使用如下代码可以初始化一个Set集合:
val set = setOf("Apple", "Banana", "Orange", "Pear", "Grape")
使用如下代码可以初始化一个Map集合(to表示kv形式):
val map = mapOf("Apple" to 1, "Banana" to 2, "Orange" to 3, "Pear" to 4, "Grape" to 5)
遍历map,取出的是kv形式的一对括号
for ((fruit, number) in map) {
println("fruit is " + fruit + ", number is " + number)
}
val list = mutableListOf("Apple", "Banana", "Orange", "Pear", "Grape")
val max=list.maxBy { it.length }
fun main() {
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val newList = list.map({ fruit: String -> fruit.toUpperCase() })
for (fruit in newList) {
println(fruit)
}
}
当Lambda参数是函数的最后一个参数时,可以将Lambda表达式移到函数括号的外面。
如果Lambda参数是函数的唯一一个参数的话,还可以将函数的括号省略。
由于Kotlin拥有出色的类型推导机制,Lambda表达式中的参数列表其实在大多数情况下也不必声明参数类型。
当Lambda表达式的参数列表中只有一个参数时,也不必声明参数名,而是可以使用it关键字来代替。
因此,Lambda表达式的写法可以进一步简化成如下方式:
val newList = list.map { it.toUpperCase() }
fun doStudy(study: Study) {
study.readBooks()
study.doHomework()
}
?. 操作符表示当对象不为空时正常调用相应的方法,当对象为空时则什么都不做。比如:
if (a != null) {
a.doSomething()
}
这段代码使用?.操作符就可以简化成:
a?.doSomething()
?: 操作符表示如果左边表达式的结果不为空就返回左边表达式的结果,否则就返回右边表达式的结果。比如:
val c = if (a ! = null) {
a
} else {
b
}
这段代码的逻辑使用?:操作符就可以简化成:
val c = a ?: b
fun doStudy(study: Study?) {
study?.let {
it.readBooks()
it.doHomework()
}
}
Kotlin中字符串内嵌表达式的语法规则如下:
"hello, ${obj.name}. nice to meet you!"
当表达式中仅有一个变量的时候,还可以将两边的大括号省略:
"hello, $name. nice to meet you!"
fun printParams(num: Int, str: String = "hello") {
println("num is $num , str is $str")
}
class Student(val sno: String = "", val grade: Int = 0, name: String = "", age: Int = 0)
val intent = Intent(this, SecondActivity::class.java)
val result = with(obj) {
// 这里是obj的上下文
"value" // with函数的返回值
}
例子:StringBuilder拼接list中的字符串
val list= listOf("apple","banana")
val builder=StringBuilder()
builder.append("start")
for (fruit in list) {
builder.append(fruit)
}
builder.append("end")
builder.toString()
使用with函数可以精简为:
val list= listOf("apple","banana")
val result= with(StringBuilder()){
append("start")
for (s in list) {
append(s)
}
append("end")
toString()//返回值
}
val result = obj.run {
// 这里是obj的上下文
"value" // run函数的返回值
}
val result2=StringBuilder().run {
append("start")
for (s in list) {
append(s)
}
append("end")
toString()//返回值
}
val result = obj.apply {
// 这里是obj的上下文
}
// result == obj
val result3=StringBuilder().apply{
append("start")
for (s in list) {
append(s)
}
append("end")
}
//因为apply无法指定返回值,result3实际上还是StringBuilder对象,所以在外面调用toString
println(result3.toString())
//用apply构建intent,放入参数
val intent=Intent(this,SecondActivity::class.java).apply {
putExtra("param1","data1")
putExtra("param2","data2")
}
this.startActivity(intent)
object Util {
fun doAction1() {
println("do action1")
}
}
class Util {
fun doAction1() {
println("do action1")
}
companion object {
fun doAction2() {
println("do action2")
}
}
}
//调用
Util.doAction2()
companion object {
@JvmStatic//注解
fun doAction2() {
println("do action2")
}
}
fun doSomething() {
println("do something")
}
在Kotlin中直接调用,无需创建实例
doSomething()
在Java中需要用类名.方法()调用,系统编译时候会生成一个叫HelperKt的Java类
HelperKt.doSomething()
repeat(2) {
fruitList.add(Fruit("Apple", R.drawable.apple_pic))
fruitList.add(Fruit("Banana", R.drawable.banana_pic))
fruitList.add(Fruit("Orange", R.drawable.orange_pic))
fruitList.add(Fruit("Watermelon", R.drawable.watermelon_pic))
fruitList.add(Fruit("Mango", R.drawable.mango_pic))
}
listView.setOnItemClickListener { _, _, position, _ ->
val fruit = fruitList[position]
Toast.makeText(this, fruit.name, Toast.LENGTH_SHORT).show()
}
class MainActivity : AppCompatActivity(), View.OnClickListener {
private lateinit var adapter: MsgAdapter
override fun onCreate(savedInstanceState: Bundle?) {
…
adapter = MsgAdapter(msgList)
…
}
override fun onClick(v: View?) {
…
adapter.notifyItemInserted(msgList.size - 1)
…
}
}
if (!::adapter.isInitialized) {
adapter = MsgAdapter(msgList)
}
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 -> "Error is ${result.error.message}"
}
fun ClassName.methodName(param1: Int, param2: Int): Int {
return 0
}
fun String.lettersCount(): Int {
var count = 0
for (char in this) {
if (char.isLetter()) {
count++
}
}
return count
}
调用处就有了String实例上下文,调用方式:
"abvc".lettersCount()
Kotlin的运算符重载允许我们让任意两个对象进行相加,或者是进行更多其他的运算操作。
这里以加号运算符为例,如果想要实现让两个对象相加的功能,那么它的语法结构如下:
class Obj {
operator fun plus(obj: Obj): Obj {
// 处理相加的逻辑
}
}
class Money(val value: Int) {
operator fun plus(money: Money): Money {
val sum = value + money.value
return Money(sum)
}
operator fun plus(newValue: Int): Money {
val sum = value + newValue
return Money(sum)
}
}
fun example(func: (String, Int) -> Unit) {
func("hello", 123)
}
可以看到,这里的example()函数接收了一个函数类型的参数,因此example()函数就是一个高阶函数。而调用一个函数类型的参数,它的语法类似于调用一个普通的函数,只需要在参数名的后面加上一对括号,并在括号中传入必要的参数即可。
fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {
val result = operation(num1, num2)
return result
}
val result0= num1AndNum2(num1,num2,::plus)
fun plus(num1: Int, num2: Int): Int {
return num1 + num2
}
fun minus(num1: Int, num2: Int): Int {
return num1 - num2
}
val result1 = num1AndNum2(num1, num2) { n1, n2 ->
n1 + n2
}
fun StringBuilder.build(block: StringBuilder.() -> Unit): StringBuilder {
block()
return this//返回了StringBuilder对象
}
val result = StringBuilder().build {
append("Start eating fruits.\n")
for (fruit in list) {
append(fruit).append("\n")
}
append("Ate all fruits.")
}
println(result.toString())
inline fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {
val result = operation(num1, num2)
return result
}
//第二次参数函数是noinline
inline fun inlineTest(block1: () -> Unit,noinline block2: () -> Unit){
}
为什么要有noinline呢?因为inline有局限性
下面这段代码会报错
inline fun runRunnable(block: () -> Unit) {
val runnable = Runnable {
block()
}
runnable.run()
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EdBbB3hB-1590142300353)(E:\Notes\Kotlin\assets\《代码》笔记.assets\1589987393629.png)]
inline fun runRunnable(crossinline block: () -> Unit) {
val runnable = Runnable {
block()
}
runnable.run()
}
Context类中提供了一个openFileOutput()方法,可以用于将数据存储到指定的文件中。
所有的文件会默认存储到/data/data//files/目录下。示例写法如下:
fun save(inputText: String) {
try {
val output = openFileOutput("data", Context.MODE_PRIVATE)
val writer = BufferedWriter(OutputStreamWriter(output))
writer.use {//自动关闭流
it.write(inputText)
}
} catch (e: IOException) {
e.printStackTrace()
}
}
Context类中还提供了一个openFileInput()方法,用于从文件中读取数据。
它会自动到/data/data//files/目录下加载文件,并返回一个FileInputStream对象,得到这个对象之后,再通过流的方式就可以将数据读取出来了。示例写法如下:
fun load(): String {
val content = StringBuilder()
try {
val input = openFileInput("data")
val reader = BufferedReader(InputStreamReader(input))
reader.use {
reader.forEachLine {//读到的每行内容就回调到Lambda表达式,所以在Lambda表达式中拼接字符串就可以
content.append(it)
}
}
} catch (e: IOException) {
e.printStackTrace()
}
return content.toString()
}
getSharedPreferences("data", Context.MODE_PRIVATE).edit {
putString("name","Tom")
putInt("age",28)
}
SharedPreferences对象中提供了一系列的get方法,用于对存储的数据进行读取,每种get方法都对应了SharedPreferences.Editor中的一种put方法。
比如读取一个布尔型数据就使用getBoolean()方法,读取一个字符串就使用getString()方法。
示例写法如下:
val prefs = getSharedPreferences("data", Context.MODE_PRIVATE)
val name = prefs.getString("name", "")
val age = prefs.getInt("age", 0)
val married = prefs.getBoolean("married", false)
Android为了让我们能够更加方便地管理数据库,专门提供了一个SQLiteOpenHelper帮助类,借助这个类可以非常简单地对数据库进行创建和升级。示例写法如下:
class MyDatabaseHelper(val context: Context, name: String, version: Int) : SQLiteOpenHelper(context, name, null, version) {
private val createBook = "create table Book (" +
"id integer primary key autoincrement," +
"author text," +
"price real," +
"pages integer," +
"name text)"
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(createBook)
Toast.makeText(context, "Create succeeded", Toast.LENGTH_SHORT).show()
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
}
}
class MyDatabaseHelper(val context: Context, name: String, version: Int) : SQLiteOpenHelper(context, name, null, version) {
…
private val createCategory = "create table Category (" +
"id integer primary key autoincrement," +
"category_name text," +
"category_code integer)"
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(createBook)
db.execSQL(createCategory)
Toast.makeText(context, "Create succeeded", Toast.LENGTH_SHORT).show()
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("drop table if exists Book")
db.execSQL("drop table if exists Category")
onCreate(db)
}
}
SQLiteDatabase中提供了一个insert()方法,专门用于添加数据。它接收3个参数:
示例写法如下:
val dbHelper = MyDatabaseHelper(this, "BookStore.db", 2)
val db = dbHelper.writableDatabase
val values1 = ContentValues().apply {
// 开始组装第一条数据
put("name", "The Da Vinci Code")
put("author", "Dan Brown")
put("pages", 454)
put("price", 16.96)
}
db.insert("Book", null, values1) // 插入一条数据
SQLiteDatabase中提供了一个非常好用的update()方法,用于对数据进行更新。
示例写法如下:
val dbHelper = MyDatabaseHelper(this, "BookStore.db", 2)
val db = dbHelper.writableDatabase
val values = ContentValues()
values.put("price", 10.99)
db.update("Book", values, "name = ?", arrayOf("The Da Vinci Code"))
SQLiteDatabase中提供了一个delete()方法,专门用于删除数据。
示例写法如下:
val dbHelper = MyDatabaseHelper(this, "BookStore.db", 2)
val db = dbHelper.writableDatabase
db.delete("Book", "pages > ?", arrayOf("500"))
SQLiteDatabase中还提供了一个query()方法用于对数据进行查询。这个方法的参数非常复杂,最短的一个方法重载也需要传入7个参数。参数的详细解释见下表:
query()方法参数 | 对应SQL部分 | 描 述 |
---|---|---|
table | from table_name | 指定查询的表名 |
columns | select column1, column2 | 指定查询的列名 |
selection | where column = value | 指定where的约束条件 |
selectionArgs | - | 为where中的占位符提供具体的值 |
groupBy | group by column | 指定需要group by的列 |
having | having column = value | 对group by后的结果进一步约束 |
orderBy | order by column1, column2 | 指定查询结果的排序方式 |
查询Book表中所有数据的示例写法:
val dbHelper = MyDatabaseHelper(this, "BookStore.db", 2)
val db = dbHelper.writableDatabase
// 查询Book表中所有的数据
val cursor = db.query("Book", null, null, null, null, null, null)
if (cursor.moveToFirst()) {
do {
// 遍历Cursor对象,取出数据并打印
val name = cursor.getString(cursor.getColumnIndex("name"))
val author = cursor.getString(cursor.getColumnIndex("author"))
val pages = cursor.getInt(cursor.getColumnIndex("pages"))
val price = cursor.getDouble(cursor.getColumnIndex("price"))
Log.d("MainActivity", "book name is $name")
Log.d("MainActivity", "book author is $author")
Log.d("MainActivity", "book pages is $pages")
Log.d("MainActivity", "book price is $price")
} while (cursor.moveToNext())
}
cursor.close()
添加数据的方法如下:
db.execSQL(
“insert into Book (name, author, pages, price) values(?, ?, ?, ?)”, arrayOf("The Da Vinci Code", "Dan Brown", "454", "16.96")
)
更新数据的方法如下:
db.execSQL("update Book set price = ? where name = ?", arrayOf("10.99", "The Da Vinci Code"))
删除数据的方法如下:
db.execSQL("delete from Book where pages > ?", arrayOf("500"))
查询数据的方法如下:
val cursor = db.rawQuery("select * from Book", null)
replaceData.setOnClickListener {
val db = dbHelper.writableDatabase
db.beginTransaction() // 开启事务
try {
db.delete("Book", null, null)
// if (true) {
// // 在这里手动抛出一个异常,让事务失败
// throw NullPointerException()
// }
val values = cvOf("name" to "Game of Thrones", "author" to "George Martin", "pages" to 720, "price" to 20.85)
db.insert("Book", null, values)
db.setTransactionSuccessful() // 事务已经执行成功
} catch (e: Exception) {
e.printStackTrace()
} finally {
db.endTransaction() // 结束事务
}
}
我们可以使用高阶函数简化SharedPreferences的用法,如下所示:
fun SharedPreferences.open(block: SharedPreferences.Editor.() -> Unit) {
val editor = edit()
editor.block()
editor.apply()
}
定义好了open函数之后,以后在项目中使用SharedPreferences存储数据就会更加方便了,写法如下:
getSharedPreferences("data", Context.MODE_PRIVATE).open {
putString("name", "Tom")
putInt("age", 28)
putBoolean("married", false)
}
Android官方的KTX库中就有一个自带的函数
getSharedPreferences("data", Context.MODE_PRIVATE).edit {
putString("name","Tom")
putInt("age",28)
}
新建一个ContentValues.kt文件,定义cvOf方法
vararg是可变参数
Pair的kv形式
for循环读取,最后返回ContentValues对象
运用了Kotlin的自动转换,进入when的某个分支,比如Int,那么value就会转换成Int类型
fun cvOf(vararg pairs: Pair): 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 Long -> cv.put(key, value)
is Short -> cv.put(key, value)
is Float -> cv.put(key, value)
is Double -> cv.put(key, value)
is Boolean -> cv.put(key, value)
is String -> cv.put(key, value)
is Byte -> cv.put(key, value)
is ByteArray -> cv.put(key, value)
null -> cv.putNull(key)
}
}
return cv
}
然后就可以使用如下的写法来简单创建ContentValues对象了:
val values = cvOf("name" to "Game of Thrones", "author" to "George Martin", "pages" to 720, "price" to 20.85)
db.insert("Book", null, values)
使用高阶函数进一步优化,借助apply函数
fun cvOf(vararg pairs: Pair) = ContentValues().apply {
for (pair in pairs) {
val key = pair.first
val value = pair.second
when (value) {
is Int -> put(key, value)
is Long -> put(key, value)
is Short -> put(key, value)
is Float -> put(key, value)
is Double -> put(key, value)
is Boolean -> put(key, value)
is String -> put(key, value)
is Byte -> put(key, value)
is ByteArray -> put(key, value)
null -> putNull(key)
}
}
}
KTX库中也有一个类似的库
val values= contentValuesOf("name" to "this is name","age" to 28)
db.insert("Book",null,values)
class MyClass {
fun method(param: T): T {
return param
}
}
在调用MyClass类和method()方法的时候,可以将泛型指定成具体的类型,如下所示:
val myClass = MyClass()
val result = myClass.method(123)
class MyClass {
fun method(param: T): T {
return param
}
}
此时的调用方式也需要进行相应的调整:
val myClass = MyClass()
val result = myClass.method(123)
可以看到,现在是在调用method()方法的时候指定泛型类型了。
另外,Kotlin还拥有非常出色的类型推导机制,例如传入了一个Int类型的参数,它能够自动推导出泛型的类型就是Int型,因此这里也可以直接省略泛型的指定:
val myClass = MyClass()
val result = myClass.method(123)
class MyClass {
fun method(param: T): T {
return param
}
}
默认泛型上界是Any?,为可空类型,如果你先设置不可空,那么修改上界为Any
6.5.1小节学习了高阶函数,写了StringBuilder.build的例子,这里可以使用泛型,它实现了和apply函数一样的功能
fun T.build(block:T.()->Unit):T{
block()
return this
}
类委托的核心思想在于将一个类的具体实现委托给另一个类去完成
但是委托也有一定的弊端,如果接口中的待实现方法比较少还好,要是有几十甚至上百个方法的话,每个都去这样调用辅助对象中的相应方法实现,写起了就非常复杂了。这个问题在Kotlin中可以通过类委托的功能来解决。
class MySet(val helperSet: HashSet) : Set by helperSet {
fun helloWorld() = println("Hello World")
override fun isEmpty() = false
}
现在MySet就成为了一个全新的数据结构类,它不仅永远不会为空,而且还能打印helloWorld(),至于其他Set接口中的功能,则和HashSet保持一致。这就是Kotlin的类委托所能实现的功能。
委托属性的核心思想是将一个属性(字段)的具体实现委托给另一个类去完成。
我们看一下委托属性的语法结构,如下所示:
这里使用by关键字连接了左边的p属性和右边的Delegate实例,这种写法就代表着将p属性的具体实现委托给了的Delegate类去完成。这里使用by关键字连接了左边的p属性和右边的Delegate实例,这种写法就代表着将p属性的具体实现委托给了的Delegate类去完成。当调用p属性的时候会自动调用Delegate类的getValue()方法,当给p属性赋值的时候会自动调用Delegate类的setValue()方法。
class MyClass {
var p by Delegate()
}
因此,我们还得对Delegate类进行具体的实现才行,代码如下所示:
整个委托属性的工作流程就是这样实现的,现在当我们给MyClass的p属性赋值时,就会调用Delegate类的setValue()方法,当获取MyClass中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
}
}
class Later(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 later(block: () -> T) = Later(block)
调用处,使用by later关键字,并且传入Lambda表达式
private val uriMatcher by later {
val matcher = UriMatcher(UriMatcher.NO_MATCH)
matcher.addURI(authority, "book", bookDir)
matcher.addURI(authority, "book/#", bookItem)
matcher.addURI(authority, "category", categoryDir)
matcher.addURI(authority, "category/#", categoryItem)
matcher
}
借助infix函数,我们可以使用一种更具可读性的语法来表达一段代码。
infix fun String.beginsWith(prefix: String) = startsWith(prefix)
这里给String类添加了一个beginsWith()函数,它用于判断一个字符串是否是以某个指定参数开头。而加上了infix关键字之后,beginsWith()函数就变成了一个infix函数,这样除了传统的函数调用方式之外,我们还可以用一种特殊的语法糖格式调用beginsWith()函数,如下所示:
if ("Hello Kotlin" beginsWith "Hello") {
// 处理具体的逻辑
}
infix fun A.with(that: B): Pair = Pair(this, that)
infix fun Collection.has(element: T) = contains(element)
class MyThread : Thread() {
override fun run() {
// 编写具体的逻辑
}
}
MyThread().start()
Thread{
//逻辑
}.start()
Kotlin还给我们提供了一种更加简单的开启线程的方式,写法如下:
thread {
// 编写具体的逻辑
}
Kotlin可以把内联函数中的泛型实化
在内联函数的泛型指定了reified,调用这个函数可以获得泛型的类型
inline fun getGenericType() = T::class.java
fun main() {
val result1 = getGenericType()
val result2 = getGenericType()
//打印出了result1 is class java.lang.String
//result2 is class java.lang.Integer
println("result1 is $result1")
println("result2 is $result2")
}
定义如下方法
inline fun startActivity(context: Context) {
val intent = Intent(context, T::class.java)
context.startActivity(intent)
}
调用处
startActivity(this)
如果有需要传入数据的跳转,则需要一个Lambda
inline fun startActivity(context: Context, block: Intent.() -> Unit) {
val intent = Intent(context, T::class.java)
intent.block()
context.startActivity(intent)
}
调用处
startActivity(this){
putExtra("param1","data")
}
一个泛型类或者泛型接口中的方法,它的参数列表是接收数据的地方,因此可以称它为in位置,而它的返回值是输出数据的地方,因此可以称它为out位置,如下图所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j9L90Cf5-1590142300356)(E:\Notes\Kotlin\assets\《代码》笔记.assets\1590047542291.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QMkDjvJh-1590142300360)(E:\Notes\Kotlin\assets\《代码》笔记.assets\1590047723717.png)]
class SimpleData(val data: T?) {
fun get(): T? {
return data
}
}
interface Transformer {
fun transform(t: T): String
}
object ServiceCreator {
private const val BASE_URL = "http://10.0.2.2/"
private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
fun create(serviceClass: Class): T = retrofit.create(serviceClass)
inline fun create(): T = create(T::class.java)
}
调用
val appService = ServiceCreator.create()
协程和线程是有点类似的,可以简单地将它理解成一种轻量级的线程。
要知道,我们之前所学习的线程是非常重量级的,它需要依靠操作系统的调度才能实现不同线程之间的切换。而使用协程却可以仅在编程语言的层面就能实现不同协程之间的切换,从而大大提升了并发编程的运行效率。
Kotlin并没有将协程纳入标准库的API当中,而是以依赖库的形式提供的。所以如果我们想要使用协程功能,需要先在app/build.gradle文件当中添加如下依赖库:
dependencies {
...
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1"
}
启动协程最简单的方式就是使用Global.launch函数,如下所示:
fun main() {
GlobalScope.launch {
delay(1500)
println("codes run in coroutine scope")
}
Thread.sleep(2000)
}
fun main() {
runBlocking {
println("codes run in coroutine scope")
delay(1500)
println("codes run in coroutine scope finished")
}
}
使用launch函数可以用于创建多个协程,如下所示:
fun main() {
runBlocking {
launch {
println("launch1")
delay(1000)
println("launch1 finished")
}
launch {
println("launch2")
delay(1000)
println("launch2 finished")
}
}
}
lauch函数的逻辑代码如果提取到单独的函数,就没有协程作用域了。那么如果调用像是delay这种挂起函数呢?
Kotlin提供了suspend关键字,使用它可以把函数声明成挂起函数,挂起函数之间都是可以互相调用的
suspend fun printDot() {
println(".")
delay(1000)
}
suspend关键字只能声明函数成为挂起函数,无法提供协程作用域
suspend fun printDot2()= coroutineScope {
launch {
println(".")
delay(1200)
}
}
常用的写法:使用job来取消协程
val job = Job()
val scope = CoroutineScope(job)
scope.launch {
// do something
}
job.cancel()//记得取消
runBlocking {
// val start = System.currentTimeMillis()
// val deferred1 = async {
// delay(1000)
// 5 + 5
// }
// val deferred2 = async {
// delay(1000)
// 4 + 6
// }
// println("result is ${deferred1.await() + deferred2.await()}.")
// val end = System.currentTimeMillis()
// println("cost ${end - start} milliseconds.")
// }
runBlocking {
val result= withContext(Dispatchers.Default){
5+5
}
println(result)
}
okhttp的协程写法
suspend fun getBaiduResponse() {
try {
val response = request("https://www.baidu.com/")
// 得到服务器返回的具体内容
println(response)
} catch (e: Exception) {
// 在这里对异常情况进行处理
}
}
suspend fun request(address: String): String {
return suspendCoroutine { continuation ->
HttpUtil.sendHttpRequest(address, object : HttpCallbackListener {
override fun onFinish(response: String) {
continuation.resume(response)
}
override fun onError(e: Exception) {
continuation.resumeWithException(e)
}
})
}
}
retrofit的协程写法
suspend fun Call.await(): T {
return suspendCoroutine { continuation ->
enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
val body = response.body()
if (body != null) continuation.resume(body)
else continuation.resumeWithException(RuntimeException("response body is null"))
}
override fun onFailure(call: Call, t: Throwable) {
continuation.resumeWithException(t)
}
})
}
}
suspend fun getAppData() {
try {
val appList = ServiceCreator.create().getAppData().await() // 这段代码想运行通过,需要将BASE_URL中的地址改成http://localhost/
println(appList)
// 对服务器响应的数据进行处理
} catch (e: Exception) {
// 对异常情况进行处理
e.printStackTrace()
}
}
fun max(vararg nums: Int): Int {
if (nums.isEmpty()) throw RuntimeException("Params can not be empty.")
var maxNum = nums[0]
for (num in nums) {
if (num > maxNum) {
maxNum = num
}
}
return maxNum
}
fun > max(vararg nums: T): T {
if (nums.isEmpty()) throw RuntimeException("Params can not be empty.")
var maxNum = nums[0]
for (num in nums) {
if (num > maxNum) {
maxNum = num
}
}
return maxNum
}
fun String.showToast(context: Context, duration: Int = Toast.LENGTH_SHORT) {
Toast.makeText(context, this, duration).show()
}
fun Int.showToast(context: Context, duration: Int = Toast.LENGTH_SHORT) {
Toast.makeText(context, this, duration).show()
}
调用处
"Data restored".showToast(this)//默认显示时间短
R.string.app_name.showToast(this)
"Data restored".showToast(this,Toast.LENGTH_LONG)//可以更改时长,而不是使用默认时间
fun View.showSnackbar(text: String, actionText: String? = null, duration: Int = Snackbar.LENGTH_SHORT, block: (() -> Unit)? = null) {
val snackbar = Snackbar.make(this, text, duration)
if (actionText != null && block != null) {
snackbar.setAction(actionText) {
block()
}
}
snackbar.show()
}
调用
view.showSnackbar("Data deleted", "Undo") {//第二个参数是action按钮的名字,Lambda表达式是按钮点击之后显示的内容
"Data restored".showToast(this)
}
ViewModel可以帮助Activity分担一部分工作,它是专门用于存放与界面相关的数据的。也就是说,只要是界面上能看得到的数据,它的相关变量都应该存放在ViewModel中,而不是Activity中,这样可以在一定程度上减少Activity中的逻辑。
另外,ViewModel还有一个非常重要的特性。我们都知道,当手机发生横竖屏旋转的时候,Activity会被重新创建,同时存放在Activity中的数据也会丢失。**而ViewModel的生命周期和Activity不同,它可以保证在手机屏幕发生旋转的时候不会被重新创建,只有当Activity退出的时候才会跟着Activity一起销毁。**因此,将与界面相关的变量存放在ViewModel当中,这样即使旋转手机屏幕,界面上显示的数据也不会丢失。
使用ViewModel要先添加依赖
implementation "androidx.lifecycle:lifecycle-extensions:2.1.0"
我们可以使用如下代码定义一个ViewModel,并加入一个counter变量用于计数:
class MainViewModel : ViewModel() {
var counter = 0
}
然后在Activity中使用如下代码即可获得ViewModel的实例:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//ViewModelProviders获取ViewModel
val viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
viewMode.counter++//获取ViewModel内部的属性,并且操作
}
}
如果需要通过构造函数向ViewModel传递参数,但是ViewModel没有构造函数,所以需要借助ViewModelProvider.Factor
声明构造函数,并且赋值给counter变量
class MainViewModel(countReserved: Int) : ViewModel() {
var counter= countReserved
class MainViewModelFactory(private val countReserved: Int) : ViewModelProvider.Factory {
override fun create(modelClass: Class): T {
return MainViewModel(countReserved) as T
}
}
感知Activity的生命周期并不复杂,但问题在于,在一个Activity中去感知它的生命周期非常简单,而如果要在一个非Activity的类中去感知Activity的生命周期,应该怎么办呢?
Lifecycles组件就是为了解决这个问题而出现的,它可以让任何一个类都能轻松感知到Activity的生命周期,同时又不需要在Activity中编写大量的逻辑处理。
MyObserver类实现LifecycleObserver接口
定义方法,使用注解表示生命周期
class MyObserver : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun activityStart() {
Log.d("MyObserver", "activityStart")
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun activityStop() {
Log.d("MyObserver", "activityStop")
}
}
在Activity中调用
lifecycle.addObserver(MyObserver())
class MyObserver (var lifecycle: Lifecycle): LifecycleObserver {
LiveData是Jetpack提供的一种响应式编程组件,它可以包含任何类型的数据,并在数据发生变化的时候通知给观察者。
LiveData特别适合与ViewModel结合在一起使用,虽然它也可以单独用在别的地方,但是绝大多数情况下,它都是使用在ViewModel当中的。
LiveData内部使用了Lifecycles组件来自我感知生命周期,从而在Activity销毁的时候释放,避免内存泄漏
Activity不可见状态的时候,如果LiveData数据发生变化,不会通知给观察者,也是基于Lifecycles组件
我们可以将ViewModel中的数据使用LiveData来包装,然后在Activity中去观察它,就可以主动将数据变化通知给Activity了。
MainViewModel的counter是MutableLiveData()类型,是一个可变的LiveData,泛型是Int
在init代码块为counter.value赋值,实现之前保存的数据通过构造函数传入,再恢复
plusOne方法先获取,如果空就为0,再+1
class MainViewModel(countReserved: Int): ViewModel() {
val counter = MutableLiveData()
init {
counter.value = countReserved
}
fun plusOne() {
val count = counter.value ?: 0
counter.value = count + 1
}
fun clear() {
counter.value = 0
}
}
Activity中调用为
plusOneBtn.setOnClickListener {
viewModel.plusOne()
}
clearBtn.setOnClickListener {
viewModel.clear()
}
viewModel.counter.observe(this, Observer{ count ->
infoText.text = count.toString()
})
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-alpha05"
可以写为
viewModel.counter.observe(this) { count ->
infoText.text = count.toString()
})
val counter: LiveData
get() = _counter
private val _counter = MutableLiveData()
init {
_counter.value = countReserved
}
fun plusOne() {
val count = _counter.value ?: 0
_counter.value = count + 1
}
fun clear() {
_counter.value = 0
}
private val userLiveData = MutableLiveData()
val userName: LiveData = Transformations.map(userLiveData) { user ->
"${user.firstName} ${user.lastName}"
}
private val userIdLiveData = MutableLiveData()
val user: LiveData = Transformations.switchMap(userIdLiveData) { userId ->
Repository.getUser(userId)
}
fun getUser(userId: String) {
userIdLiveData.value = userId
}
getUserBtn.setOnClickListener {
val userId = (0..10000).random().toString()
viewModel.getUser(userId)
}
viewModel.user.observe(this, Observer { user ->
infoText.text = user.firstName
})
上面的例子是有传入userId作为参数的,但是如果没有参数呢?
把旧的数据取出来,重新赋值给自己,也能触发一次数据变化,如果refresh方法一样
private val refreshLiveData = MutableLiveData()
val refreshResult = Transformations.switchMap(refreshLiveData) {
Repository.getUser("")
}
fun refresh() {
refreshLiveData.value = refreshLiveData.value
}
先来看一下Room的整体结构。它主要由Entity、Dao和Database这3部分组成,每个部分都有明确的职责,详细说明如下。
apply plugin: 'kotlin-kapt'//插件
dependencies {
....
implementation "androidx.room:room-runtime:2.1.0"
kapt "androidx.room:room-compiler:2.1.0"
}
首先定义一个Entity,也就是实体类。
@Entity
data class User(var firstName: String, var lastName: String, var age: Int) {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}
接下来定义Dao,代码如下所示:
@Dao
interface UserDao {
@Insert
fun insertUser(user: User): Long
@Update
fun updateUser(newUser: User)
@Query("select * from User")
fun loadAllUsers(): List
@Query("select * from User where age > :age")
fun loadUsersOlderThan(age: Int): List
@Delete
fun deleteUser(user: User)
@Query("delete from User where lastName = :lastName")
fun deleteUserByLastName(lastName: String): Int
}
最后定义Database,定义好3个部分
数据库版本号
包含哪些实体
以及提供Dao层的访问实例
头部的@Database实现了前两个部分,多个实体类之间用逗号隔开
AppDatabase 必须继承RoomDatabase,兵器一定要声明成为抽象类,提供抽象方法的声明
在 companion object编写一个单例模式,提供一个获取实例的方法
@Database(version = 1, entities = [User::class])
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
private var instance: AppDatabase? = null
@Synchronized
fun getDatabase(context: Context): AppDatabase {
instance?.let {
return it
}
return Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "app_database").build().apply {
instance = this
}
}
}
}
在Activity中使用如下代码来进行数据库操作了:
val userDao = AppDatabase.getDatabase(this).userDao()
val user1 = User("Tom", "Brady", 40)
val user2 = User("Tom", "Hanks", 63)
addDataBtn.setOnClickListener {
thread {
user1.id = userDao.insertUser(user1)
user2.id = userDao.insertUser(user2)
}
}
updateDataBtn.setOnClickListener {
thread {
user1.age = 42
userDao.updateUser(user1)
}
}
deleteDataBtn.setOnClickListener {
thread {
userDao.deleteUserByLastName("Hanks")
}
}
queryDataBtn.setOnClickListener {
thread {
for (user in userDao.loadAllUsers()) {
Log.d("MainActivity", user.toString())
}
}
Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "app_database")
.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
.allowMainThreadQueries()//加上这句就可以在主线程执行,但是建议只在测试环境使用
.build().apply {
instance = this
dependencies {
implementation "androidx.work:work-runtime:2.2.0"
}
WorkManager的基本用法其实非常简单,主要分为以下3步:
第一步要定义一个后台任务,这里创建一个SimpleWorker类,代码如下所示:
class SimpleWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
Log.d("SimpleWorker", "do work in SimpleWorker")
return Result.success()
}
}
第二步,配置后台任务的运行条件和约束信息,代码如下所示:
val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java)
.setInitialDelay(5, TimeUnit.MINUTES)
.build()
val request2=PeriodicWorkRequest.Builder(SimpleWorker::class.java,15,TimeUnit.MINUTES).build()
最后一步,将构建出的后台任务请求传入WorkManager的enqueue()方法中,系统就会在合适的时间去运行了,代码如下所示:
WorkManager.getInstance(context).enqueue(request)
val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java)
.setInitialDelay(5,TimeUnit.MINUTES)//5分钟后运行
.build()
可以设置tag,之后通过tag来取消任务
也可以一次性取消所以后台任务
可以设置retry来重新执行
可以使用链式调用任务,比如时间同步数据、压缩、上传,按顺序执行
WorkManger在国产手机上不稳定,不能依赖它实现核心功能
DSL的全称是领域特定语言(Domain
Specific Language),它是编程语言赋予开发者的一种特殊能力,通过它我们可以编写出一些看似脱离其原始语法结构的代码,从而构建出一种专有的语法结构。
第9章的infix函数构建出的特有语法结构就是DSL
介绍了一个gradle和HTML的例子,暂时没学
class MyApplication : Application() {
companion object {
@SuppressLint("StaticFieldLeak")//消除编译器的报错
lateinit var context: Context
}
override fun onCreate() {
super.onCreate()
context = applicationContext
}
}
调用
Toast.makeText(MyApplication.context, this, duration).show()
class Person : Serializable{
...
}
//class Person : Parcelable {
// var name = ""
// var age = 0
//
// override fun writeToParcel(parcel: Parcel, flags: Int) {
// parcel.writeString(name) // 写出name
// parcel.writeInt(age) // 写出age
// }
//
// override fun describeContents(): Int {
// return 0
// }
//
// companion object CREATOR : Parcelable.Creator {
// override fun createFromParcel(parcel: Parcel): Person {
// val person = Person()
// person.name = parcel.readString() ?: "" // 读取name
// person.age = parcel.readInt() // 读取age
// return person
// }
//
// override fun newArray(size: Int): Array {
// return arrayOfNulls(size)
// }
// }
//}
@Parcelize
class Person(var name: String, var age: Int) : Parcelable
object LogUtil {
private const val VERBOSE = 1
private const val DEBUG = 2
private const val INFO = 3
private const val WARN = 4
private const val ERROR = 5
private var level = VERBOSE
fun v(tag: String, msg: String) {
if (level <= VERBOSE) {
Log.v(tag, msg)
}
}
fun d(tag: String, msg: String) {
if (level <= DEBUG) {
Log.d(tag, msg)
}
}
fun i(tag: String, msg: String) {
if (level <= INFO) {
Log.i(tag, msg)
}
}
fun w(tag: String, msg: String) {
if (level <= WARN) {
Log.w(tag, msg)
}
}
fun e(tag: String, msg: String) {
if (level <= ERROR) {
Log.e(tag, msg)
}
}
}
架构主要分成三部分 :
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fTjveEpw-1590314475000)(E:\Notes\Kotlin\assets\《代码》笔记.assets\1590240327219.png)]
class SunnyWeatherApplication : Application() {
companion object {
const val TOKEN = "" // 填入你申请到的令牌值
@SuppressLint("StaticFieldLeak")
lateinit var context: Context
}
override fun onCreate() {
super.onCreate()
context = applicationContext
}
}
import com.google.gson.annotations.SerializedName
class PlaceResponse(val status: String, val places: List)
class Place(val name: String, val location: Location, @SerializedName("formatted_address") val address: String)
class Location(val lng: String, val lat: String)
interface PlaceService {
@GET("v2/place?token=${SunnyWeatherApplication.TOKEN}&lang=zh_CN")
fun searchPlaces(@Query("query") query: String): Call
}
object ServiceCreator {
private const val BASE_URL = "https://api.caiyunapp.com/"
private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
fun create(serviceClass: Class): T = retrofit.create(serviceClass)
inline fun create(): T = create(T::class.java)
}
object SunnyWeatherNetwork {
private val placeService = ServiceCreator.create(PlaceService::class.java)
suspend fun searchPlaces(query: String) = placeService.searchPlaces(query).await()
private suspend fun Call.await(): T {
return suspendCoroutine { continuation ->
enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
val body = response.body()
if (body != null) continuation.resume(body)
else continuation.resumeWithException(RuntimeException("response body is null"))
}
override fun onFailure(call: Call, t: Throwable) {
continuation.resumeWithException(t)
}
})
}
}
}
object Repository {
fun searchPlaces(query: String) = fire(Dispatchers.IO) {
val placeResponse = SunnyWeatherNetwork.searchPlaces(query)
if (placeResponse.status == "ok") {
val places = placeResponse.places
Result.success(places)
} else {
Result.failure(RuntimeException("response status is ${placeResponse.status}"))
}
emit(result)
}
}
class PlaceViewModel : ViewModel() {
private val searchLiveData = MutableLiveData()
val placeList = ArrayList()//用于缓存界面上显示的城市数据,因为原则上相关数据要保存,稍后旋转屏幕才不会丢失
val placeLiveData = Transformations.switchMap(searchLiveData) { query ->
Repository.searchPlaces(query)
}
fun searchPlaces(query: String) {
searchLiveData.value = query
}
}
class PlaceFragment : Fragment() {
val viewModel by lazy { ViewModelProviders.of(this).get(PlaceViewModel::class.java) }
private lateinit var adapter: PlaceAdapter
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_place, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val layoutManager = LinearLayoutManager(activity)
recyclerView.layoutManager = layoutManager
adapter = PlaceAdapter(this, viewModel.placeList)
recyclerView.adapter = adapter
searchPlaceEdit.addTextChangedListener { editable ->
val content = editable.toString()
if (content.isNotEmpty()) {
viewModel.searchPlaces(content)
} else {
recyclerView.visibility = View.GONE
bgImageView.visibility = View.VISIBLE
viewModel.placeList.clear()
adapter.notifyDataSetChanged()
}
}
viewModel.placeLiveData.observe(this, Observer{ result ->
val places = result.getOrNull()
if (places != null) {
recyclerView.visibility = View.VISIBLE
bgImageView.visibility = View.GONE
viewModel.placeList.clear()
viewModel.placeList.addAll(places)
adapter.notifyDataSetChanged()
} else {
Toast.makeText(activity, "未能查询到任何地点", Toast.LENGTH_SHORT).show()
result.exceptionOrNull()?.printStackTrace()
}
})
}
}