kotlin筑基

一. Kotlin最基础语法

1.1 变量

var表示可变变量,val表示不可变变量,注意并不是常量。变量名写在前面,类型写在后面,编译器如果能推断出你的类型,那么类型是不用声明的 。

var age:Int = 18
val name :String = "zhangsan"

编译器自动推断类型。

var age = 18
val name = "zhangsan"

空安全类型编译器报错

val name2: String = null

在这里插入图片描述

如果还是想给赋初始化值的话

val name2: String? = null

注意:String和String?是两个完全不同的类型。不能互相赋值的。

在这里插入图片描述

如果还是想强行通过编译呢?加上两个感叹号,!!表示强行的意思,当你确定name2不为null,那么就可以强行赋值给name

var age: Int = 18
var name: String = "zhangsan"
var name2: String? = null
fun main() {
    name = name2!!
}

但是我们反过来,name赋值给name2可以吗,

可以的,name确定不为空,name2包含可空和不可空,所以可以赋值,

var age: Int = 18
var name: String = "zhangsan"
var name2: String? = null
fun main() {
    name = name2!!
    name2 = name
}

1.2 函数

fun声明函数,printLen函数名,str: String参数和参数类型,: String 返回值,"12312123 s t r " 模板字符串, str"模板字符串, str"模板字符串,符号可以引用一个变量,将这个变量组合成新的字符串。

fun printLen(str: String): String {
    println("12312123  $str")
    return str
}

测试一下

var age: Int = 18
var name: String = "zhangsan"
var name2: String? = null
fun main() {
//    name = name2!!
//    name2 = name
    printLen("1231231")
}

fun printLen(str: String): String {
    println("adads $str")
    return str
}

在这里插入图片描述

二. Kotlin与Java完全兼容,就可以随便调用了吗?

2.1 语法变化

Java 与 Kotlin 交互语法变化

kotlin是可以写在文件中而不一定在类中,

//Utils.kt
fun echo(name: String) {
    println("$name")
}

java是要写在类中,在java中调用kotlin文件中的代码,UtilsK后缀要加Kt,kotlin文件中都会被编译为public static变量,所以在java的main函数中可以直接文件名.函数名调用。

// Main.java
public static void main(String[] args) {
	UtilsKt.echo("hello");
}

kotlin的特殊语法,object开头,后面跟一个类的声明

object Test {
    fun sayMessage(msg: String) {
        println(msg)
    }
}

调用:

kotlin code

Test.sayMessage("hello")

java code

Test.INSTANCE.sayMessage("hello");

接下来我们康康类的class两种语言的区别

java code

TestMain.class

kotlin code

TestMain::class.java

在kotlin中调用java类的class,JavaMain由于这个类是java类,所以JavaMain::class.java后缀需要加上.java

java code

public class JavaMain {

}

kotlin code

fun testClass(clazz: Class<JavaMain>) {
    println(clazz.simpleName)
}
fun main() {
    testClass(JavaMain::class.java)
}

下面演示调用kotlin的class,如果是kotlin类的class,需要KClass这样声明,KotlinMain::class调用的时候直接调用,后缀不用加上.java因为是kotlin类的class。

kotlin code

class KotlinMain {
}
import kotlin.reflect.KClass

fun testClass(clazz: Class<JavaMain>) {
    println(clazz.simpleName)
}

fun testClass(clazz: KClass<KotlinMain>) {
    println(clazz.simpleName)
}

fun main() {
    testClass(JavaMain::class.java)
    testClass(KotlinMain::class)
}

2.2 kotlin关键字处理

比方说在java中,有个变量,变量名叫in

public class JavaMain {
    public static String in = "in";
}

在kotlin中 ,in是一个关键字,在kotlin中调用java这个变量就不能直接调用了,需要加上单引号

fun main() {
    println(JavaMain.`in`)
}

三. 新手使用Kotlin常碰到的问题

3.1 Kotlin 没有封装类

看看demo

java code

public class A implements AInterface {
    public static final A a = new A();
    @Override
    public void putNumber(int num) {
        System.out.println("int");
    }

    @Override
    public void putNumber(Integer num) {
        System.out.println("Integer");
    }
}
public interface AInterface {
    void putNumber(int num);
    void putNumber(Integer num);
}

kotlin code

fun main() {
    A.a.putNumber(123)
}

输出:int

在这里插入图片描述

我们发现输出是int而不是Interge,也就是说kotlin运行的时候调用的是基本数据参数的那个方法,而不会去执行有封装类的那个重载方法。

