学习Kotlin,看这一篇就够了

作者: alexhilton | 来源:发表于2018-05-20 10:20 被阅读229次

    人生苦短,要用Kotlin

    这是一种对程序猿更为友好的语言,可以减少开发者的工作量,原本由开发者干的事情,其实很多都可以由编译器实现了,这是一种更为高级的语言。Java虽然严谨,但却过于繁琐,太啰嗦了,一个小事情却要写大量的代码,而且有些代码又是非常机械式的,在实际编码过程中都是用IDE来自动生成。Java,C,C++,Object C这些都是上世纪的编程语言。

    Kotlin img

    现在到了新时代了,编程也发展了很多,像lambda表达式,函数式编程,等等一些新的概念和范式在涌现。所以就有了新时代的编程语言,像水果的Swift,Groovy,Scala,以及Java阵营的Kotlin。Kotlin是新一代的编程语言,与Java完美融合,简洁,方便,可以大大提高程序可读性,特别是对于Android开发者来说。水果推出了Swift以解放水果平台的开发者,而Kotlin就是来解放Android开发者的。

    虽然说Kotlin可以用在任何可以用Java的地方,但目前主要就是两大领域服务端,以及Android应用开发,特别是有了Google官方的支持,所以Kotlin对于Android开发者的意义更为重大,身为一个Android猿,是一定要学习一下这门现代的编程语言的,因为当你学过了之后 ,你会发现,之前写的代码都是在浪费生命。

    Development environment setup

    有三种方式

    命令行

    其实,这是最好的方式,因为配置起来非常的方便。到官网去下载编译器,解压,然后把kotlinc/bin/放到PATH环境变量里面,就可以了。如果要配置Vim,还需要安装一下插件,大神们早就把插件准备好了,只需要下载,然后按照官方方法安装即可,其实就是把解压后的东西拷贝到相应的目录里面就好了。

    Idea IntellJ

    这个看官方文档就可以了,孤未亲测,如遇困难请自行Google。

    Android Studio

    因为Kotlin官已支持了Android Studio,而Google也支持了,总而言之就是在Android Studio中可以直接使用Kotlin。所以, Android Stuido 3.0以后的版本无需特殊配置,就可以用例Kotlin了。

    对于刚开始学习Kotlin而言呢,孤推荐使用命令行的方式,而不要使用Android Studio,特别是直接创建一个基于Kotlin的Android项目,因为此时对语言还不够熟悉,直接上项目,会迷失在项目配置,frameworks以及语言基础之中。刚学习一门语言的时候要先学习基本的语法以及语言本身的特性,这最好先绕开框架和项目,会更容易上手一些。

    Hello world

    这是所有编程语言的入门必学课程,目的是让学习者快速的体验一下一门语言,我们也不用多想,照着一个字母,一个字母的把示例敲进去就好了:

    1. 选择喜欢的文本编辑器,如Vim hello.kt,Kotlin的文件扩展名是*.kt,我们遵循就好。
    2. 一字不差的敲进去:
    package hello
    
    fun main(args: Array<String>) {
        println("Hello, world")
    }
    

    然后,保存文件

    1. 回到命令行,编译源码,如果一切顺利会得到一个叫hello.jar的文件,这就是kotlin的最终输出,也就是它的目标文件.
    kotlinc hello.kt -include-runtime -d hello.jar
    
    1. 运行,这里跟Kotlin其实已经没啥关系了,因为经过编译得到的是一个标准的Jar文件,像运行其他jar一样运行就好了:
    java -jar hello.jar
    

    就会得到输出Hello, world到此,第一个Kotlin程序已经完成,是不是很酷,已经迫不及待的想深入学习了!往下看吧。

    The basics

    语句结构

    一行一个语句(先不纠结语句与表达式的区别),不用加分号,不用打分号,光这个就可以节省多少时间呢?是不是感觉人生都浪费在了分号上面。如果想在一行写多个语句,前面的要加上分号。

    缩进规则与Java一致,用四个空格,也可以用tab,或者不加缩进,只要没人打你。

    语句块需要加上花括号{}。总之,语句结构与Java很类似。

    变量

    用var来声明变量,用val来声明常量,因为Kotlin是静态强类型语言(也就是说每个变量在编译的时候必须知道类型)声明时需要带上类型,方法是在变量名的后面加冒号,空格跟上类型名字,与Pascal差不多。如果声明时直接定义,则可以不用指定类型,编译器会根据定义表达式来推测它的类型。示例:

    var str: String
    val i: Int
    var str = "Hello, world"
    

    语句和表达式

    主要想说一下语句和表达式的区别,简单来说就是表达式是有值的,可以放在变量赋值的右边,而语句是没有值的,不能放在赋值的右边

    基本运算

    不多说了,跟Java一样

    注释

    这个跟Java也一样:
    // 单行注释
    /* / 多行注释
    /
    * */ documentation

    函数

    以fun关键字来定义一个函数格式为:fun 函数名(参数): 返回类型 {函数体},如:

    fun foo(name: String): Int {
       return name.length()
    }
    

    命名参数和默认值,调用函数时可以把参数的名字带上,以增加可读性。声明函数时可以用默认值 ,以更好的支持函数的重载。如:

    fun foo(name: String, number: Int = 42, toUpper: Boolean = false): String {}
    

    使用时,可以指定参数的名字:

    foo("a)
    foo("b", number = 1)
    foo("c", toUpper = true)
    foo(name = "d", number = 2, toUpper = false)
    

    表达式体如果一个函数体内只有一个表达式,且有返回值时,那么,可以直接把返回值放在函数 的后面,如:

    fun foo(name: String): String = name.toUpperCase()
    

    甚至还可以把返回类型的声明给省略掉,如:

    fun foo(name: String) = name.toUpperCase()
    

    跟Java不一样的是,Kotlin的函数可以声明为toplevel也就是跟class一个级别,也就是说不必非放在类里面,也就是说跟C和C++是类似的。此外,还可以函数赋值给一个变量,这个变量就像其他变量一样。

    类与对象

    类的声明与对象创建

    用class来声明一个类型,用:来继承父类或者实现接口,不需要使用new来创建对象:

    class Person {
       var name: String
       var age: Int
    }
    

    假如,一个类,是空的,没有内容,那么花括号{}是可以省略的:

    class Person
    

    创建对象:

    var someone = Person()
    

    Primary constructor

    构造方法,有所谓的primary constructor,可以直接写在类名的后面:

    class Person constructor(name: String)
    

    一般情况下,constructor 可以省略掉:

    class Person(name: String)
    

    初始化块因为primary constructor不能包含代码,所以,想要做些初始化工作就可以放在初始化块里面(initializer block),也可以在定义属性时直接使用:

    class Person(name: String) {
        var firstName: String = name
        init {
            println("First initializer block that prints ${name}")
        }
    }
    

    一般情况下,如果声明的属性变量在primary constructor中都有赋值(通过initializer block)的话,可以有更简洁的表达方式:

    class Person(var name: String, var age: Int)
    

    这相当于:

    class Person(theName: String, theAge: Int) {
       var name: String = theName   var age: Int = theAge
    }
    

    如果primary construct前面要声明属性,或者有annotation的话,关键字constructor不能省略:

    class Person public @Inect constructor(var name: String)
    

    Secondary constructor

    如果primary constructor不能满足需求怎么办呢?还可以声明其他constructor,所谓的secondary constructor:

    class Person {
       var name: String constructor(name: String){
           this.name = name
       }
    }
    

    是不是看起来舒服一些,因为跟Java一样了,可以把primary constfuctor和second constructor联合起来一起用:

    class Person(var name: String) {
        constructor(name: String, parrent: Person) : this(name) {
            parrent.addChild(this)
        }
    }
    

    这里要把secondary construct尽可能delegate到primary constructor,这里的delegate的意思就是primary constructor会在second constructor之前 执行,还有就是initiailzer block都是在primary construct中执行的,这就能保证initiliazer block在second constructor之前执行。即使没有显示的声明primary constructor,编译器还是会生成一个默认的primary constructor以及把secondary constructor默认的delegate到primary constrcutor上面。也就是说,会保证primary constructor以及initializer block执行在second constructor前面:

    class Constructors {
        init {
            println("Initializer block")
        }
    
        constructor(i: Int) {
            println("second constructor")
        }
    }
    
    fun main(args: Array<String>) {
        val c = Constructors(3)
    }
    

    输出:

    Initializer block
    second constructor
    

    属性和访问方法

    Kotlin会为声明的属性生成默认的setter和getter:

    class Person(var name: Strring, var age: Int)
    
    val p = Person("Kevin", 24)
    p.getName() // 返回"Kevin"
    p.setAge(32) // age变成了32
    

    如果想自定义setter和getter,也是可以的:

    class Person {
        var name: String
            set(n: String) {
                if (n == null || n == "") {
                    name = "Unkown"
                } else {
                    name = n
                }
            }
            get() {
                if (name == "Unkwon") {
                    return "Nobody"
                }
                return name
            }
    }
    

    定义类的方法

    跟声明普通函数一样,只不过是放在了类里面:

    class Person(val name: String, val age: Int) {
        fun report() = "My name is $name, and I'm $age"
    }
    

    如果,要覆写父类的方法,需要使用在方法声明时加上override关键字。

    class Doggy(val name: String) : Animal {
        override fun yell() = "Barking from $name"
    }
    

    访问权限

    访问权限也跟Java类似分为public,protected,private以及internal,前三个意义也都一样,只不过默认值不一样,在Java里,如果对成员没有指明,则是package scope,也就是同一个package可以访问,但是Kotlin默认是public的。

    internal是module内部可见,有点类似于Java中的package,但是module定义跟package不一样,module是一组编译在一起的Kotlin文件,它跟编译打包有关系,简单的理解它的范围要比package要大。

    还有就是类,默认是不可被继承的,相当于final class。如果想要允许继承就要在声明类的时候加上open。

    字串

    概念就不说了,大部分与Java一模一样的,像支持的方法等。唯一需要说的就是字串模板,就是说把其他类型转化为字串时,有较Java更为方便的方式:直接用$来把变量嵌入到字串之中,如:

    val msg = "Error 1"
    val count = 32
    print("We got message $msg") //等同于"We got message " + msg
    print("Total is $count") // Total is 32
    

    lambda表达式

    首先要介绍一个概念,高阶函数,其实就是把另外函数当作参数的函数,或者说产生一个函数,也即把函数作为返回值 的函数。前面说过,函数是一级对象,可以像常规变量一样来使用,所以,就能把函数作为参数或者返回值来使用高阶函数。lambda表达式就是为高阶函数更方便使用而生的。

    lambda 表达式

    作为新时代的编程语言,都会支持函数式编程,而lambda表达 式又是函数式编程里面必不可少的一份子。其实啥是lambda表达式呢?说的简单点就是没有名字的函数,非常简短的,通常都是一两句话的没有名字的函数。就是长这个样子{A, B -> C},这里面A,B是参数,C是表达式,如:

    val sum = { x: Int, y: Int -> x + y }
    

    其中,参数的类型是可以省略的,因为编译器能从上下文中推测出来:
    max(strings, { a, b -> a.length < b.length }
    表达式部分,可以不止一个,最后一个表达式作为返回值。

    当把一个lambda表达作为最后一参数,传给某个函数时,可以直接把lambda表达式写在参数的外面,比如:

    val product = items.fold(1) { acc, e -> acc * e }
    

    而当lambda是唯一的参数时,也可以把参数的括号省略掉:

    run { println("Hello, world") }
    

    还有就是,如果lambda表达中只有一个参数,那么参数也可以省略,直接写表达式:

    eval{ x * x }
    

    函数类型

    前面提到了函数是可以像普通变量一样使用的一级类,也就是说它是一个类型。它的具体形式是: (A, B)->C,其中括号内的是参数,C是返回类型,如:

    val sum: (Int, Int)->Int = { x, y -> x + y }
    val square: (Int)->Int = { x -> x * x }
    

    为啥要提一下函数类型呢,因为有时需要声明高阶函数:

    fun walk(f: (Int)->Int)
    fun run(f: ()->Unit)
    

    Unit是一个特殊的返回值,相当于void,意思就是此函数没有返回值。

    集合

    其实大部分跟Java是一样的。只不过有一些函数式的操作,要多注意使用,从而让代码更简洁,如:

    • 遍历
    • 过滤
    • 映射
    • 排序
    • 折叠
    • 分组
    • 归类

    这些操作,对于大家应该都不难理解,就不一一解释了,来断代码就知道了:

    fun collectionTests() {
        val list = listOf("Apple", "Google", "Microsoft", "Facebook", "Twitter", "Intel", "QualComm", "Tesla")
        // 遍历,以进行某种操作
        list.forEach{ println(it) }
        //按条件进行过滤,返回条件为true的
        val short = list.filter { it.length < 6 }
        println(short) // [Apple, Intel, Tesla]
        // 把列表元素映射成为另外一种元素
        val lenList = list.map{ it.length }
        println("Length of each item $lenList") //Length of each item [5, 6, 9, 8, 7, 5, 8, 5]
        // 按某种条件进行排序
        val ordered = list.sortedBy { it.length }
        println("Sorted by length $ordered") // Sorted by length [Apple, Intel, Tesla, Google, Twitter, Facebook, QualComm, Microsoft]
        // 折叠,用累积的结果继续遍历
        val joint = list.fold("", {partial, item -> if (partial != "")  "$partial, $item" else item })
        println("Joint list with comma $joint") // Joint list with comma Apple, Google, Microsoft, Facebook, Twitter, Intel, QualComm, Tesla
        //分组,用某种条件 把列表分成两组
        val (first, second) = list.partition { it.length < 6 }
        println("Length shorter than 6 $first") // Length shorter than 6 [Apple, Intel, Tesla]
        println("Longer than 6 $second") // Longer than 6 [Google, Microsoft, Facebook, Twitter, QualComm]
        // 归类,按某种方法把元素归类,之后变成了一个Map
        val bucket = list.groupBy { it.length }
        println("$bucket is a map now") //{5=[Apple, Intel, Tesla], 6=[Google], 9=[Microsoft], 8=[Facebook, QualComm], 7=[Twitter]} is a map now
    }
    

    null处理

    为了有效的减少空指针异常,Kotlin加入了Nullable类型,核心的原理是这样的:声明类型的时候要明确的告诉编译器,这个变量是否可能为null,如果可能为null,那么可以赋null给这个变量,并且在使用此变量时必须检查是否为null;假如这个变量不可能为null,那么是不可以赋null给此变量的。也就是说,编译器会帮忙做一些检查,以减少NullPointerException的发生。

    Nullable变量

    默认的变量声明都是不可为null的,如:

    var safe: String
    safe = null // 会有compile error
    

    要想允许变量为null,要在类型后面加一个问号,以告诉编译器这是一个nullable类型:

    var danger: String?
    danger = null // OKay
    

    使用时,nullable不能直接使用,必须检查是否为null:

    safe.length // okay
    danger.length // compile error, danger could be null
    

    检查Nullable的真伪

    可以用传统方式:

    val len = if (danger != null) danger.length else -1
    
    Safe call

    既然有Nullable类型,自然就有配套的方式来更方便的使用它:

    val len = danger?.length
    

    如果danger是null就返回null,否则返回长度,注意它的返回值是一个Int?(又是一个Nullable类型)。这个还能链起来:

    bob?.department?.head?.name
    

    如果任何一环为null,则直接返回null。是不是感觉省了好多if (a == null)判断。

    Elvis operator

    假如不能接受safe call返回的null,咋办呢?想提供默认值的呢?也有方式:

    val len = danger?.length
    println(len ?: -1)
    

    稍有点绕哈,首先,danger?.length返回一个Int?吧,那么?:的作用就是如果len是null,那么就返回-1,否则返回它的值。

    强制取值符!!

    它的作用是如果Nullable变量为null就抛出NullPointerException,如果正常的话就取其值,返回的类型是一个non-null类型:

    val len = danger!!.length // get length or NullPointerException
    

    尽管,编译器可以帮助我们做一些事情,但是现实的项目中的大量的NPE并不是直接来源于,可以方便追踪的赋值为null,而多是发生在多线程环境中,以及非常复杂的逻辑之中,编译器能否追踪到并警示,还有待考察。另外,就是虽有利器,但是要运用恰当,何时用允许null,何时不允许,还是要靠工程师的设计能力,比如尽可能返回空列表,空Map,或者空字串,而不是直接简单的返回null,这就能减少一定的NPE。

    Exercises

    光是看书或者看教程是比较乏味的,学习编程最重要的是要上手去练习,这样能加深印象,更好的理解书中或者教程中所讲的概念和知识点。官方也准备了一个非常好的练习项目叫Kotlin-koans,非常适配初学习者来练手。
    下面说一下如何使用这个练习项目:

    1. 官网去下载后,解压
    2. 用Android Studio打开此项目,一切提示都回答yes
    3. 要想运行测试前需要先编译一下项目,否则会提示找不到基础的测试类,找到Gradle窗口,一般在右侧,点开找到kotlin-koans->Tasks->build->build,运行它
    4. 现在就可以用先进的TDD方式来学习Kotlin了,在Project视图下面,可以看到kotlin-koans项目,里面有两个,一个是java,一个是tests,这两个目录里面的子目录都是一一对应的,先运行tests下面的,会失败,然后编辑java/下面的对应的代码,直到测试通过。

    Essence of Kotlin

    致此,我们可以看出Kotlin这门语言的设计的核心理念:简洁,这是Kotlin的核心理念,所以我们看到,一些机械的,重复的,可以从上下文中推测 出来的都 可以省略,以增加可读性。我们在使用Kotlin的时候要践行此理念,把语言的特性发挥到最大。
    当然,简洁,不是牺牲可读性的方式来缩短代码,而是要使用语言中的标准的简洁的表达方式,比如lambda表达式,省略参数等。

    要注意参考Kotlin conventions以及Android Kotlin conventions以写出更加简洁和容易理解的代码。

    Android dev setup

    我们来新建一个项目,用纯Kotlin实现一个Hello, world Android应用,来展示一下如何在Android中使用Kotlin:

    注意: 这里使用的是Android Studio 3.1.2版本,默认就支持Kotlin,如果使用小于3.0的版本需要安装Kotlin插件,可自行Google,孤还是建议先升级AS吧。

    1. 新建一个项目,其实流程跟新建一个普通Android Studio项目是一样一样的,从Android Studio3.0起,新建项目时就会有一个Checkbox,问你要不要添加Kotlin。这里把它选上。


      Step 1
    2. 就直接下一步就好


      Step 2
    3. Next,创建一个empty activity


      Step 3
    4. Finish

      Step 4
    5. 布局跟其他新建的Android项目无差别


      Step 5
    6. 代码已经是Kotlin的了


      Step 6
    7. 直接显示"Hello, world"略显无聊,所以加一下点击事件:
    class HelloActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_hello)
    
            val colorTable = listOf("#ff0000", "#00ff00", "#0000ff", "#ffff00", "#00ffff", "#ff00ff")
            val label = findViewById<TextView>(R.id.label)
            label.setOnClickListener { view ->
                val randomIndex = (Math.random() * colorTable.size).toInt()
                view.setBackgroundColor(Color.parseColor(colorTable[randomIndex]))
            }
        }
    }
    

    其实,整体来看,布局和项目的结构还是按照Android的方式来,唯一的不同是代码可以用Kotlin来写了。

    Good to go

    至此,Kotlin就算入门了,可以使用Kotlin来构建应用程序了,或者在你的项目中应用Kotlin了。

    参考资料和有用的资料分享

    原文链接:http://toughcoder.net/blog/2018/05/17/introduction-to-kotlin-programming-language/

    相关文章

      网友评论

      • Flipped199:感觉行尾没有分号看着好不舒服😂
      • luo2016:写的很好,看一遍还是有个大概的了解,:+1:

      本文标题:学习Kotlin,看这一篇就够了

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