kotlin

595 阅读9分钟

一、基础语法

1.1 变量与常量

1.2 可变参数vararg

1.3 字符串模板

$varName 表示变量值

${varName.fun()} 表示变量的方法返回值

Log.e("渠道号","Channel:${BuildConfigApp.getChannelName(this)},参数为:$params")
   

1.4 条件表达式

if else 与 Elvis 操作符(?:);如果 ?: 左侧表达式非空,elvis 操作符就返回其左侧表达式,否则返回右侧表达式。 请注意,当且仅当左侧为空时,才会对右侧表达式求值。

val count = array?.length ?: -1

1.5 when表达式

when 将它的参数和所有的分支条件顺序比较,直到某个分支满足条件.

when (url) {
    "sample://flutterPage" -> {
       FlutterPage.startActivity(context,"nidalee")
     }
     "sample://nativePage" -> {
       UserModule.startPersonalPage(context)
     }
   }

1.6 接口

  • Kotlin接口和Java8类似,都是使用interface来定义一个接口,同样也允许方法有默认实现。与Java一样,一个类或者对象可以实现一个或多个接口。接口中的属性只能是抽象的,不允许初始化值,接口不会保存属性值,实现接口时,必须重写属性
interface TestInterface {
	 //抽象的name属性
	 var name: String
	 //未实现的方法
     fun functionOne()  
     //默认实现的方法   
     fun functionTwo() {       
        println("this is function two")  
    }
}

class User: TestInterface {
	override var name: String = "Nidalee" //重写属性
}


1.7 继承

kotlin 中所有类都继承该 Any 类,它是所有类的超类,对于没有超类型声明的类是默认超类:

如果一个类要被继承,可以使用 open 关键字进行修饰。

在基类中,使用fun声明函数时,此函数默认为final修饰,不能被子类重写。如果允许子类重写该函数,那么就要手动添加 open 修饰它。

1.8 对象的声明

Kotlin 使用 object 关键字来声明一个对象,使用它可以很方便的获得一个单例

object LocationManager {
    fun getCurrentLocation() {
    	//doSomeThing
    }
  }

//使用
LocationManager.getCurrentLocation()

1.9 伴生对象

在Java中可以通过static关键字声明静态的属性或方法。但是在Kotlin中并没有延续这个关键字,而是使用伴生对象实现,在class内部声明一个companion object代码块,其内部的成员类似java的静态成员

class MyClass {
    companion object {
        fun testFun(){
        	print("call testFun")
        }
    }
}

val instance = MyClass.testFun()   // 访问到对象的内部元素

2、函数调用

1、Java中在函数调用上存在怎样的不变?

参数过多容易混淆不明其意

2、如何用Kotlin去优化?

使用命名参数

3、Java的函数重载存在怎样的不变?

代码模板过多

4、如何用Kotlin去优化?

默认参数

5、Java和Kotlin互调时,重载函数需要注意哪些?

①java中没有默认参数的概念,如果在java中调用kotlin中的默认参数重载函数时,必须指定所有的参数值。可以使用@JvmOverloads来优化,kotlin自动生成多个重载函数供Java来使用

②kotlin调用java不能使用命名参数和默认值的

3、Java与Kotlin混编

Kotlin 在设计之初就考虑了和 Java 的互操作,这也是 Kotlin 的一大优点。在AndroidStudio中,可以通过convertFileToJava or Kotlin将代码转化。

  • 属性读写:Kotlin可以自动识别Java中的getter/setter;在Java中可以过getter/setter操作Kotlin属性。Java约定的getter和setter方法在Kotlin中表示为属性。如果Java类只有一个setter,那么它在Kotlin中不会作为属性可见,因为Kotlin目前不支持只写(set-only)属性。

  • 空安全

  • 注解的使用:@JvmField是Kotlin和Java互相操作属性经常遇到的注解;@JvmStatic是将对象方法编译成Java静态方法;@JvmOverloads主要是Kotlin定义默认参数生成重载方法;@file:JvmName指定Kotlin文件编译之后生成的类名。

  • kotin可以直接在一个文件中不需要声明类, 直接写方法 java在调用的时候, 是通过文件名kt.方法名()调用这个方法

  • 关键字冲突

  • Java和Kotlin中调用Kotlin中object修饰的类:kotlin调用时类名+方法名。而java调用的时候是类名+INSTANCE+方法名。为什么要加Instance(kotlin编译成java代码时Object修饰的类会生成一个静态对象),如何摆脱?(Kotlin中被@JvmStatic修饰的方法,编译成Java代码后就变成了一个静态方法,所以在Java中调用是不需要加上INSTANCE的。)

4、扩展函数