3.2 Kotlin 类型空值敏感

java code

public class A {
    public static String format(String string) {
        return string.isEmpty() ? null : string;
    }
}

在kotlin中调用format方法,

fun main() {
    val format = A.format("123")
    val format1: String = A.format("123")
    val format2: String = A.format("123")
}

三种返回类型,

第一种类型自动推断,带有感叹号的String,只有在java和kotlin互调才会有,不能手动声明,由编译器认为这是个java的类型,但是放到了kotlin代码中去使用,所以带有感叹号
在这里插入图片描述

第二,三种类型是手动声明的。

3.3 Kotlin 没有静态变量与静态方法

我们加上@JvmStatic注解之后,在Java中就可以像kotlin一样调用这个方法了,

这个方法在代码编译之后也会被编译为public static的方法。

object Test {
    @JvmStatic
    fun sayMessage(msg: String) {
        println(msg)
    }
}

java code

Test.sayMessage("hello")

而不需要这样

Test.INSTANCE.sayMessage("hello");

四. 函数也能嵌套?这个特性要注意

4.1 函数的特性语法

函数举例

fun echo(name: String) {
    println("$name")
}
fun echo(name: String ="zhangtao") {
    println("$name")
}
fun echo(name: String) = println("$name")

参数可以有默认值,

fun echo(name: String ="zhangtao") {
    println("$name")
}

测试,不传参数,直接取默认值

fun main() {
    echo()
}

在这里插入图片描述

优点,可以大大减少重载函数的数量。

函数体只有一条语句是可以直接赋值给这个函数的。

fun echo(name: String) = println("$name")

4.2 嵌套函数

用途:
在某些条件下触发递归的函数不希望被外部函数访问到的函数,内部函数可以访问外部函数的局部变量。

fun function() {
    var str = "helle world"
    fun say(count: Int = 10) {
        println(str)
        if (count > 0) {
            say(count - 1)
        }
    }
    say()
}

测试

fun main() {
    function()
}

在这里插入图片描述

4.3 扩展函数

File是类名,readText扩展的方法名,

fun File.readText(charset: Charset = Charsets.UTF_8):
        String = readBytes().toString(charset)

test,跟我们正常调用方法一样。

fun main() {
    val file = File("test.txt")
    println(file.readText())
}

在java中怎么使用我们的扩展函数。

java code

public class A {
    public static void main(String[] args) {
        File file = new File("test.txt");
        String s = FilesKt.readText(file, Charsets.UTF_8);
        System.out.println(s);
    }
}

扩展函数的静态解析:

demo: 不具备多态的运行类型,

open class Animal
class Dog : Animal()

fun Animal.name() = "animal"
fun Dog.name() = "dog"

fun Animal.printfName(animal: Animal) {
    println(animal.name())
}
fun main() {
    Dog().printfName(Dog())
}

输出

在这里插入图片描述

反编译看看这段代码

annimal的扩展函数name()

fun Animal.name() = "animal"  
//对应下面的反编译代码
@NotNull
   public static final String name(@NotNull Animal $this$name) {
      Intrinsics.checkNotNullParameter($this$name, "$this$name");
      return "animal";
   }

dog的扩展函数name()

fun Dog.name() = "dog"
//对应下面的反编译代码
 @NotNull
   public static final String name(@NotNull Dog $this$name) {
      Intrinsics.checkNotNullParameter($this$name, "$this$name");
      return "dog";
   }

Animal的扩展函数printfName

   fun Animal.printfName(animal: Animal) {
    println(animal.name())
	}
	//对应下面的反编译代码
	//@NotNull Animal $this$printfName  这个参数指的是扩展这个函数的那个对象
   public static final void printfName(@NotNull Animal $this$printfName, @NotNull Animal animal) {
      Intrinsics.checkNotNullParameter($this$printfName, "$this$printfName");
      Intrinsics.checkNotNullParameter(animal, "animal");
      String var2 = name(animal);
      System.out.println(var2);
   }
   public static final void main() {
      printfName((Animal)(new Dog()), (Animal)(new Dog()));
   }

main函数反编译后,(Animal)(new Dog()), (Animal)(new Dog())我们传入的Dog对象实际上被强转为Animal的,所以最终输出annimal的扩展方法name().

4.4 DSL的基础Lambda闭包的语法

java code 常规写法

public class A {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                
            }
        });
        thread.start();
    }
}

Lambda写法Runnable对象省略为一对括号,加箭头,

public class A {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {

        });
        thread.start();
    }
}

