lateinit var
lateinit主要用于var声明的变量,然而它不能用于基本数据类型,如Int、Long等,我们需要用Integer这种包装类作为替代,如何让用var声明的基本数据类型变量也具有延迟初始化的效果,一种可参考的解决方案是通过Delegates.notNull,这是利用Kotlin中委托的语法来实现的。我们会在后续介绍它的具体用法,当前你可以通过一个例子来认识这种神奇的效果:
fun main() {
var test by Delegates.notNull<Int>()
fun doSomething() {
test = 1
println("test value is ${test}")// test = 1
}
doSomething()
}
泛型空安全
// 泛型定义处 泛型使用处
// ↓ ↓
fun <T> saveSomething(data: T) {
val set = sortedSetOf<T>()
// 空指针异常
// ↓
set.add(data)
}
fun main() {
// 编译通过
// ↓
saveSomething(null)
}
在上面的代码中,虽然我们定义的泛型参数是“T”,函数的参数是“data: T”,看起来 data 好像是不可为空的,但实际上,我们是可以将 null 作为参数传进去的。这时候,编译器也不会报错。紧接着,由于 TreeSet 内部无法存储 null,所以我们的代码在“set.add(data)”这里,会产生运行时的空指针异常。
出现这样的问题的原因,其实是因为泛型的参数 T 给了我们一种错觉,让我们觉得:T 是非空的,而“T?”才是可空的。实际上,我们的 T 是等价于 <T: Any?> 的,因为 Any? 才是 Kotlin 的根类型。这也就意味着,泛型的 T 是可以接收 null 作为实参的。
那么,saveSomething() 这个方法,正确的写法应该是怎样的呢?答案其实也很简单:
// 增加泛型的边界限制
// ↓
fun <T: Any> saveSomething(data: T) {
val set = sortedSetOf<T>()
set.add(data)
}
fun main() {
// 编译无法通过
// ↓
saveSomething(null)
}
以上代码中,我们为泛型 T 增加了上界“Any”,由于 Any 是所有非空类型的“根类型”,这样就能保证我们的 data 一定是非空的。这样一来,当我们尝试往 saveSomething() 这个方法里传入 null 的时候,编译器就会报错,让这个问题在编译期就能暴露出来。
看到这里,相信你也可以总结出 Kotlin 空安全的第四条准则了:明确泛型可空性
。我们不能被泛型 T 的外表所迷惑,当我们定义 的时候,一定要记住,它是可空的。在非空的场景下,我们一定要明确它的可空性,这一点,通过增加泛型的边界就能做到 <T: Any>。
kotlin中 inilne crossline noinline关键字
inline fun runRunable(block :()->Unit){
val runnable = Runnable{
block()
}
runnable.run();
}
这段代码会报错,block不能写在这里,以下是具体原因
1、inline关键字是代表runRunable这个函数是内联函数,内联函数的作用是消除lambda的开销,当runRunable被调用的时候,编译器会把当前runRunable的方法的字节码粘贴到被调用的地方,当runRunable前面添加了内联支持,那么该函数的所有函数类型的参数,自动会被内联,也就是说block函数也会被内联,当一个函数被内联之后,那么这个函数中(lambda中就可以直接使用returern关键字进行函数返回),一般情况下lmbda中是不不可以直接用用return关键字进行返回的,要用也必须在return关键字后面加上标签(return@runnable)这种方式,但是这个return只能返回lmbda表达式后面的代码不能执行,外层函数不受影响。
2、这里报错的原因是因为,block被内联,他的代码会直接粘贴到被调用的地方,而且block函数中是可以直接使用return关键字进行返回的,但是它又被放在了另外一个lambda函数中执行,。在lambda中,是不能直接使用return关键字的,所以就会报错(就是说 block会被粘贴到val runnable = Runnable{ block() }中, 并且block可以写return,但是他却放在了Runnable中,相互冲突所以就报错了)总结一句话就是 内联函数可以使用return lambda中却不可以使用rteturn,这两者是冲突的
3.如果需要让内联函数,在另外一个内联函数中被调用的话,需要在当前这个内联函数前面加上crossinline关键字,crossinline关键字,代表由于上面的这中写法,是因为内联函数的lambda表表达式中允许直接使用return关键字和高阶函数的匿名内部类不允许直接使用retunrn关键字之间造成了冲突。二crollinline的关键字就好比一个契约,他用于保证在内联函数中的lambda表达式中一定不能是用return关键字,这样冲突就不会存在了,代码也就可以正常与运行了
inline fun runRunable (crollinline block :()->Unit){
//crollinline表示 调用runRunable方法的时候传递block的lambda中不可以加return,那么
//block就可以写在Runnable{}lambda中了
val runnable = Runnable{
block()
}
runnable.run();
}
中缀函数
中缀函数infix是不能定义成顶层函数的,它必须是某个类的成员函数,可以使用扩展函数的方式将它定义到某个类中;其次infix必须且只能接受一个参数,至于参数的类型没有限制
中缀函数必须是某个类型的扩展函数或者成员方法;·该中缀函数只能有一个参数;·虽然Kotlin的函数参数支持默认值,但中缀函数的参数不能有默认值,否则以上形式的B会缺失,从而对中缀表达式的语义造成破坏;·同样,该参数也不能是可变参数,因为我们需要保持参数数量始终为1个。
infix fun <A,B> A.to(that:B):Pair<A,B> = Pair(this,that)
泛型实化
泛型实化 函数必须是内联函数使用inline关键字 修饰过的函数,且reified关键字需要写在泛型的前面
inline fun <reified T> getType{
println(T::class.java)
}
inline fun <reified T> startActivity(context:Context){
val intent = Intent(context , T::class.java)
context.startActivity(intent)
}
startActivity<TestActivity>(context)
//传递参数
inline fun <reified T> startActivity(context:Context,block:Intent.()->Unit){
val intent = Intent(context , T::class.java)
intent.block()//1
block.invoke(intent)//2
block(intent)//3
context.startActivity(intent)
}
startActivity<TestActivity>(context){
putExtra("param","1")
}
泛型的协变和逆变
1、Student是Person的子类,但是List却不是List的子类,否则将会有类型转换的错误:
假如上述成立那么就会有如下代码
val students = listOf(Student(),Student())
val persons:List<Person> = students//会报错 假设可以成立
val pserson:Personns[0]//从persons集合中取数据 然后转换成Person类型 会报错因为persons集合中都是student实例
操作符重载 操作符重载的函数 和中缀函数一样有且只能有一个参数
操作符可以多次重载,当操作符重载再类中 和 扩展函数中都出现时,会先调用类中的函数
data class User(val age: Int, val name: String) {
//类中操作符重载 比扩展函数优先级高
operator fun plus(that: User) = User(that.age + age, (that.name + name))
}
//扩展函数操作符
operator fun User.plus(user: User): User {
return User(user.age + age, (user.name + name) * 3)
}
//报错'operator' modifier is inapplicable on this function: must have a single value parameter
operator fun User.plus(user: User,str:String): User {
return User(user.age + age, (user.name + name) * 3)
}
kotlin位运算符,kotlin取消了按位运算符的写法,and=& ,or=|,xor=^
(Int)->((Int)->Unit) == (Int)->Int->Int//括号可以去掉
假如我们有一个ContryTest类,里面有个放方法为test(),对象的实例为contryTest,我们要引用这个对象的方法,可以这么写 contryTest::test
定义一个类的构造方法引用的变量
class Book(val name:String)
fun main(){
val getBook = ::Book
//调用构造方法的引用 getBook的类型就是 (name:Book)->Book
val book = getBook("kotlin核心编程")
println(book.name)
//还可以引用某个类的成员变量 他的类型为 (Book)->String
val name = Book::name
val bookNames = listOf(Book("kotlin核心编程","flutter实战"))
.map(Book::name)
val bookNames = listOf(Book("kotlin核心编程","flutter实战"))
.map{book->
book.name
}
// 以上两种学法是等价的
}
高阶函数:
Kotlin存在一种特殊的语法,通过两个冒号来实现对于某个类的方法进行引用。以上面的代码为例,假如我们有一个CountryTest类的对象实例countryTest,如果要引用它的isBigEuropeanCountry方法,就可以这么写:countryTest::isBigEuropeanCountry
class CountryTest{
fun isBigEuropeanCountry():Boolean{
println("我被调用了")
return false
}
}
fun main() {
val countryTest = CountryTest()
val fun1 = countryTest::isBigEuropeanCountry
fun1.invoke()//我被调用了
fun1()//我被调用了
//因为是CountryTest类不是CountryTest的对象,所以调用isBigEuropeanCountry需要一个receiver
val fun2= CountryTest::isBigEuropeanCountry
fun2.invoke(countryTest)//我被调用了
fun2(countryTest)//我被调用了
}
此外,我们还可以直接通过这种语法,来定义一个类的构造方法引用变量。class Book(val name: String)fun main(args: Array) { val getBook = ::Book println(getBook("Dive into Kotlin").name)}可以发现,getBook的类型为(name:String)->Book。类似的道理,如果我们要引用某个类中的成员变量,如Book类中的name,就可以这样引用:Book::name以上创建的Book::name的类型为(Book)->String。
class Book(val name: String)
fun main() {
val book = Book("kotlin核心编程")
val getBook1 = ::Book//getBook的类型为(name:String)->Book
val getBook2: (name: String) -> Book = ::Book
//调用
val book11 = getBook1.invoke("kotlin核心编程")//方式1
val book12 = getBook1("kotlin核心编程")//方式2
val book21 = getBook2("kotlin核心编程")//方式1
val book22 = getBook2.invoke("kotlin核心编程")//方式2
//调用
val getName1 = Book::name//(Book)->String
val getName2: (book: Book) -> String = Book::name
val name11 = getName1.invoke(book)
val name12 = getName1(book)
val name21 = getName2.invoke(book)
val name22 = getName2(book)
}
fun main() {
listOf(1,2,3)
.forEach {
// foo(it)
foo(it).invoke()
foo(it)()
}
}
fun foo(n: Int) = {
println(n)
}
foo函数的返回类型是Function0。这也意味着,如果我们调用了foo(n),那么实质上仅仅是构造了一个Function0对象。这个对象并不等价于我们要调用的过程本身。通过源码可以发现,需要调用Function0的invoke方法才能执行println方法。所以,我们的疑惑也迎刃而解,上述的例子必须如下修改,才能够最终打印出我们想要的结果:fun foo(int: Int) = { print(int)}>>> listOf(1, 2, 3).forEach { foo(it).invoke() } // 增加了invoke调用 123也许你觉得invoke这种语法显得丑陋,不符合Kotlin简洁表达的设计理念。确实如此,所以我们还可以用熟悉的括号调用来替代invoke,如下所示:>>> listOf(1, 2, 3).forEach { foo(it)() }123
Kotlin还支持一种自运行的Lambda语法:
fun main() {
println({ x: Int -> println(x) }(1))
//结果是1
}
这么理解contract函数是个高阶函数 接收一个函数,函数名字为builder 参数为ContractBuilder 返回值为Unit的函数 可以写成public inline fun contract(builder:(ContractBuilder) ->; Unit) { } 类似这样,这样就好理解了一些了,把ContractBuilder 对象传递到lambda中这样就可以直接用ContractBuilder对象(隐士参数it)的方法了。原本写法ContractBuilder.() 这样只是一个扩展函数,这样写,在lambda中就可以直接使用this,或者可以去省去this直接调用ContractBuilder对象的方法了。一个语法糖,其实编译之后的字节码是一样的
如下图 String代表ContractBuilder,三种写法都可以,但是第一种写法,调用的时候更烦方便
但是让我不理解的是fun2中,lambda中隐士参数是this 也就是String对象,可以直接调用String的方法 而fun1和fun2中lambda隐士参数是it,只能通过it来调用传递进来的String对象的方法 我看了下 编译过后的代码 其实这三个方法都是会生成一个Function1实例,然后调用invoke方法 把String传递进去的,没有任何区别 请大神帮忙解释一下:其实是语法糖
lambda可以直赋值给个函数比如
fun foo(num:Int) = {
println(num)
}
fun foo(num:Int):(Int)->Unit{
println(num)
}
listOf(1,2,3,4).forEach{
foo(it)()//
foo(it).invoke()
}
kotlin Unit关键字
如何理解Unit?其实它与int一样,都是一种类型,然而它不代表任何信息,用面向对象的术语来描述就是一个单例,它的实例只有一个,可写为()。
kotlin枚举
enum class DayOfWeek(private val day: Int) {
MON(1),
TUE(2),
WEN(3),
THU(4),
FRI(5),
SAT(6),
SUN(7);
fun getDayNumber(): Int {
return day
}
}
设计模式原则
1、开闭原则:
对扩展开放,对修改关闭
2、里氏替换原则:
子类可以实现父类的抽象方法,但不能复写父类的非抽象方法;
子类可以增加自己特有的方法;
当子类的方法实现父类的方法时,方法的前置条件(即方法的参数)要比父类方法的输入参数更宽松;
当子类的方法实现父类的抽象方法的时,方法的后置条件(及方法的返回值)要比父类更加严格;
var list3 :MutableList<*> = mutableListOf(1,"2",true)
val list4:MutableList<out Any> = mutableListOf(1,"2",true)
协变
这两个list是一样的 *代表的就是泛型的上界 out & extends
携带接收者的labmda
fun html(init :Html.()->Unit):Html{
val html = Html()
init(html) //第一种
init.invoke(html) //第二种
html.init() //第三种
return html
}
class Html{
fun body(){
}
}
//调用
html {
body()
}
扩展方法
当扩展函数定义在class中的时候,只有当前类和子类可以访问,背后会在当前类中声明一个非静态的public方法
当扩展函数定义在kt文件中的时候,任何类都可以访问,需要导包
同名的类成员方法的优先级高于扩展方法,当扩展函数和现有类的成员方法同时存在时,Kotlin将会默认使用类的成员方法。
扩展函数始终是静态调度的
companion object伴生对象可以添加扩展函数
class Son{
companion object{
val gae = 10
}
}
fun Son.Companion.foo(){
print(age)
}
Son.foo()
扩展函数可以被继承示例如下:
open class Person(val name: String)
class User(name: String) : Person(name)
//person 扩展函数
fun Person.printName() {
println("Person name is $name")
}
//fun User.printName(){
// println("User name is $name")
//}
fun main() {
val person = Person("tom")
person.printName()//Person name is tom
val user = User("jack")
user.printName()//Person name is jack
//如果User.printName接触注释,那么user.printName()将会打印User name is jack
}
* * *
**扩展属性**
扩展属性不能有初始化器,以下代码不能通过编译 不能添加=0
val String.asd:Int = 0
get() {
return length
}
匿名内部类:
fun start(runnable: Runnable) {
runnable.run()
}
start {
println("start")
}
start2 {
println("start2")
}
start之所以可以这么调用其实是背后kotlin做了转换,生成了start2方法
因为函数是内联函数,所以block会被复制调用地方可以使用return返回整个函数,由于他是在Runnable的 lambda中调用, lambda中不允许直接returen 所以需要加crossinline
创建了一个runnable匿名类,然后把传递进来的lambda包装了一下
inline fun start2(crossinline block: () -> Unit) {
Runnable {
block.invoke()
}
}
object : Runnable{
override fun run() {
}
}
=
相当于编辑器生成了一个函数名为Runnable返回数据为Runnable接口的高阶函数
val r = Runnable {
}
=
inline fun Runnable(crossinline block:()->Unit):Runable{
return object : Runnable{
override fun run() {
block()
}
}
}