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

Posted 战国剑

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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用法,需要在上面的基础上,自身去拓展了。

以上是关于Kotlin语言------一文了解的主要内容,如果未能解决你的问题,请参考以下文章

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

不知道该学那一个语言?一文带你了解三门语言

一文了解预训练语言模型!

赠书 | 一文了解预训练语言模型

了解Kotlin,看这个就够了

想要了解Kotlin,看这个就够了