kotlin code

fun main() {
    val thread = Thread({ -> Unit })
    thread.start()
}

如果Lambda没有参数,可以省略箭头符号

fun main() {
    val thread = Thread({})
    thread.start()
}

如果Lambda是函数的最后一个参数,可以将大括号放在小括号外面

fun main() {
    val thread = Thread(){}
    thread.start()
}

如果函数只有一个参数且这个参数是Lambda,则可以省略小括号

fun main() {
    val thread = Thread {}
    thread.start()
}

Lambda 闭包声明

fun main() {
    echo.invoke("123")
    echo("123")
}
val echo = { name: String ->
    println(name)
}

参数是有上线的,最多只能有22参数,

fun main() {
    lambdaA(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
}

val lambdaA = { a1: Int, a2: Int, a3: Int, a4: Int, a5: Int, a6: Int, a7: Int, a8: Int, a9: Int, a10: Int, a11: Int, a12: Int, a13: Int, a14: Int, a15: Int, a16: Int, a17: Int, a18: Int, a19: Int, a20: Int, a21: Int, a22: Int ->
    println("!23")
}

输出:!23

我们加多一个参数总共23个参数,会抛一个异常

Lambda会被编译成一个匿名内部类,Function23,有23参数

源码中只有22个参数

在这里插入图片描述

解决:
在这里插入图片描述

手动定义一个Function23.

4.5 高阶函数

kotlin code

fun main() {
    onlyif(true) {
        println("打印日志")
    }
}

fun onlyif(isDebug: Boolean, block: () -> Unit) {
    if (isDebug) block()
}

重点:函数是“一等公民”

fun main() {
    val runnable = Runnable {
        println("run")
    }
    val function: () -> Unit
    function = runnable::run

    onlyif(true, function)
}

fun onlyif(isDebug: Boolean, block: () -> Unit) {
    if (isDebug) block()
}

输出:run

正常执行

4.6 内联函数

用内联优化代码

Kotlin 的 Lambda 是一个匿名对象

可以使用inline 修饰方法,这样当方法在编译时就会拆解方法的调用为语句调用,进而减少创建不必要的对象

fun main() {
    val runnable = Runnable {
        println("run")
    }
    val function: () -> Unit
    function = runnable::run

    onlyif(true, function)
}

inline fun onlyif(isDebug: Boolean, block: () -> Unit) {
    if (isDebug) block()
}

加上inline反编译

public static final void onlyif(boolean isDebug, @NotNull Function0 block) {
      int $i$f$onlyif = 0;
      Intrinsics.checkNotNullParameter(block, "block");
      if (isDebug) {
         block.invoke();
      }

   }

main函数,语句调用

public static final void main() {
      Runnable runnable = (Runnable)null.INSTANCE;
      Function0 function = null;
      function = (Function0)(new Function0(runnable) {
         // $FF: synthetic method
         // $FF: bridge method
         public Object invoke() {
            this.invoke();
            return Unit.INSTANCE;
         }

         public final void invoke() {
            ((Runnable)this.receiver).run();
         }
      });
      boolean isDebug$iv = true;
      int $i$f$onlyif = false;
      function.invoke();
   }

没加反编译

public static final void onlyif(boolean isDebug, @NotNull Function0 block) {
      Intrinsics.checkNotNullParameter(block, "block");
      if (isDebug) {
         block.invoke();
      }

   }

main函数,方法调用

 public static final void main() {
      Runnable runnable = (Runnable)null.INSTANCE;
      Function0 function = null;
      function = (Function0)(new Function0(runnable) {
         // $FF: synthetic method
         // $FF: bridge method
         public Object invoke() {
            this.invoke();
            return Unit.INSTANCE;
         }

         public final void invoke() {
            ((Runnable)this.receiver).run();
         }
      });
      onlyif(true, function);
   }

减少临时对象的生成。inline只会用于修饰高阶函数。

五. 类与对象

1. 构造函数

Kotlin 的类默认是 public final 的,init是在构造函数被调用的时候执行。

open class MainActivity(var int: Int) : AppCompatActivity(), View.OnClickListener {
    init {
        println("loge====")
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test)
    }

    override fun onClick(v: View?) {

    }
}

Kotlin 构造方法,隐藏的主构造函数跟着类名后面,次级构造函数,间接或直接继承主构造函数或者父类的构造函数。

class MyView : View {
    constructor(context: Context?) : super(context)
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    )
}

2. 访问修饰符

private

protected

public

internal:同一个模块内可以互相调用。

