Kotlin初体验

作者: mymdeep | 来源:发表于2017-06-03 14:51 被阅读517次

    最近似乎一直在忙,没有抽出时间来写点什么,我也不知道自己在忙什么,但是感觉时间过得好快。这次的文章主要是总结一下最近接触Kotlin的一些实践经历,没有什么太过于复杂深奥的东西,毕竟我也是第一次接触。想了解Kotlin的可以看一下,牛人可以绕行了。


    准备工作

    我当前使用的AS版本是2.3.x,需要安装Kotlin的相关插件:


    下载好插件之后重启AS。
    还是新建一个Android 工程,然后对于已经建立好的文件可以使用插件提供的自动转化功能:

    • 选中Java文件->Code->Convert Java File to KotlinFile
    • 转化工程:Tools->Kotlin->Configure Kotlin in Project
      转化完成后的工程样式如下:
      MainActivity
    class MainActivity : AppCompatActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
        }
     
    

    module下的build.gradle:

    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
            exclude group: 'com.android.support', module: 'support-annotations'
        })
        compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"//不做检测会出现问题,找不到库
        // sdk19, sdk21, sdk23 are also available
        // In case you need support-v4 bindings
        // For appcompat-v7 bindings
        compile 'com.android.support:appcompat-v7:25.3.1'
        compile 'com.android.support.constraint:constraint-layout:1.0.2'
        compile 'org.jetbrains.anko:anko-sdk15:0.8.3'
        compile 'org.jetbrains.anko:anko-support-v4:0.8.3'
        compile 'org.jetbrains.anko:anko-appcompat-v7:0.8.3'
        testCompile 'junit:junit:4.12'
    }
    

    project下的build.gradle:

    buildscript {
        ext.kotlin_version = '1.1.2-4'
        repositories {
            jcenter()
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:2.3.1'
            classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    
            // NOTE: Do not place your application dependencies here; they belong
            // in the individual module build.gradle files
        }
    }
    
    allprojects {
        repositories {
            jcenter()
        }
    }
    
    task clean(type: Delete) {
        delete rootProject.buildDir
    }
    

    我下了一个AS3.0版,是可以直接建立Kotlin工程的,2.3版本冒死不行。如果你是使用已有的工程直接转成Kotlin,我的意见是慎重,因为,如果工程过于庞大,很可能出现转化不规范,或者转化错误的地方,到时候很不好检查,可以部分文件部分文件的转化。同时,个人认为,通过java文件转kotlin对比学习,是一个学习Kotlin的好办法

    语法

    学习一门新的语言的时候,总是要先学习它的语法,对比它的语法与自己已会语言的语法有什么不同。

    变量声明

       //空安全变量
            var str: String = "hello"//非空类型
            var str1 = "word"
            //可为空字符串变量
            var str2: String? = null//可空类型 不允许我们直接使用一个可选型的变量去调用方法或者属性。
    

    对于非空类型,如果你将变量设置为空,就会提示错误。
    对于可空类型,你是不许调用的它的方法火属性,如val l = str2.length也会报错,但是提供了另外一种方式调用val l = str2?.length ?: -1或者你能确保他是非空的val l1 = str2!!.length两个叹号用来确认变量的非空。
    Kotlin的空安全设计对于声明可为空的参数,在使用时要进行空判断处理,有两种处理方式,字段后加!!像Java一样抛出空异常,另一种字段后加?可不做处理返回值为 null或配合?:做空判断处理
    还有一种方法更加巧妙:

    str2?.let { println("str2"+str2) }
    

    str2后面一定要加?标识可空,let的方法内的执行,只有在str2非空的时候才会去执行。
    还有一种写法:

    str2?:let {}
    

    把点换成冒号,标识当str2为空的时候才去执行。
    以上可以看出Kotlin的用心良苦,这些都完美避免了空指针问题。
    顺带说一句吧,拼接字符串跟以前一样,用个加号即可println(str+str1)

    switch

    when (x) {
        7 -> println("7")//当x为7的时候,输出7
        if (x > 0) 1 else -1 -> println("大于0并等于1,或小于0并等于-1")//如果x>0是1的时候或小于0是-1的时候
        in 1..10 -> println("范围匹配1-10")//是否在1-10之间
        is String -> println("String类型")//是String类型
    }
    

    ifelse还是可以正常使用的,所以这里不多介绍,主要介绍一下switch,这个跟以前不太一样了,改用when来代替。
    when比switch更加强大,when子式可以是常量、变量、返回数值的表达式、返回Boolean值的表达式,甚至ifelse

    ArrayList

    fun testArrayList(){
            val list:ArrayList<String> = ArrayList<String>()
            list.add("111")
            list.add("222")
            list.add("333")
            list.add("444")
            list.add("555")
            list.add("666")
            //indices相当于序号集合
            for (i in list.indices) {
                print(list[i]+"  ")
    
            }
            print("\n")
            for (i in 2..list.size-1) {
                print(list[i]+"  ")
    
            }
            print("\n")
            for (item in list) {
                print(item+"   ")
    
            }
            print("\n")
            //加强版
            for ((i,item) in list.withIndex()){
                print(list[i]+"   ")
                print(item+"  |")
    
            }
            print("\n")
            //变种版
            list.forEach {
                print(it)
    
            }
          print("\n")
        }
    

    打印效果如下:

    06-02 14:06:07.122 20087-20087/com.umeng.soexample I/System.out: 111  222  333  444  555  666  
    06-02 14:06:07.122 20087-20087/com.umeng.soexample I/System.out: 333  444  555  666  
    06-02 14:06:07.122 20087-20087/com.umeng.soexample I/System.out: 111   222   333   444   555   666   
    06-02 14:06:07.123 20087-20087/com.umeng.soexample I/System.out: 111   111  |222   222  |333   333  |444   444  |555   555  |666   666  |
    06-02 14:06:07.123 20087-20087/com.umeng.soexample I/System.out: 111222333444555666
    

    类的相关特性

    构造函数

    class Person (private val name: String,private val age: String) {
        fun sayHello() {
            println("$name is $age years old")
        }
    }
    

    Kotlin允许将构造参数直接跟在类名后面,这种写法声明的构造函数,我们称之为主构造函数。
    这时可能有人会有疑问,如果希望在构造函数中添加一些代码逻辑怎么实现?

    class Person (private val name: String,private val age: String) {
    
    
        init {
            println("$name init")
            println("$age init")
        }
        fun sayHello() {
            println("$name is $age years old")
        }
    }
    

    刚才提到了这是主构造函数,有主就有次,那么一定有次级构造函数。次级构造函数就需要写在类中了:

    class Person (private val name: String,private val age: String) {
    
        private var sex: String? = null
        init {
            println("$name init")
            println("$age init")
        }
        constructor(name: String, age: String,sex: String) : this(name,age) {
                this.sex = sex
        }
        internal fun sayHello() {
            println("$name is $age years old")
        }
    }
    

    我们让次级构造函数调用了主构造函数。由于次级构造函数不能直接将参数转换为字段,所以需要手动声明一个 sex 字段,并为 sex 字段赋值。
    在字符串内部调用$符号,标识后面的是变量,将会把变量的内容替换到该位置。

    这里要说一下internal是含义,在同一个module中,它相当于public,在module之外,相对于private

    类的扩展

    Kotlin可以在已有类中添加新的方法, 比继承更加简洁和优雅。
    我们可以在Activity中写一个这样的方法:

       fun Person.toast() {
    
            var userInfo = "name:$name,  age:$age"
    
            println(userInfo)
        }
    

    为什么这么写呢,这个方法不是Activity类内的方法,实际它是刚才Person类的方法,我们调用一下:

      val person = Person("deep","30","man")
       person.toast();
    

    结果如下:

    06-02 17:25:54.095 3989-3989/com.umeng.soexample I/System.out: deep init
    06-02 17:25:54.095 3989-3989/com.umeng.soexample I/System.out: 30 init
    06-02 17:25:54.095 3989-3989/com.umeng.soexample I/System.out: name:deep,  age:30
    

    转换

    之前也提到过,可以用 is 来判断一个对象是否是某个类的实例,但是如果需要强制转化呢,我们可以使用as

      (person as Person).sayHello()
    

    伴生对象

    Kotlin之所以这么做,是由于Kotlin 没有静态方法。在大多数情况下,官方建议是简单地使用伴生对象。

    public final class StringUtils public constructor() {
        public companion object {
            public final fun getLength(str: kotlin.String): Int {
               return str.length;
            }
        }
    }
    

    我们可以这样调用:

     StringUtils.getLength("1111111");
    

    我们可以看出这种用法相当于静态方法。
    所以,我们也可以用这种方式来处理单例模式的类:

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

    然后用SingleObject.get();进行调用
    要注意private constructor()这种写法,把构造函数私有化。

    布局

    如果以之前的思路使用xml,布局文件,那么将Activity中的代码,转成Kotlin,应该是如下的样子

    class LayoutActivity : Activity() {
        internal var btn1: Button? = null;
        internal var btn2:  Button? = null;
        internal var btn3:  Button? = null;
        internal var btn4:  Button? = null;
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            setContentView(R.layout.activity_layout)
            btn1 = findViewById(R.id.btn1) as Button
            btn2 = findViewById(R.id.btn2) as Button
            btn3 = findViewById(R.id.btn3) as Button
            btn4 = findViewById(R.id.btn4) as Button
            btn1!!.setOnClickListener { Toast.makeText(this@LayoutActivity, "1111", Toast.LENGTH_LONG).show() }
            btn3!!.setOnClickListener { Toast.makeText(this@LayoutActivity, "333333", Toast.LENGTH_LONG).show() }
            btn2!!.setOnClickListener { Toast.makeText(this@LayoutActivity, "222222", Toast.LENGTH_LONG).show() }
            btn4!!.setOnClickListener { Toast.makeText(this@LayoutActivity, "444444", Toast.LENGTH_LONG).show() }
    
        }
    }
    
    

    在这里推荐另外一种方式布局,就是不使用布局文件。
    这种方式需要额外依赖一些库:

        compile 'org.jetbrains.anko:anko-sdk15:0.8.3'
        compile 'org.jetbrains.anko:anko-support-v4:0.8.3'
        compile 'org.jetbrains.anko:anko-appcompat-v7:0.8.3'
    

    我们来看一下写法:

    class AnkoActivity : AppCompatActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            relativeLayout {
                val tv = textView("aaaaa") {
                    textSize = 25f
                }.lparams {
                    width = matchParent
                    alignParentTop()
                }
    
                button("Sample button").lparams {
                    width = matchParent
                    alignParentBottom()
                }
                textView("bbbbb").lparams {
                    below(tv)
                    alignParentRight()
                    topMargin = 400
                }
            }
        }
    }
    

    这种设置方式与布局文件类似,所有能在布局文件中设置的,在这里也能设置,也是使用的嵌套的方式。
    这种方式不做过多解释,觉得喜欢的朋友可以查看Anko的相关用法。

    总结

    Kotlin的学习成本并不高,而且可以通过这个AS的插件,将现有java文件逐个转化进行学习。如果能够熟练掌握Kotlin,确实从代码量上能节省很多。
    有疑问的朋友欢迎给我留言指正,或者关注我的公众号留言:


    相关文章

      网友评论

        本文标题:Kotlin初体验

        本文链接:https://www.haomeiwen.com/subject/nanyfxtx.html