利用Kotlin中的扩展函数特性让我们的代码变得更加简单和整洁。扩展函数是Kotlin语言中独有的新特性,利用它可以减少很多的样板代码,大大提高开发的效率;

扩展函数非常适合Kotlin和Java语言混合开发模式。在很多公司一些比较稳定良好的库都是Java写,也完全没必要去用Kotlin语言重写。但是想要扩展库的接口和功能,这时候扩展函数可能就会派上用场。

使用Kotlin的扩展函数还有一个好处就是没有副作用,不会对原有库代码或功能产生影响。

fun ImageView.load(url: String?) {
  url?.let {
    val placeholderId = RandomPlaceholder.getPlaceHolder()
    val config: ImageConfig = ImageConfig.builder()
        .useCrossFade(false)
        .url(it)
        .placeholder(placeholderId)
        .errorSrc(placeholderId)
        .build()
    load(config)
  }
}

fun View.click(function: () -> kotlin.Unit) {
  RxView.clicks(this)
      .throttleFirst(600, TimeUnit.MILLISECONDS)
      .subscribe {
        function()
      }
}

扩展函数的原理:扩展函数实际上就是一个对应Java中的静态函数,这个静态函数参数为接收者类型的对象,然后利用这个对象就可以访问这个类中的成员属性和方法了,并且最后返回一个这个接收者类型对象本身。这样在外部感觉和使用类的成员函数是一样的。

Java调用Kotlin中的扩展函数:静态函数调用,和我们之前讲的顶层函数在Java中调用类似,不过唯一不同是需要传入一个接收者对象参数。

注意:

1、扩展函数不能打破扩展类的封装性,不能像成员函数一样直接访问内部私有函数和属性。(原理: 原理很简单,扩展函数访问实际是类的对象访问,由于类的对象实例不能访问内部私有函数和属性,自然扩展函数也就不能访问内部私有函数和属性了)

2、扩展函数实际上是一个静态函数是处于类的外部,而成员函数则是类的内部函数。

3、父类成员函数可以被子类重写,而扩展函数则不行

##5、标准函数库 在Kotlin标准函数库的源码中,也就是Standard.kt文件当中,提供了一些通用的扩展函数,它们适用于任何对象,如run,with,let,also,apply;但是对于这些标准函数,它们的用法极其相似,以至于在开发中无法确定该选择哪一个使用。


public inline fun TODO(): kotlin.Nothing { /* compiled code */ }

public inline fun TODO(reason: kotlin.String): kotlin.Nothing { /* compiled code */ }

public inline fun repeat(times: kotlin.Int, action: (kotlin.Int) -> kotlin.Unit): kotlin.Unit { contract { /* compiled contract */ }; /* compiled code */ }

public inline fun <R> run(block: () -> R): R { contract { /* compiled contract */ }; /* compiled code */ }

public inline fun <T, R> with(receiver: T, block: T.() -> R): R { contract { /* compiled contract */ }; /* compiled code */ }

public inline fun <T> T.also(block: (T) -> kotlin.Unit): T { contract { /* compiled contract */ }; /* compiled code */ }

public inline fun <T> T.apply(block: T.() -> kotlin.Unit): T { contract { /* compiled contract */ }; /* compiled code */ }

public inline fun <T, R> T.let(block: (T) -> R): R { contract { /* compiled contract */ }; /* compiled code */ }

public inline fun <T, R> T.run(block: T.() -> R): R { contract { /* compiled contract */ }; /* compiled code */ }

public inline fun <T> T.takeIf(predicate: (T) -> kotlin.Boolean): T? { contract { /* compiled contract */ }; /* compiled code */ }

public inline fun <T> T.takeUnless(predicate: (T) -> kotlin.Boolean): T? { contract { /* compiled contract */ }; /* compiled code */ }

fun <R> synchronized(lock: kotlin.Any, block: () -> R): R { contract { /* compiled contract */ }; /* compiled code */ }