3. 伴生对象

kotlin code

class StringUtils {
    companion object {
        fun ifEmpty(str: String): Boolean {
            return "" == str
        }
    }
}

kotlin code调用

fun main() {
    StringUtils.ifEmpty("123")
}

java code 调用

public class A {
    public static void main(String[] args) {
        StringUtils.Companion.ifEmpty("213123")
    }
}

4. 单例类

class Single private constructor() {
    companion object {
        fun get(): Single {
            return Holder.instance
        }
    }

    private object Holder {
        val instance = Single()
    }
}

fun main() {
    Single.get()
}

5. 动态代理

类的动态代理

fun main() {
    Zoo(Dog()).back()
}

interface Animal {
    fun back()
}

class Dog : Animal {
    override fun back() {
        println("12312")
    }
}

class Zoo(animal: Animal) : Animal by animal

输出 12312

假设我们Zoo中

class Zoo(animal: Animal) : Animal by animal{
    override fun back() {
        println("Zoo")
    }
}

输出:Zoo

6. Kotlin 特有的类

数据类

public final
getter()/setter()
toString(),hashCode(),equals(),copy()

data class User(var id: Int, var name: String)

枚举类

密闭类

sealed class TestOne {
    object A : TestOne()
    object B : TestOne()
    object C : TestOne()
    object D : TestOne()
}

六. 高级特性

1. 解构

operator:将一个函数标记为重载一个操作符或者实现一个约定

class User(var age: Int, var name: String) {
    operator fun component1() = age
    operator fun component2() = name
}

fun main() {
    val user = User(12, "name")
    val (age, name) = user
    println(age)
    println(name)
}

常用在遍历map

fun main() {

    val map = mapOf<String, String>("key" to "key", "value" to "value")
    for ((k, v) in map) {
        println("$k, $v")
    }
}

2. 循环与集合操作符

五种常用循环

fun main() {
    for (i in 1..10) {
        println(i)
    }
    for (i in 1 until 10) {
        println(i)
    }
    for (i in 10 downTo 1) {
        println(i)
    }
    for (i in 1..10 step 2) {
        println(i)
    }
    repeat(10) {
        println(it)
    }

}

遍历集合,解构

fun main() {
    val list = arrayListOf<String>("a", "b", "c")
    for (str in list) {
        println(str)
    }
    for ((index, str) in list.withIndex()) {
        println("$index $str")
    }
    
}

3. 集合操作符

集合操作符

fun main() {
    val arrayListOf = arrayListOf<Char>('a', 'b', 'c', 'd')
    val a = arrayListOf.map { it - 'a' }.filter { it > 0 }.find { it > 1 }
}

4. 作用域函数

五个常用函数

run{},with(T){},let{},apply{},also{}.

import javax.jws.soap.SOAPBinding.Use

data class User(var name: String)

fun main() {
    val user = User("1231")
    //let与run都会返回闭包的执行结果,区别在于let有闭包参数,而run没有闭包参数
    val let = user.let { user: User -> "let::${user.javaClass}" }
    println(let)
    val run = user.run { "run::${this.javaClass}" }
    println(run)

    //also与apply都不返回闭包的执行结果,区别在于aso有闭包参数,而apply没有闭包参数
    user.also { println("also::${it.javaClass}") }
    user.apply { println("apply::${this.javaClass}") }

    //takeIf 的闭包返回一个判断结果,为false时,takeIf函数会返回空
    // takeUnless 与 takeIf 刚好相反, 闭包的判断结果,为true时函数会返回空
    user.takeIf { it.name.length > 0 }?.also { println("姓名为${it.name}") } ?: println("姓名为空")
    user.takeUnless { it.name.length > 0 }?.also { println("姓名为空") } ?: println("姓名${user.name}")

    //重复执行当前闭包
    repeat(5) {
        println(user.name)
        println(it)
    }

    //with比较特殊,
    //不是以扩展方法的形式存在的,而是一个顶级函数
    with(user) {
        this.name = "with"
    }
}

5. 中缀表达式,运算符重载

一个函数只有用于两个角色类似的对象时才将其声明为中缀函数推荐示例:and、 to、zip。反例: add。
如果一个方法会改动其接收者,那么不要声明为中缀形式。

6. DSL

七. Kotlin的扩展库

1. kotlinx.coroutines

协程是什么

通过提升 CPU 利用率,减少线程切换进而提升程序运行效率

可控制:协程能做到可被控制的发起子任务

轻量级:协程非常小,占用资源比线程还少

