Kotlin语言------一文了解

Kotlin语言作为一种完全兼容Java的语言,在2017年,被Google指定为官方开发语言,并宣布后续将以Kotlin作为Android的第一开发语言。相对于Java,Kotlin解决了许多的痛点,使用会更简洁。

Kotlin目前应用最为广泛的,也是在Android上。Google官方在后续官网的Android更新都有提供Java和Kotlin双版本,并且目前许多的开源库都以Kotlin写就。因此了解Kotlin还是很有必要的。

本文会将Kotlin中基础以及与Java的不同部分做分析,主要按以下的三个类别做整理。

基础部分:常量、变量、类、方法、循环控制、接口、继承、集合;

语言特色部分:内联函数、高阶函数、lambda、泛型实化、泛型协变、泛型逆变、单例、扩展方法、标准库方法等;

新概念部分:协程。

一、基础介绍

1、常量与变量

Kotlin中常量的定义方式是val,变量的定义方式是var,不可变量定义方式为 const val。

以下是定义的例子:

//常量
val valMain = "val in Main"
//变量
var int:Int = 1
//const 定义的常量(const 只能定义在 val前)
const val constVal:String = "CONST_VAL"

无论是常量还是变量,定义的基础格式都是:var/val  名称  类型 = 具体赋值。

我们可以很语义化的读出这个变量/常量的意思,比如 var int:Int = 1,就是:变量int是Int类型,值等于1。常量也是类似的。这种声明与Java是不一致的,Java是把类型声明放在首位的。

另外,为什么会有两个常量的定义:val、const val?

通过反编译可以看到:

const val 可见性为public final static,可以被其他类和文件直接访问。
val 可见性为private final static,并且val 会生成方法getNormalObject(),通过方法调用访问。

二者可见性不同,使用范围不同。如果是通用的常量定义,建议使用const val,定义在统一外层文件,便于使用与查找。

Kotlin中数据类型的定义与Java中也是有较大的改变。Kotlin中,在数据类型的定义上,取消了基础数据类型的定义,全部都是以类的形式对外展示的。

    var int:Int = 1
    var double:Double = 1.00
    var float:Float = 1.0F
    var string:String = "str"
    var shot:Short = 127
    var bool:Boolean = true
    var char:Char = 'a'

Java中的基础类型int,在Kotlin中用Int表示、float用Float表示。这样在编码上,对开发人员统一。

Kotlin的编译器,会在编译的时候,自动将基础类型的格式做转换,比如把Int转为int,Float转为float。

2、循环与判断、控制语句

2-1、循环:其中、while的用法与Java一致。for语句有特殊的写法。        

1、正序遍历
   for (i in 0..5){
        println(i) //print 0 1 2 3 4 5
    }

2、逆序遍历
   for(i in 5 downTo 0){
        println(i) //print 5 4 3 2 1 0
    }
    
3、遍历带上步长
    for (i in 0..5 step 2){
        println(i) //print 0 2 4
    }
4、遍历不包括最后一个元素
    for (i in 0 until 5){
        println(i) //print 0 1 2 3 4
    }

2-2、判断、控制语句:

ifelse与Java是一样的用法。

这里介绍一个范围表达式:range表达式,格式是 in a..b ,用来判断某个数是否在这个范围内。这个在前面的循环中已经有使用到。

另一个常用的表达是是:when表达式(有if/switch语句的地方,如果有需要,都可以用when取代)

这里做一个if语句和when语句的比对

val testIf: Int =1
1、if语句

    if(testIf==1){
        println("is one")
    }else{
        println("is other")
    }

2、when语句
    
    when(testIf){
        1-> println("is one")
        else -> println("is other")
    }

when表达式的入参,可以是任何数。when语句还有其他写法,比如when{},具体可以手动实验。

2-3、Null判断

Kotlin中,鼎鼎大名的还有一个特色,就是它的安全机制,也就是null安全。

Kotlin中,为了避免NPE问题,除非有特殊规定,否则变量都不可为null。

但是Kotlin中,肯定还是会存在null,因为我们也会用到。

Kotlin中区分了可空类型和非可空类型。

可空类型:var a:String?

非可空类型:var b:String

如果你要使用可空类型,做更多操作,那么你要加上语句:?.   这个就是Kotlin的安全调用符。

a?.count() ,这样的调用,在a为null是,是不会执行后续的count()函数的。只有当a不为null,才会执行,这就保证了程序中不容易出现NPE。