4.1 函数分类

  • 普通函数与扩展函数

    使用扩展函数的形式可以简化空安全的判断

	with(webview.settings) {
      this?.javaScriptEnabled = true
      this?.databaseEnabled = true
   }
   
   webview.settings?.run {
      javaScriptEnabled = true
      databaseEnabled = true
	}

  • 传递参数this与it

    如果对比T.runT.let,它们可以实现相同的功能,之间的区别是传递的参数不同。

    对于T.run来说,T.run仅仅只是被当做了 block: T.() 扩展函数的调用块。因此,在其作用域内,T 可以被 this 指代。

    对于T.let来说, 是传递它自己本身到函数中block: (T),它可以在函数作用域内部使用it来指代. 所以我把这个称之为传递 it参数

	choose_media_recycler_folder.run {
      layoutManager = LinearLayoutManager(mContext)
      adapter = mFolderAdapter
    }
    
    url?.let {
    	imageView.load(it)
    }

  • 返回值本身与最后一行

    T.letT.also来说,它们之间传递的参数都是一样的,同为it参数,但是它们的返回值又有些不同。一个是返回的自己本身,一个返回的是函数的最后一行。合理利用返回值的特性可以很好的完成一些逻辑复杂的链式调用。

	
	val original = "abc"
	original.let {
  		println("The original String is $it") // "abc"
  		it.reversed() // evolve it as parameter to send to next let
	}.let {
  		println("The reverse String is $it") // "cba"
  		it.length  // can be evolve to other type
	}.let {
  		println("The length of the String is $it") // 3
	}
	
	original.also {
  		println("The original String is $it") // "abc"
  		it.reversed() // even if we evolve it, it is useless
	}.also {
  		println("The reverse String is ${it}") // "abc"
  		it.length  // even if we evolve it, it is useless
	}.also {
  		println("The length of the String is ${it}") // "abc"
	}
	

总结

在Kotlin中Standard.Kt文件中短短不到100来行库函数源码,但是它们作用是非常强大,可以说它们是贯穿于整个Kotlin开发编码过程中。使用它们能让你的代码会更具有可读性、更优雅、更简洁。善于合理使用标准库函数,也是衡量你对Kotlin掌握程度标准之一,因为你去看一些开源Kotlin源码,随处可见的都是使用各种标准库函数。

6、DSL

Kotlin语言最显著的特点就是简洁,Kotlin中的诸多语言特性都是为了精简代码同时保证代码具有更高的可读性。如扩展函数、高阶函数、默认参数、lambda、函数参数、带接收者的函数参数等等,通过合理的组合这些语言特性实现DSL则可以将这种简洁性发挥到极致。

注意点:不要过多的嵌套

//DSL使用之前
val dialogFragment = CustomDialogFragment.newInstance()
dialogFragment.title = "title"
dialogFragment.message = "message"
dialogFragment.rightClicks(key = "确定", dismissAfterClick = true) {
    toast("clicked!")
}
val ft = supportFragmentManager.beginTransaction()
val prev = supportFragmentManager.findFragmentByTag("dialog")
if (prev != null) {
    ft.remove(prev)
}
ft.addToBackStack(null)
dialogFragment.show(ft, "dialog")

//DSL使用之后
showDialog {
    title = "title"
    message = "message"
    rightClicks {
        toast("clicked!")
    }
}

inline fun TextView.addTextChangedListener(init: EditTextChangeListener.() -> Unit) {
  addTextChangedListener(EditTextChangeListener().apply(init))
}

class EditTextChangeListener : TextWatcher {
  private var afterTextChanged: ((s: Editable?) -> Unit)? = null
  private var beforeTextChanged: ((s: CharSequence?, start: Int, count: Int, after: Int) -> Unit)? = null
  private var onTextChanged: ((s: CharSequence?, start: Int, before: Int, count: Int) -> Unit)? = null

  fun afterTextChanged(listener: ((s: Editable?) -> Unit)?) {
    this.afterTextChanged = listener
  }

  fun beforeTextChanged(listener: ((s: CharSequence?, start: Int, count: Int, after: Int) -> Unit)? = null) {
    this.beforeTextChanged = listener
  }

  fun onTextChanged(listener: ((s: CharSequence?, start: Int, before: Int, count: Int) -> Unit)?) {
    this.onTextChanged = listener
  }

  override fun afterTextChanged(s: Editable?) {
    afterTextChanged?.invoke(s)
  }

  override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
    beforeTextChanged?.invoke(s, start, count, after)
  }

  override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
    onTextChanged?.invoke(s, start, before, count)
  }

}

//函数调用
add_friend_search_et.addTextChangedListener {
      onTextChanged { s, start, before, count ->
        s?.let {
          if(it.isNotEmpty()){
            add_friend_search_clear.show()
          }else{
            add_friend_search_clear.gone()
          }
        }
      }
    }

实现步骤:

1、扩展函数:给TextView扩展了一个addTextChangedListener方法,这样就可以通过此方法直接给TextView添加指定的文本监听方法。

2、带接收者的函数参数:

在扩展方法中只有唯一的参数init,其类型是EditTextChangeListener.() -> Unit。即带有EditTextChangeListener参数类型的函数。在扩展方法内部,调用了addTextChangedListener(EditTextChangeListener().apply(init)),对TextView进行了监听器的设置,而这个监听器为自定义的,这样在调用扩展方法时就可以 调用自定义的public接口进行配置了。