kotlin基础知识
基础语法
kotlin中的特殊语法
- object
创建匿名内部类的写法
kotlin中声明单例的写法
object修饰的类是单例类
object Test{
fun getData(s :String):String{
return s
}
}
fun main(args: Array<String>) {
Test.getData("")
}
- String!
只会在java和kotlin互调的时候出现:编译器认为是一个java类型却用在了kotlin上 - Unit
- 没有返回值的函数的隐藏类型
- s函数作为参数时需要显示注明
kotlin中的基础说明
- kotlin中的Class不是编译为class, 而是KClass
- kotlin中没有封装类
java中的封装类会被转换为基本数据类型 - Kotlin类型空值敏感
- Kotlin中没有静态变量和静态方法
object KotlinObject{ /** * 不声明 : KotlinObject.INSTANCE.sayMessage("s"); * 声明 : KotlinObject.sayMessage("s"); */ @JvmStatic fun sayMessage(string: String){ println(string) } }
java和kotlin中互相调用的注意点
- kotlin中接受一个java对象的时候,如果你不能确定这个对象是否为空,一定需要将kotlin的接受对象设为可空类型的,否则会报空指针异常
函数与Lambda闭包
基础
- 函数的特性语法
- 支持默认参数:减少重载函数的数量
- 函数体只有一个语句可以直接赋值给这个函数
fun echo(name:String) = prinln("$name")
- 嵌套函数
- 与内部类类似,内部函数可以访问外部函数的局部变量
- 用于:在某些条件下触发递归的函数 or 不希望被外部函数访问到的函数
- 不推荐
- 扩展函数
kotlin: 可以静态的给一个类扩展它的成员方法和成员变量, 不具备运行时的多态效果
----kotlin------
public fun File.readText(charset: Charset = Charsets.UTF_8): String = readBytes().toString(charset)
----Java------
public class JavaMain {
public static void main(String[] args) {
File file = new File("readme.md");
System.out.println(FilesKt.readText(file, Charsets.UTF_8));
}
}
- 扩展函数会被编译为 public static final
- 用于对第三方或无法控制的类新增需要用到的方法
- Lambda闭包
- 如果lambda没有参数, 可以省略箭头符号 ->
- 如果lambda是函数的最后一个参数,可以将大括号放在小括号的外面
- 如果函数只有一个参数且这个参数是lambda,则可以省略小括号
//lambda闭包声明
var name = {str: String -> println(str)}
fun main(args: Array<String>) {
name("name")
}
//编译为
Function1<String, Unit> echo = (Function1)echo.INSTANCE;
- 高阶函数
函数或者lambda的参数又是一个函数或lambda
fun onlyIf(isDebug: Boolean, block: () -> Unit){
if(isDebug) block()
}
fun main(args: Array<String>) {
onlyIf(true){
println("打印日志")
}
}
- 函数是“一等公民”, 可以用对象:: 方法名 引用一个函数声明
- 内联函数
- Kotlin的Lambda是一个匿名对象,大量重复的lambda表达式,会生成很多临时的无用对象
- inline 进行修饰,这样当方法在编译的时候就拆解方法的调用为语句调用,进而减少创建不必要的对象
- 过度使用会造成编译器的编译负担,同时使代码块很庞大
- 一般仅用于修饰高阶函数
类与对象
- 构造函数
- Kotlin中默认的类是public final的
- 主构造函数和次构造函数
一个类有多个构造函数,需要显示声明它的次级构造函数,次级构造函数必须直接或间接的继承主构造函数或者父类的构造函数
- 访问修饰符
private protected public internal
- internal 一个模块的类可以访问 跨模块的不可以 用于项目的结构化扩展
- 伴生对象
- 一个类的伴生对象只能有一个
class StringUtils {
companion object{
fun isEmpty(str: String):Boolean{
return str == ""
}
}
}
fun main(args: Array<String>) {
StringUtils.isEmpty("")
}
//Java
StringUtils.Companion.isEmpty("");
- 单例类
//单例的写法 推荐
class Single private constructor(){
companion object{
fun get() : Single{
return Holder.instance
}
}
private object Holder{
val instance = Single()
}
}
fun main(args: Array<String>) {
val single = Single.get()
}
- 动态代理
- 在运行时动态地对某些东西代理
- 在语言层面原生支持的动态代理:by
- kotlin会将动态代理编译为静态代理
- 比java的动态代理效率高
interface Animal{
fun bark()
}
class Dog : Animal{
override fun bark() {
println("wang wang")
}
}
class Zoo(animal: Animal): Animal by animal
fun main(args: Array<String>) {
Zoo(Dog()).bark()
}
- Kotlin中特有的类
- 数据类 data class 类名字 final 类型 不能再添加open方法 自动重写toString hashCode equals copy方法
- 枚举类:与java中的类似,但很少使用,用更强大密闭类代替
enum class Command{
A, B, C, D
}
fun exec(command: Command) = when(command){
Command.A -> {}
Command.B -> {}
Command.C -> {}
Command.D -> {}
}
- 密闭类 sealed class 可以有子类,但子类需要放在一个文件中,所以一般将子类写在内部。 密闭类可以有扩展子类
sealed class SupperCommand{
object A: SupperCommand()
object B: SupperCommand()
object C: SupperCommand()
object D: SupperCommand()
//扩展子类
class PACE(var paceL: Int) : SuperCommand()
}
fun exec(supperCommand: SupperCommand) = when(supperCommand){
SupperCommand.A -> println("A")
SupperCommand.B -> println("B")
SupperCommand.C -> println("C")
SupperCommand.D -> println("D")
is SupperCommand.PACE -> {}
}
fun main(args: Array<String>) {
exec(SupperCommand.A)
}
kotlin中的高级特性
- 解构
将类拆解并分别赋值
常用于遍历map
var map = mapOf<String, String>("key" to "key", "value" to "value")
for((k,v) in map){
println("$k ----- $v")
}
- 循环与集合操作符
var list = arrayListOf<Char>('a','b','c')
val a = list.map { it - 'a' }.filter { it > 0 }.find{it > 1}
println(a)
-
运算符重载
. 通过operator关键字
. 修饰一个方法的时候,表示方法命指代一个运算符
. operator:将一个函数标记为重载一个操作符或者实现一个约定
. 一定是定义好的运算符,不能凭空重载运算符,运算符有上线 -
作用域函数
. kotlin内置一系列可以对数据进行变换的函数,与集合操作符号相似,但集合操作符值只能用于集合的操作变换,而作用域函数可以用于所有对象
. run{...} with(T){...} let{...} apply{...} also{...}
fun main(args: Array<String>) {
val user = User("zhangsan")
//let 和 run 都会返回闭包的执行结果,区别在于let有闭包参数,run没有闭包参数
var letResult= user.let{"let::${it.javaClass}"}
//lambda的特性,如果只有一个参数的时候,可以省略不写,用it替代
var letResult2 = user.let{user : User -> "let::${user.javaClass}"}
println(letResult)
val runResult = user.run { "run::${this.javaClass}" }
println(runResult)
//also 和 apply都不返回闭包的执行结果,区别在于also有闭包参数,apply没有
user.also {
println("also::${it.javaClass}")
}.apply {
println("apply::${this.javaClass}")
}.name = "hello"
//takeIf 的闭包返回一个判断结果, 为false时,takeIf函数会返回空
//takeUnless 与 takeIf 刚好相反,闭包的判断结果, 为true时函数会返回空
user.takeIf { it.name.length > 0 } ?.also { println("姓名为${it.name}") } ?: print("姓名为空")
user.takeUnless { it.name.length > 0 } ?.also { println("姓名为空") } ?: print("姓名为${user.name}")
//重复执行当前闭包
repeat(5){
println(user.name)
println(it)
}
//with比较特殊,不是以扩展方法的形式存在,而是一个顶级函数
with(user){
this.name = "with"
}
user.apply {
this.name = "with"
}
}
- 中缀表达式
. 和运算符的重载一样,本质都是一个特殊的函数,通过函数的调用完成
. 一个函数只有用于两个角色类似的对象时才将其声明为中缀函数, 如果一个方法会改动其接受者,那么不要声明为中缀形式。
. infix关键字
fun main(args: Array<String>) {
println(5 vs 6)
}
//Int. 表示函数的接收者
infix fun Int.vs(num: Int): CompareResult =
if(this - num < 0){
CompareResult.LESS
}else if(this - num > 0){
CompareResult.MORE
}else{
CompareResult.EQUAL
}
sealed class CompareResult{
object MORE: CompareResult(){
override fun toString(): String {
return "大于"
}
}
object LESS: CompareResult(){
override fun toString(): String {
return "小于"
}
}
object EQUAL: CompareResult(){
override fun toString(): String {
return "等于"
}
}
}
- kotlin中的特殊符号
. 反引号:解决关键字冲突问题 将一个不合法的字符变为合法的
不推荐使用
fun`1234`(){}
使用场景较小:比如internal 只能用于kotlin中, java当作public
如果某个类不希望被java访问, 则可以将这个类做一些特殊不合法的字符
. == 等同于java的equals === 等同于java的 ==
. typealias
类似于c和c++中的def 将一个类映射到另一个类上
可以用在跨平台上,提供兼容性 - DSL
Domain Specific Language
领域专用语言
. 提高开发效率 减小沟通成本
. Lambda 高阶函数 扩展函数 运算符重载 中缀表达式 - 总结
[图片上传失败...(image-bda9ca-1579446616919)]
[图片上传失败...(image-62dc93-1579446616919)]
语法特性解析
变量、常量与只读
. var和val最本质的区别是val不能有setter,但val 可以通过重写get方法达到改变它的值的效果
. 编译时常量 const val a = 0 const只能修饰object的属性 或 top-level变量 const变量的值必须在编译期间确定下来,所以它的类型只能是String或基本类型
因为对象的值在编译器是不确定的,会随着在运行时分配内存位置不一致,导致对象不是一个固定的对象。
空安全是如何实现的
尝试调用空对象的成员变量或方法会触发空指针异常
- 每次引用对象的时候,都去进行空对象判空,在运行期避免对象空指针
- 通过静态代码检查,编译插件检查,在编译期避免空指针异常
kotlin是以上两种方式的结合
内联的特殊情况
- 在kotlin中,内部Lambda是不允许中断外部函数执行的
- inline的Lambda可以中段外部函数调用
- croossinline不允许inline的Lambda中断外部函数执行
- noinline拒绝内联 通常用于修饰一个返回函数为内联函数的时候
Kotlin的真泛型与实现方法
- kotlin的泛型支持限定泛型的参数类型,支持多个类型, java的泛型会在编译时将泛型参数抹去,变为object
class Test<T> where T : Callback, T : Runnalble{
fun add(t: T){
t.run()
t.callback()
}
}
//java
public <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException{...}
//kotlin
//inline关键字不能省略 因为必须在编译时知道T的类型 reified表示T是一个真泛型
inline fun <reified T> Gson.fromJson(json: String): T{
return fromJson(json, T::class.java)
}
- reified表示T是一个真泛型, 只能修饰函数,不能修饰类,所以类可以通过下面的方法在运行时拿到泛型参数,从而使得类也有真泛型
//android中实现MVP
fun main(args: Array<String>) {
val b = View<Presenter>().presenter
//等同于
val a = View.Companion.invoke<Presenter>().presenter
}
class View<T>(val clazz: Class<T>){
val presenter by lazy { clazz.newInstance() }
//伴生对象会在构造函数调用之前,也就是类被加载到类加载器的时候创建好
companion object{
//重载了invoke操作符,同时调用了构造函数,并将当前的泛型类型,传递给了view的构造函数,所以在运行时可以拿到clazz运行变量
inline operator fun <reified T> invoke() = View(T::class.java)
}
}
class Presenter{
override fun toString(): String {
return "Presenter"
}
}
协程
Kotlin中的相关注解
- @JvmOverloads: 在有默认参数值的方法中使用@JvmOverloads注解,则Kotlin就会暴露多个重载方法。
网友评论