但有些情况下,你知道该变量不可能为null。那么你可以这么写:a!!.count() ,这里的 !!. 叫做非空断言运算符,这样就必定会执行后续的count()函数,无论前面的a是否为null。

看个例子:

 val test:String ?= null
    val s = test?.count()
    println(s) // print null



test!!.count() // npe exception

3、方法(函数)

Kotlin中,方法的定义方式如下:

private fun toastInfo(name:String,age:Int):String{
    return "my name is $name, age is $age"
}

修饰符+fun关键字(表示函数/方法)+方法名称+(入参和入参类型)+冒号+返回类型。

上述方法的意思就是:私有函数,名称为toastInfo,入参是name(String类型)和age(Int类型),返回值是String类型。

这是Kotlin中函数最基本的格式,当然Kotlin中是可以用高阶函数的,这会在后面介绍。

4、类

Kotlin中的类与Java中的类,还是有较大差别的。

还是以代码方式来说明差异,在代码上标明了5点的差异:

//1、open 可继承的类
//2、主构造函数为空的时候,可以省略类名后的()
open class Person {
    var name = "bill"
    //3、方法要被继承,需要open
    open fun display() {
       println("person's name is $name")
    }
}
//4、继承、接口实现的标志都是:,与java的 extends 有差别
class Student(var age: Int) : Person() {

    override fun display() {
        println("The student's info --> name:$name,age:$age")
    }
}

fun main() {
    //5、实例化,不需要new
    val person = Person()
    println(person.name)

    val student = Student(18)
    student.display()
}

1、Kotlin的类,默认是不可继承的。相当于Java中的 final class,以保证类的数据安全性。

2、类的实例化,去除了Java中的new关键字。

3、继承与实现接口的标志,统一都是冒号(:)。

4、类中的方法要被继承,也需要加上open标志。

5、构造方法的写法发生了改变。

关于构造方法的写法,上面代码中,只是展示了一个如果主构造函数的参数为空的时候,可以省略类名后面的括号。Kotlin中,还有次级构造方法的概念,详细介绍如下:

5-1、主构造函数的写法

以下的差别主要在于:如果在主构造函数的入参中,有var或者val标志,该参数就是类的属性,否则就只是一个临时的值参传入。

//空主构造函数
open class InitTest {
    var field1 = 0
    val field2 = ""
}
//主构造函数存在,传入两个临时变量
class InitTest1(_name: String, _age: Int) {
    var name = _name
    var age = _age
}
//函数属性 直接定义 用var 或者 val,空函数体
class InitTest2(var name: String, var age: Int)

//函数属性 直接定义 用var 或者 val,有函数体
class InitTest3(var name: String, var age: Int) {
    fun initTest3() {
        println("name is $name,age is $age")
    }
}
//参数有默认赋值
class InitTest4(var name: String, var sex: String = "男", var age: Int) {
    fun initTest4() {
        println("name is $name,age is $age,sex is $sex")
    }
}

5-2、次级构造函数

次级构造函数,最后总要调用到主构造函数,下方代码中的:

    //次级构造函数1
    constructor(_age1: Int):this("mock",_age1)
    //次级构造函数2
    constructor(name1: String):this(name1,0)

这两个次级构造函数中的this(xx,xx)表示调用了主构造函数。

open class Init(var name:String,_age:Int){
    var age = _age
    var sex:String
    init {
        sex = ""
    }
    //次级构造函数1
    constructor(_age1: Int):this("mock",_age1)
    //次级构造函数2
    constructor(name1: String):this(name1,0)
}

以上是Kotlin类与Java类的主要差异点。Kotlin类,既然可以用open修饰,其实还有更多其他的修饰符,如data、object等,后续介绍。

5、接口与抽象类

这里主要介绍接口。Kotlin的抽象类写法与Java是一致的,但是接口也实现了部分抽象类的功能,接口可以有默认实现了。

下方接口DoSome中的drink方法有默认实现了!这对于代码的简化是显而易见的。日常我们的代码为了实现公共的接口,经常会有很多的空实现,在Kotlin中就不会有这个问题。

//1、接口 interface
interface DoSome{
    fun eat()
    //该接口方法有默认实现了
    fun drink(){
        println("drink water")
    }
}

class Do1 :DoSome{
    override fun eat() {
        TODO("Not yet implemented")
    }