语法糖:使多任务或多线程切换不再使用回调语法

启动协程

runBlocking :T,//用于执行协程任务
通常只用于启动最外层协程

launch : Job,//用于执行协程任务

async/await : Deferred,//用于执行协程任务

package com.chk.myexpandlist

import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

fun main() {
    runBlocking<Unit> {
        val launch = launch {
            repeat(1000) { i ->
                println("挂起中 $i。。。")
                delay(500L)
            }
        }
        var job2 = async {
            delay(500L)
            return@async "hello"
        }
        println(job2.await())
        delay(1300L)
        println("main:主线程等待中")
        launch.cancel()
        launch.join()
        println("main:即将完成退出")
    }
}

三种启动方式launch的源码中共四个参数

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
)

context:当前程序上下文,协程与协程切换传递参数,CoroutineContext调度器,可放到某个线程中,

start:以哪种方式启动协程,

block:启动的协程,

demo

private val okHttpClient = OkHttpClient()
private val request = Request.Builder().url("https//baidu.com").get().build()
fun displayDashboard(textView: TextView) = runBlocking {
    withContext(Dispatchers.Main) {
        textView.text = withContext(Dispatchers.Default) {
            okHttpClient.newCall(request).execute().body()?.string()
        }
    }
}

java code

public class Dashboard {
    private final OkHttpClient okHttpClient = new OkHttpClient();
    private final Request request = new Request.Builder().url("https://baidu.com").get().build();
    private final Handler handler = new Handler(Looper.getMainLooper());

    public void display(final TextView textView) {
        okHttpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                final String string = response.body().string();
                handler.post(() -> textView.setText(string));
            }
        });
    }
}

test

class TestMainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test_main)
    }

    fun test2(view: View) {
        val tvContent = findViewById<TextView>(R.id.mContent)
        Dashboard().display(tvContent)
        displayDashboard(tvContent)
    }
    fun test1(view: View) {
        runBlocking {
            withContext(Dispatchers.Main) {
                println("在主线程中启动一个协程," + Thread.currentThread().name)
            }
        }

    }
}

xml

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:text="hello world"
            android:id="@+id/mContent"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <Button
            android:id="@+id/btnTestThread"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="test1"
            android:text="测试1" />

        <Button
            android:text="测试2"
            android:onClick="test2"
            android:id="@+id/btnTestCoroutine"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </LinearLayout>
</ScrollView>
object AndroidCommonPool : CoroutineDispatcher() {
    override fun dispatch(context: CoroutineContext, block: Runnable) {
        AsyncTask.THREAD_POOL_EXECUTOR.execute(block)
    }
}

suspend关键字:

/ / suspend,被suspend修饰的函数只能被有suspend 修饰的函数调用
//因为suspend修饰的函数(或lambda)被编译后会多一个参数类型叫Continuation, //协程的异步调用本质上就是一次回调

suspend fun getHtml(): String {
    return withContext(AndroidCommonPool) {
            URL("http://baidu.com").readText()
        }
}

在java中调用getHtml

public class JavaActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        LaunchCoroutineKt.getHtml(new Continuation<String>() {
            @NonNull
            @Override
            public CoroutineContext getContext() {
                return null;
            }

            @Override
            public void resumeWith(@NonNull Object o) {

            }
        });
    }
}

特殊的启动协程方式

buildSequence/yield: Sequence

buildlterator: lterator

produce : Channel

2. kotlinx-io


```kotlin
object AndroidCommonPool : CoroutineDispatcher() {
    override fun dispatch(context: CoroutineContext, block: Runnable) {
        AsyncTask.THREAD_POOL_EXECUTOR.execute(block)
    }
}

suspend关键字:

/ / suspend,被suspend修饰的函数只能被有suspend 修饰的函数调用
//因为suspend修饰的函数(或lambda)被编译后会多一个参数类型叫Continuation, //协程的异步调用本质上就是一次回调

suspend fun getHtml(): String {
    return withContext(AndroidCommonPool) {
            URL("http://baidu.com").readText()
        }
}

在java中调用getHtml

public class JavaActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        LaunchCoroutineKt.getHtml(new Continuation<String>() {
            @NonNull
            @Override
            public CoroutineContext getContext() {
                return null;
            }

            @Override
            public void resumeWith(@NonNull Object o) {

            }
        });
    }
}

特殊的启动协程方式

buildSequence/yield: Sequence

buildlterator: lterator

produce : Channel

2. kotlinx-io

3. Android KTX

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