矩阵置换
矩阵的转置是指将矩阵的主对角线翻转,交换矩阵的行索引与列索引。
示例 1:
输入:[[1,2,3],[4,5,6],[7,8,9]]
输出:[[1,4,7],[2,5,8],[3,6,9]]
示例 2:
输入:[[1,2,3],[4,5,6]]
输出:[[1,4],[2,5],[3,6]]
提示:
1 <= A.length <= 1000
2 <= A[0].length <= 1000
思路: 根据矩阵的特性,如果需要置换,主要是将A[i][j] 转化成 A[j][i], 由此我们可知道,输出矩阵的大小为B[A[0].length][A.length].
代码
public static int[][] transpose(int[][] A) {
int r = A.length;
int c = A[0].length;
int[][] ans = new int[c][r];
// i:原来的列
for(int i = 0; i < c; i++){
//j:原来的行
for(int j = 0; j < r; j++){
// 原来的列是之后的行,原来的行是之后的列
ans[i][j] = A[j][i];
}
}
return ans;
}
kotlin 基础语法
变量
kotlin 变量不会默认初始化,主要和kotlin的判空机制有关
申明变量:
class A {
//kotlin 默认属性都是public
val ab = "string" //不可从新赋值
var a = "string"
var b? = null //可为空对象, 引用时 b?.xx 或者 b!!.xx
lateinit var c //稍后初始化
val d:String by lazy { //调用的时候再初始化
"String"
}
}
属性get和set
class A {
// getter / setter 函数有了专门的关键字 get 和 set
// getter / setter 函数位于 var 所声明的变量下面
// setter 函数参数是 value
// val 变量只有get方法
var a = "String"
get() {
retrun field + 'customStr'
}
set(value) {
field = value
}
val b = "String"
get() {
return field + 'customStr'
}
}
类
kotlin 自定义的类默认是不可以继承的,如果需要继承需加 open 关键字
类型强转
//'(activity as? NewActivity)' 之后是一个可空类型的对象,所以,需要使用 '?.' 来调用
(activity as? NewActivity)?.action()
if (activity is NewActivity) {
activity.action()
}
静态属性声明
//第一种,伴生类
class A {
companion object {
val info = "String" //使用 A.info
}
}
//第二种 创建一个object对象类,该类属于单例,里面所有的属性和方法都是静态
object A {
val info = "info"
}
//top-level 将方法和属性声明再class外面,这样就属于包属性,使用的时候直接是引入该包名即可。
package com.hu.test
val custsomInfo = "string"
class A {
}
// 使用
improt com.hu.test.customInfo
class B {
fun test() {
println(customInfo)
}
}
在实际使用中,在 object、companion object 和 top-level 中该选择哪一个呢?简单来说按照下面这两个原则判断:
如果想写工具类的功能,直接创建文件,写 top-level「顶层」函数。
如果需要继承别的类或者实现接口,就用 object 或 companion object。
常量
Kotlin 的常量必须声明在对象(包括伴生对象)或者「top-level 顶层」中,因为常量是静态的。
Kotlin 新增了修饰常量的 const 关键字。
Kotlin 中只有基本类型和 String 类型可以声明成常量。
构造函数
kotlin 的构造函数申明通常有2种
当一个类有多个构造器时,只需要把最基本、最通用的那个写成主构造器就行了
//第一种 主构造函数 第一个参数可以直接引用,而第二个参数需要赋值才能用
class A(var params: String, twoParams: String) {
lateinit var tempParams : String
init {
tempParams = twoParams
}
//声明次构造函数,必须调用主构造函数
constructor(paramsOne : String, paramsTwo: String, ParamsThree: Strig) : this(paramsOne, paramsTwo) {
}
}
//第二种 次构造函数
class A {
constructor() {
}
}
参数默认值和命名参数
//参数命名一定要注意,第一个参数命名后,后续所有的参数都需要使用参数命名
fun a(b: String = "name", c: String, d:String) {}
a(c="tt", d="tt")
a("tt", "cc", "dd")
a(b = "tt", "cc", "dd") //错误,第一个已经参数命名,后面的必须参数命名
raw原生字符串
原生字符串是用"""包裹起来的,它具有 最后输出的内容与写的内容完全一致,包括实际的换行,它也可以引用$符号
val name = "world"
val myName = "kotlin"
val text = """
Hi $name!
My name is $myName.\n
"""
println(text)
输出结果:
但对齐方式看起来不太优雅,原生字符串还可以通过 trimMargin() 函数去除每行前面的空格:
val text = """
|Hi world!
|My name is kotlin.
""".trimMargin()
println(text)
输出结果:
这里的 trimMargin() 函数有以下几个注意点:
- | 符号为默认的边界前缀,前面只能有空格,否则不会生效
- 输出时 | 符号以及它前面的空格都会被删除
- 边界前缀还可以使用其他字符,比如 trimMargin("/"),只不过上方的代码使用的是参数默认值的调用方式
try catch 和 ?. ?:
Kotlin 中的异常是不会被检查的,只有在运行时如果 sayHi 抛出异常,才会出错。 Java的属于编译时就回抛错
?: Kotlin 中的 Elvis 操作符
data?.length 当data为空的时候,可以使用?:来返回为空时候的值
val str: String? = "Hello"
val length: Int = str?.length ?: -1//当str为空的时候,length值为 -1
fun validate(user: User) {
val id = user.id ?: return // 👈 验证 user.id 是否为空,为空时 return
}
// 等同于
fun validate(user: User) {
if (user.id == null) {
return
}
val id = user.id
}
== 和 ===
- == :可以对基本数据类型以及 String 等类型进行内容比较,相当于 Java 中的 equals
- === :对引用的内存地址进行比较,相当于 Java 中的 ==
kotlin 泛形
Java代码 List<? extends TextView> textViews,其中 <? extends> 叫做上界配符,可以使 Java 泛型具有「协变性 Covariance」,协变就是允许上面的赋值是合法的
对应kotlin的 Array<out TextView》, 这代表着,该对象不能添加,只能查看。
而List<? super Textview>, 其中 <? super> 叫做下届配符, 可以使 Java 泛型具有「逆变性 Contravariance」。对应kotlin 中的 Array, 代表该对象能添加,但查看只返回object对象。
小结下,Java 的泛型本身是不支持协变和逆变的。
- 可以使用泛型通配符 ? extends 来使泛型支持协变,但是「只能读取不能修改」,这里的修改仅指对泛型集合添加元素,如果是 remove(int index) 以及 clear 当然是可以的。
- 可以使用泛型通配符 ? super 来使泛型支持逆变,但是「只能修改不能读取」,这里说的不能读取是指不能按照泛型类型读取,你如果按照 Object 读出来再强转当然也是可以的。
和 Java 泛型一样,Kolin 中的泛型本身也是不可变的。
- 使用关键字 out 来支持协变,等同于 Java 中的上界通配符 ? extends。
- 使用关键字 in 来支持逆变,等同于 Java 中的下界通配符 ? super。
class Producer<T> {
fun produce(): T {
...
}
}
val producer: Producer<out TextView> = Producer<Button>()
val textView: TextView = producer.produce() // 👈 相当于 'List' 的 `get`
class Consumer<T> {
fun consume(t: T) {
...
}
}
val consumer: Consumer<in Button> = Consumer<TextView>()
consumer.consume(Button(context)) // 👈 相当于 'List' 的 'add'
Kotlin 提供了另外一种写法:可以在声明类的时候,给泛型符号加上 out 关键字,表明泛型参数 T 只会用来输出,在使用的时候就不用额外加 out 了。
class Producer<out T> {
fun produce(): T {
...
}
}
val producer: Producer<TextView> = Producer<Button>() // 👈 这里不写 out 也不会报错
val producer: Producer<out TextView> = Producer<Button>() // 👈 out 可以但没必要
Java 中声明类或接口的时候,可以使用 extends 来设置边界,将泛型类型参数限制为某个类型的子集:
//T 的类型必须是 Animal 的子类型
class Monster<T extends Animal>{
}
//多个
class Monster<T extends Animal & Food>{
}
kotlin:
class Monster<T : Animal>
//多个
class Monster<T> where T : Animal, T : Food
//练习题
- 实现一个 fill 函数,传入一个 Array 和一个对象,将对象填充到 Array 中,要求 Array 参数的泛型支持逆变(假设 Array size 为 1)。
- 实现一个 copy 函数,传入两个 Array 参数,将一个 Array 中的元素复制到另外个 Array 中,要求 Array 参数的泛型分别支持协变和逆变。(提示:Kotlin 中的 for 循环如果要用索引,需要使用 Array.indices)
fun <T> fill(array : Array<in T>, t : T) {
array[0] = t
}
fun <T> copy(fromArray : <out T>, toArray : <in T>) {
fromArray.indices.forEatch {
toArray[it] = fromArray[it]
}
}
kotlin协程
kotlin协程是什么?
kotlin协程本质上是对线程的api封装工具包。主要为决解了直接使用线程的困难与不便(在java中可以使用Executor进行线程管理):
- 线程什么时候执行结束
- 线程间的相互通信
- 多个线程的管理
在什么时候适合使用协程
- 耗时任务:如网络请求,文件处理,加载图片等。
- 等待任务:如定时5秒网络请求处理
如何启动一个协程
// 方法一,使用 runBlocking 顶层函数 (测试用,因为它是线程阻塞的)
runBlocking {
getImage(imageId)
}
// 方法二,使用 GlobalScope 单例对象 (但在 Android 开发中同样不推荐这种用法,因为它的生命周期会和 app 一致,且不能取消)
// 可以直接调用 launch 开启协程
GlobalScope.launch {
getImage(imageId)
}
// 方法三,自行通过 CoroutineContext 创建一个 CoroutineScope 对象 (推荐:和上下文同一生命周期)
// 需要一个类型为 CoroutineContext 的参数
val coroutineScope = CoroutineScope(context)
coroutineScope.launch {
getImage(imageId)
}
切换线程
//切换到io线程
coroutineScope.launch(Dispatchers.IO) {
...
}
//切换到主线程
coroutineScope.launch(Dispatchers.Main) {
...
}
//使用withContext来指定线程,改内容执行完毕以后,会自动的切回到调用函数的线程,这就是用同步的方式写异步的代码
launch(Dispatchers.Main) { // 👈 在 UI 线程开始
val image = getImage(imageId)
avatarIv.setImageBitmap(image) // 👈 执行结束后,自动切换回 UI 线程
}
suspend fun getImage(imageId: Int) = withContext(Dispatchers.IO) {
...
}
kotlin协程挂起如何理解?
协程的挂起对象是什么?是线程?还是函数?都不是,协程挂起的对象是它自己本身。也就是用launch或者async函数中的代码块,当协程里的代码执行到某一个suspend函数时,这个协程就会被挂起。
launch(Dispatchers.Main) {
xxx //当执行到suspend函数时,这个协程会被挂起。
}
如何理解挂起?
挂起其实就是从当前线程挂起,也就是说,从当前线程脱离,切换到supend函数指定的线程进行执行(通常是withContext)。而当前线程就继续执行其余的代码,不会再执行协程里面的代码。
挂起的协程如何执行?
当协程被挂起的时候,会执行supend函数中的代码,执行完毕后,会自动的切换回当前线程继续执行协程里面的剩余代码
supend关键字
//并未切换线程,还是和调用函数同一个线程
suspend fun suspendingPrint() {
println("Thread: ${Thread.currentThread().name}")
}
//切换线程,用withContext
suspend fun suspendingGetImage(id: String) = withContext(Dispatchers.IO) {
...
}
从上面的例子可以看出来,suspend并没有起到切换线程的作用,它只是一个标志,标志该函数是一个耗时任务。提醒调用函数。
说到这里,Kotlin 协程的三大疑问:协程是什么、挂起是什么、挂起的非阻塞式是怎么回事,就已经全部讲完了。非常简单:
- 协程就是切线程;
- 挂起就是可以自动切回来的切线程;
- 挂起的非阻塞式指的是它能用看起来阻塞的代码写出非阻塞的操作,就这么简单。
Lambda 表达式
方法参数和方法返回
在kotlin中,允许方法参数和返回方法, 其实不管是方法参数还是方法返回,他们都是返回了函数类型对象。
例如:
//参数方法
fun a(params : (Int) -> String) {
...
}
a(fun(params : Int) : String {
return params.toString()
})
//lambda 简写
a{
it.toString()
}
//方法返回参数
fun b () : (Int) -> String {
retrun fun(params : Int) {
return params.toString()
}
}
方法赋值
kotlin 是采用::来将一个方法赋值给变量的
fun a(params : Int) {
}
val b = ::a
等价于
val b = fun(params : Int) {
}
//b(1) 等价 a(1) 等价 (::a) (1)
Lambda表达式
如果 Lambda 是函数的最后一个参数,你可以把 Lambda 写在括号的外面:
view.setOnClickListener() { v: View ->
switchToNextPage()
}
而如果 Lambda 是函数唯一的参数,你还可以直接把括号去了:
view.setOnClickListener { v: View ->
switchToNextPage()
}
另外,如果这个 Lambda 是单参数的,它的这个参数也省略掉不写:
view.setOnClickListener {
switchToNextPage()
}
为什么可以这么简写,因为Lambda表达式会根据上下文来推断你的参数和返回值。 例如上面的例子是通过setOnClickListener推断的:
fun setOnClickListener(onClick: (View) -> Unit) {
this.onClick = onClick
}
所有要使用lambda表达式,一定要让表达式指定参数类型和返回值类型。
var b = {
params : Int -> return params.toString()
}
var b : (Int) -> String = {
it.toString()
}
// 报错,未指定参数类型和返回类型,表达式也无法根据上下文来获取参数类型和返回类型
var b = {
it.toString()
}
Lambda表达式里面不能有return返回, 默认最后一行是返回值。
Kotlin 是不支持使用 Lambda 的方式来简写匿名类对象的,因为我们有函数类型的参数嘛,所以这种单函数接口的写法就直接没必要.
不过当和 Java 交互的时候,Kotlin 是支持这种用法的:当你的函数参数是 Java 的单抽象方法的接口的时候,你依然可以使用 Lambda 来写参数。但这其实也不是 Kotlin 增加了功能,而是对于来自 Java 的单抽象方法的接口,Kotlin 会为它们额外创建一个把参数替换为函数类型的桥接方法,让你可以间接地创建 Java 的匿名类对象。
这就是为什么,你会发现当你在 Kotlin 里调用 View.java 这个类的 setOnClickListener() 的时候,可以传 Lambda 给它来创建 OnClickListener 对象,但你照着同样的写法写一个 Kotlin 的接口,你却不能传 Lambda。因为 Kotlin 期望我们直接使用函数类型的参数,而不是用接口这种折中方案。
//申明的接口需要用 这种方式来设置
testInterface(object : InterfaceTest{
override fun testSomething() {
TODO("Not yet implemented")
}
})
-
在 Kotlin 里,有一类 Java 中不存在的类型,叫做「函数类型」,这一类类型的对象在可以当函数来用的同时,还能作为函数的参数、函数的返回值以及赋值给变量;
-
创建一个函数类型的对象有三种方式:双冒号加函数名、匿名函数和 Lambda;
-
一定要记住:双冒号加函数名、匿名函数和 Lambda 本质上都是函数类型的对象。在 Kotlin 里,匿名函数不是函数,Lambda 也不是什么玄学的所谓「它只是个代码块,没法归类」,Kotlin 的 Lambda 可以归类,它属于函数类型的对象。
扩展函数和属性
kotlin允许直接给类添加方法和属性, 如:在顶层中为String类添加方法
//扩展方法支持写在顶层和类里面
fun String.c(params : Int) {
xxxx
}
//扩展属性只支持写在顶层
val String.d
get() = 1111
class b {
fun main() {
"test".c(10)
(String::c)("test",10) //必须调用时候把 调用者传入进去
("test"::c)(10)
println(String.d) // 输出 1111
"test".f() //输出 sss
(String::c)f("test") //不支持,因为该函数不是顶层扩展函数
}
//当扩展函数写在类里面,当前调用该函数作用域只能在当前类里面,而且该函数也无法进行::赋值
fun String.f() {
println("sss")
}
val m1 = String::c //支持
val m2 = String::f //报错
}