    override fun drink() {
        super.drink()
    }
}

class Do2 :DoSome{
    override fun eat() {
        TODO("Not yet implemented")
    }
}


//2、抽象类 abstract
abstract class DoAny{
    fun eat(){

    }

    abstract fun drink()
}

class DoAny1:DoAny(){

    override fun drink() {
        TODO("Not yet implemented")
    }
}

6、集合

Kotlin很大程度上,简化了集合的写法,提供了多种的集合数据提供方式。

常用的集合的结构有List、Map、Set、Array等,对这几种集合收集数据的方式,做了简化处理:

//list
val list:List<String> = listOf("1","2")

//set
val setList:Set<String> = setOf("5","6")

//array
val intArray:IntArray = intArrayOf(1,2)

//map
val map:Map<Int,String> = mapOf(1 to "one",2 to "two")

集合也默认是不可变集合,可以通过toMutable式的函数,做可变转化。相近集合类型之间,也可以做到转化。

//可变集合的转化:从不可变 专为 可变
val setList:Set<String> = setOf("5","6")
val toMutableSet = setList.toMutableSet()


//List->Set的转化
val toMutableList1 = toMutableSet.toMutableList()
toMutableList1.add("7")
val message = toMutableList1.toMutableSet()

集合还提供了许多的语法糖方法,比如过滤、最大最小值、排序等,操作十分方便。

二、语言特色

Kotlin相比Java,在很多地方做了优化与改进,这些特性都归为语言特色来介绍。

1、高阶函数与lambda

高阶函数是Kotlin的一大特色。Java在低版本上,是不支持这种特性的(Java7中仍不支持)。

高阶函数就是指:方法的入参或者返回值,可以是函数。

1-1:函数作为入参

这里用一个数字运算的例子来说明:

operation这个函数中,第三个入参是一个函数:这个入参函数的参数名是func,类型是:(Int,Int)->Int,也就是两个整型入参,返回一个整型(这里比较绕,多看几遍就明白了)。

函数test1、test2、test3都是两个整型入参,返回一个整型的形式,只是内部运算方式不同。

//高阶函数:函数作为入参

//func:(Int,Int)->Int 一个函数作为入参
//数据运算,运算的方法主体实现,等待外部传入
fun operation(a: Int, b: Int, func: (Int, Int) -> Int): Int {
    return func(a, b)
}

fun test1(a: Int, b: Int): Int {
    return a + b
}

fun test2(a: Int, b: Int): Int {
    return a - b
}

fun test3(a: Int, b: Int): Int {
    return a * b
}

如何使用?调用方式就是这样的:operation(3,4,::test1),其中test1前面的::表示函数引用。

fun main() {
    //带函数名称的入参。函数引用方式传入 ::
    println("operation test1 = ${operation(3, 4, ::test1)}")
    println("operation test2 = ${operation(3, 4, ::test2)}")
    println("operation test3 = ${operation(3, 4, ::test3)}")
}

Kotlin中有很多的高阶函数作为入参使用的例子,很多扩展函数的入参都是一个lambda,也就是一个匿名函数。我们手动写一个also的例子(关于扩展类,后续有再介绍):

//1、T 泛型类  T. 泛型类的扩展方法。T可以是任意类
//2、block:(T) -> Unit,表示入参是block,类型是一个函数,该函数的入参是T的实例,返回值是空
//3、myalso函数,最后的返回值是T,也就是实例本身
//4、函数体中的block(this),表示调用函数,入参this表示T的实例
fun <T> T.myalso(block:(T) -> Unit):T{
    block(this)
    return this
}


//测试类
class TestInfo{
    var info:String = ""
}


fun main() {
    
    //lambda入参形式的also 怎么写
    val testInfo = TestInfo()
    testInfo.info = "my info"

    val result = testInfo.myalso {
        it.info = "your info"
    }
    println("operation lambda myalso = ${result.info}")
}

这里主要是模仿系统的also函数,看我们高阶函数的作用。这个例子可以后续回头再看。

1-2:函数作为返回值

函数作为返回值,我们可以将它赋值给一个变量或者常量,这个变量或者常量,就具有了函数的特征,仔细看下方的函数调用方式。

//高阶函数:函数返回值

//该函数返回一个:入参为Int,出参为空的函数(Int)->Unit
//该函数返回的写法,一个lambda = { num:Int -> println(num) }
fun getFun():(Int)->Unit{
    return { num:Int -> println(num) }
}

