2021/1/11 ~ 2021/1/17

125 阅读3分钟

矩阵置换

矩阵的转置是指将矩阵的主对角线翻转,交换矩阵的行索引与列索引。

 

示例 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 //报错
}