//funReturn是一个函数变量
val funReturn = getFun()

fun main() {
    //调用方式1
    getFun()(2)
    //调用方式2
    funReturn(3)
}

以上是高阶函数的例子,高阶函数可以让代码更灵活(如果使用不当,也容易造成代码混乱...)

2、内联函数

这是一个Java中没有的新概念。

引用一段来自https://www.jianshu.com/p/ab877fe72b40的说明:

当我们使用lambda表达式时,它会被正常地编译成匿名类。这表示每调用一次lambda表达式,一个额外的类就会被创建,并且如果lambda捕捉了某个变量,那么每次调用的时候都会创建一个新的对象,这会带来运行时的额外开销,导致使用lambda比使用一个直接执行相同代码的函数效率更低。

如果使用inline修饰符标记一个函数,在函数被调用的时候编译器并不会生成函数调用的代码,而是 使用函数实现的真实代码替换每一次的函数调用

它的主要作用是实现代码的替换,避免资源的浪费。另外、内联函数不要递归方法上声明。

它的另一个作用就是后续要介绍的泛型实化上。

看一个例子,你就明白内联函数的作用:

我们定义了两个方法:testInlineFun,这是一个内联方法。testCommonFun是一个普通方法。

我们对这个文件做反编译后,可以看到下方的注释代码。在main中,声明了内联的函数,直接被拷贝进了main方法中,普通的方法则是正常调用。

package com.wudaokou.kotlinuse

inline fun testInlineFun(){
    println("testInlineFun print")
}

fun testCommonFun(){
    println("testCommonFun print")
}

fun main() {
    println("main start")
    testInlineFun()
    testCommonFun()
}

/* show kotlin bytecode
public final class Kotlin11_inlineKt {
    public static final void testInlineFun() {
        int $i$f$testInlineFun = 0;
        String var1 = "testInlineFun print";
        boolean var2 = false;
        System.out.println(var1);
    }

    public static final void testCommonFun() {
        String var0 = "testCommonFun print";
        boolean var1 = false;
        System.out.println(var0);
    }

    public static final void main() {
        String var0 = "main start";
        //将testInlineFun方法的内容拷贝到main方法里了
        boolean var1 = false;
        System.out.println(var0);
        int $i$f$testInlineFun = false;
        String var4 = "testInlineFun print";
        boolean var2 = false;
        System.out.println(var4);
        //此处调用方法testCommonFun
        testCommonFun();
    }

    // $FF: synthetic method
    public static void main(String[] var0) {
        main();
    }
}*/

3、泛型实化

这也是Kotlin中的一个新概念。 在Java中,Java 泛型的类型参数之实际类型在编译时会被消除,所以无法在运行时得知其类型参数的类型。所以Java中,T.class , T  instanceOf xx 等是无法使用。

在Kotlin中,对这种情况,有了改进。如果这个泛型所在的方法是一个内联方法,并且泛型用reified修饰,那么就可以实现T.class、T is xx等取值和判断。原因在于:在编译时就会将内联函数的代码替换到实际调用的地方,所以对于内联函数/方法来说是不存在泛型的擦除的。

如下方,getType函数中,我们直接返回了T的类型。

//泛型实化 :inline 内联 reified 修饰关键字(具体化)
inline fun <reified T> getType() = T::class.java


fun main() {
    val type = getType<String>()
    val type1 = getType<Int>()

    println("type --> $type")
    println("type1 --> $type1")
    //result:
    // type --> class java.lang.String
    //type1 --> class java.lang.Integer
}

4、泛型协变与逆变

这个可以直接看这篇解析,赞:https://www.jianshu.com/p/0c2948f7e656

文章对Kotlin和Java做了详细对比,也很清晰的看到了Kotlin中的改进在哪里。

对此也写了一个例子,供参考:

fun main() {

    //1、MutableList 中用out修饰后,作为生产者,无法add操作。add是一个消费者方法。
    val ls:MutableList<out Fruit> = listOf(Orange(1), Orange(2)).toMutableList()
    println(ls[0].name)
    //  ls.add(Orange(3)) //Out-projected type 'MutableList<out Fruit>' prohibits the use of 'public abstract fun add(element: E)
    //  ls.add(Fruit("")) //Out-projected type 'MutableList<out Fruit>' prohibits the use of 'public abstract fun add(element: E)

    //2、MutableList 中用in修饰后,作为消费者,可以add操作。add是一个消费者方法。但取值被限制了,无法取得有效的类。
    val ls1:MutableList<in Fruit> = listOf(Orange(1),Orange(2)).toMutableList()
    //此时拿到的是Any?类型,并无实际意义
    val get = ls1.get(0)
    println(get)
    //add 操作
    ls1.add(Orange(4))

}

5、单例

Kotlin中,对单例类的写法做了很大的改进。直接将类的关键字class改为object就可以了,这样就表示是一个单例类。

除此之外,object关键字还有其他三种用法:普通类中嵌套单例类、伴生类、object关键字的表达式。

//普通类
open class MyClass{
   open fun testClass(){
        println("testClass")
    }
}

//普通类中含有object
class MyObjectInClass{
    fun testClass(){
        println("testClass")
    }

   companion object ObjectInClass{
       fun testObjectInClass(){
           println("testObjectInClass")
       }
   }
}


//单例类
object MyObjectClass{
    fun testObject(){
        println("testObject")
    }
}


//普通类中嵌套单例类
class MyClassWithObject{

    object ClassWithObject{
        fun testClassWithObject(){
            println("testClassWithObject")
        }
    }

    fun testObject(){
        println("testObject")
    }
}

上面是object的几种用法,注意:伴生类一个类中只能有一个。

这里看下如何使用它们?这里定义了多个接口,用于object对象表达式的演示。在代码里可以看到,object对象表达示例和Java的匿名类还是有挺大的区别的。object对象表达式,可以同时继承类、实现多个接口。

interface DoSome{
    fun getSome():String
    fun doSome()
}

interface DoSome1{
    fun getSome1():String
    fun doSome1()
}

fun doSomeThing(doSome:DoSome){
    doSome.getSome()
    doSome.doSome()
}

fun doSomeThing1(doSome1:DoSome1){
    doSome1.getSome1()
    doSome1.doSome1()
}

fun doSomeThing2(myClass: MyClass){
    myClass.testClass()
}

fun main() {
    //单例类的使用
    MyObjectClass.testObject()
    //普通类的使用
    val myClass = MyClass()
    myClass.testClass()
    //伴生类的使用 - 一个类中,只能有一个伴生类
    val myObjectInClass = MyObjectInClass()
    myObjectInClass.testClass()
    MyObjectInClass.testObjectInClass()

    //普通类中嵌套单例类
    MyClassWithObject.ClassWithObject.testClassWithObject()

    //对象表达式 - setOnClick 等,可继承一个类以及同时实现多个接口
    val objectTest = object : MyClass(),DoSome, DoSome1 {

        override fun testClass() {
            super.testClass()
        }

        override fun getSome(): String {
            println("getSome()")
            return "getSome()"
        }

        override fun doSome() {
            println("doSome()")
        }

        override fun getSome1(): String {
            println("getSome1()")
            return "getSome1()"
        }

        override fun doSome1() {
            println("doSome1()")
        }

    }
    println("test do some")
    doSomeThing(objectTest)

    println("test do some1")
    doSomeThing1(objectTest)

    println("test super class")
    doSomeThing2(objectTest)

    

}

6、扩展

这又是一个很重要的特性,前面文章中的myalso,就是扩展方法的一种写法。

Kotlin 可以对一个类的属性和方法进行扩展,且不需要继承或使用装饰者模式。

扩展是一种静态行为,对被扩展的类代码本身不会造成任何影响。

假如,Kotlin中一个类是普通类,它不允许继承。那么如果想往这个类中新增方法有什么办法呢?这就可以用扩展方法:

下面这个例子可以看到如果要定义一个扩展函数,真的很简单:类名.方法名 即可。

一般情况下,定义的类的扩展函数,可以放到外层或者顶层,便于查看和使用。

class ExtClass{
    fun test(){
        println("test")
    }
}

//这个就是类ExtClass的扩展函数
fun ExtClass.myExtFun(){
    println("my Ext fun")
}

fun main() {
    //本文件中定义 ext
    val extClass = ExtClass()
    extClass.test()
    extClass.myExtFun()

    //外部定义ext 导入
    extClass.test2()
    val str = "my str"
    str.myFun()
}

8、标准库方法

Kotlin的标准库中,提供了很多便捷的方法。这里以一些常用方法来举例。标准库的定义方式,很多也是扩展方法。

这里主要是关于 run \ with \ T.run \ T.let \ T.also \ T.apply 范围函数的示例。可参考注释来看各种标准库方法的作用。

class Info {
    var name: String? = null
    var age: Int = 0

    override fun toString(): String {
        return "$name --- $age"
    }
}
//关于 run \ with \ T.run \ T.let \ T.also \ T.apply 范围函数
fun main() {
    val info = Info()

    //run 返回最后一行作为返回值
    //run0 返回空
    val run = run{
        println("only print line")
    }
    println(run::class.java)
    //run1 返回整型
    val run1 = run{
        0
    }
    println(run1::class.java)

    //with 无返回值
    val with =  with(info){
        name = "bob"
        age = 18
    }
    println(info.toString())
    println(with::class.java)

    //T.run 扩展函数 无返回值
    val TRun = info.run {
        name = "cell"
        age = 19
    }
    println(info.toString())
    println(TRun::class.java)

    //T.let 扩展函数 无返回值
    val let = info.let {
        it.name = "allen"
        it.age = 20
    }
    println(info.toString())
    println(let::class.java)

    //T.also 扩展函数 返回自身
    val also = info.also {
        it.name = "jack"
        it.age = 21
    }
    println(info.toString())
    println(also::class.java)

    //T.apply 扩展函数 返回自身
    val apply = info.apply {
        name = "pony"
        age = 22
    }
    println(info.toString())
    println(apply::class.java)

}

三、协程

协程建议看这篇解析:https://www.cnblogs.com/mengdd/p/kotlin-coroutines-basics.html

很清晰的描述了它的原理和使用方式。

概括下协程容易弄混的地方:

0、挂起函数是协程的特色函数,它的挂起和重入,类比线程的阻塞和可运行。

1、一个协程中的函数,是顺序执行的。

2、协程阻塞只阻塞本协程,不会对其他协程和线程产生影响。

3、协程可以有多个子协程并行执行。

4、协程可以提高线程的利用率。线程中可以开多个协程,比如10万个。

5、协程在android中,可以很方便的把异步方式改为同步方式调用。

6、协程可以配合android的许多特性,增加生命周期等。

7、协程可以和网络库等配合使用(retrofit新版,已有支持协程)。

协程主要用法有三种:

GlobalScope、runBlocking、CoroutineScope。CoroutineScope是实际应用中最常用的。其他两种多用于测试。

协程的简单例子:

package com.wudaokou.kotlinuse

import kotlinx.coroutines.*

fun testGlobalScopeDelayLong(){
    //1、delay 较长时间
    println("start  ")
    GlobalScope.launch {
        println("start GlobalScope ,thread = ${Thread.currentThread().name}")
        delay(5000)
        println("end GlobalScope ,thread = ${Thread.currentThread().name}")
    }
    println("main end ,thread = ${Thread.currentThread().name}")
    Thread.sleep(6000)
}

fun testGlobalScopeDelayShort(){
    //2、delay 较短时间
    println("start  ")
    GlobalScope.launch {
        println("start GlobalScope ,thread = ${Thread.currentThread().name}")
        delay(1000)
        println("end GlobalScope ,thread = ${Thread.currentThread().name}")
    }
    println("main end ,thread = ${Thread.currentThread().name}")
    Thread.sleep(1500)
}

fun  testRunBlocking(){
    //3、runBlocking
    println("start  ")
    runBlocking {
        println("start GlobalScope ,thread = ${Thread.currentThread().name}")
        delay(1000)
        println("end GlobalScope ,thread = ${Thread.currentThread().name}")
    }
    println("main end ,thread = ${Thread.currentThread().name}")
}

fun testCoroutineScope(){
    //4、CoroutineScope
    println("start  ")
    CoroutineScope(Dispatchers.Default).launch {
        println("CoroutineScope start ,thread = ${Thread.currentThread().name}")
        delay(3000)
        println("CoroutineScope end ,thread = ${Thread.currentThread().name}")
    }
    println("main end ,thread = ${Thread.currentThread().name}")
    Thread.sleep(4000)
}

fun main() {
    testCoroutineScope()
}

可以运行上面的实例,看具体效果。

以上是Kotlin语言的一些基础以及有别于Java的特性。更多的Kotlin用法,需要在上面的基础上,自身去拓展了。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值