闪现入门Kotlin

闪现入门Kotlin

前言

这篇文章作为我读《第一行代码第三版》第二章的笔记,同时算一篇(混乱的)极简教程,献给和我一样从C++/Java/C#进入安卓开发的新手。

基本语法


源文件与入口

Kotlin的文件后缀为 .kt ,就是Kotlin的简写
在Kotlin中,创建一个main函数(就是入口啦),可以和C一样单独声明一个main函数:

fun main()
{
    //Coding here...
}

也可以如OOP形式,在类中声明一个静态main方法,在这种情况下,main函数的参数不可少

class MyMain{
 companion object {
  @JvmStatic
  fun main(args: Array<String>) {
   //Coding here
  }
 }
}

在IDEA中,main函数的边上会出现一个绿色的三角,点击即可编译运行。
两种方法都可,但作为学习,第一种更加简洁
暂且先用着,后面刨根问底。

变量

在Kotlin中,用val声明常量(value),用var声明变量(variable),kotlin有着优秀的类型推导,所以在声明变量时可以不用显示指明类型 例:

val a = 1
a =a * 4//报错! Val cannot be reassigned
var b = “Hello”
b = "a"//Success!

当类型推导不能正常工作或者延迟赋值的时候,可以手动声明变量的类型

val a : Int = 3
var b : String = "Hello"
var c : Long
c= 100000

但注意,不论是手动声明类型还是自动推导,一旦变量初始化了,就不能再更改类型。
Kotlin的内置类型与Java对照如下表
| 类型 | Kotlin | Java |
|:------:|:------:|:------:| |字节|Byte|byte/Byte| |整型|Int & Long|int/Integer & long/Long| |浮点型|Float & Double|float/Float & double/Double| |字符|Char|char/Character| |字符串|String|String|

函数

用fun关键字可声明一个函数

fun myFun(){
    return
}

函数的返回值与声明变量类型的方法相同,在函数声明的后面加上冒号和类型

fun myFun():Int{
    return 3
}

带参数的函数也大差不差

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

由于Kotlin还提供了一种语法糖,来简化只有一行的函数

fun Add(a : Int ,b : Int):Int = a + b

再结合一下他的类型推导系统,可以得到一种更简洁的语法

fun Add(a : Int ,b : Int) = a + b

if语句

Kotlin的if与其它语言的if并无不同

fun myMax(a : Int,b : Int):Int{
    if(a > b){
    return a
    }else{
    return b
    }
//当然还可以后面跟着else if,随意!
}

但Kotlin的if是可以有返回值的,就比如上面的函数可有化简为

fun myMax(a : Int,b : Int):Int{
    return if(a > b){
        a
    }else{
        b
    }
}

再次结合之前的一大堆内容,我们可以得到终极化简

fun myMax(a : Int,b : Int) = if(a > b) a else b

可以说Kotlin的if是加强版的三元运算符

when条件语句

Kotlin的when是大号版的switch,不同于其他语言的switch需要break,when语句相当直接

fun getScore(name: String) = when (name) 
{     
    "Tom" -> 86
    "Jim" -> 77     
    "Jack" -> 95     
    "Lily" -> 100     
    else -> 0 
}

当然,你也可以让他执行一些逻辑,比如你想帮某个学生偷偷加点分,只需要一个大括号

fun getScore(name: String) = when (name)
{
    "Tom" -> {
        var score = 87
        score +=10
    }
    "Jim" -> {
        77
    }
    "Jack" -> {
        95
    }
    "Lily" -> {
        100
    }
    else -> {
        0
    }
}

还有一种when的语法可以看作加强版的if-else if-else
比如上面的语句可以改写

fun getScore(name: String) = when
{
    name == "Tom" -> {
        var score = 87
        score +=10
    }
    name == "Jim" -> {
        77
    }
    name == "Jack" -> {
        95
    }
    name == "Lily" -> {
        100
    }
    else -> {
        0
    }
}

这种方式不在when中传入参数,而是将判断逻辑写在了when的结构体里

ps.Kotlin的String可以用==直接判断相等


循环控制

学过其他的语言都会while,所以Kotlin的while不用多说了...
这里最难理解就是for循环
Kotlin的for的语法,是和别处不同的:都是括号里一个循环变量,但是Kotlin有区间这个东西!
先说一下区间,在Kotlin你可以做如下的赋值:

val range = 0..10

这相当与创建了一个从0到10的区间,并且两端都是闭区间,用数学的方法来写就是$[0,10]$,假如你想要一个左闭右开,那么这么来:

val c = 0 until 10

同样的,在数学上就是$[0,10)$
现在我们可以进入Kotlin的for循环了,准确的说是for-in循环:

for(i in 1..10){
    println(i)
}//std out:1 2 3... 10
for(i in 1 until 10){
    println(i)
}//std out:1 2 3... 9

或者可以用step关键字声明步长

for(i in 1..10 step 2){
    println(i)
}//std out:1 3 5 7 9

那如果是反着来呢?比如从10降到1?是把步长的值调为负数么?试一下

for(i in 10..1 step -1){
    println(i)
}//报错!: Step must be positive, was: -1.

这样行不通!因为步长必须是正数!
Kotlin有针对这样情况的语法,只要用关键词downTo

for(i in range 10 downTo 1){
    println(i)//std out:10 9....1
}

同样的,这代表了一个两边都是闭区间的范围
如果还有更多需求...找while去吧


数据结构与Lambda


数据结构

ArrayList可以类比C++的std::vector<T>
Kotlin的写法如下

val fruit = arrayListOf<String>("Apple","Banana","Peach")
fruit.add("Pear")
fruit[1] = "cherry"

这个val的意思不是ArrayList里的东西不可改,而是表示指向ArrayList的引用不可改!如同C++的int * const

如上,ArrayList是可变的,不定长的,需要指明类型
不过你也可以不指明类型,然后把各种类型放进去

val largeArrayList = arrayListOf(1,"Banana",3.33f)

这是其实是省略了arrayListOf<Any><Any>类型,一个数据结构如果指明了是Any的话,那就可以放入任何东西了,除了Null
Any类型事实上就是Java的Object类,在Compile时被映射,Runtime时Any就被视为了Object
所以,一切的类都可以视作继承自Any
利用这个特性,可以用is关键词来判断元素类型,先用一个简单的for-in来遍历这个ArrayList,然后根据不同的类型打印不同的语句

     for(i in largeArrayList)
            {
                when
                {
                    (i is Int) ->
                    {
                        println("I is Int")
                    }
                    (i is String) ->
                    {
                        println("I is String")
                    }
                    (i is Double) ->
                    {
                        println("I is Double")
                    }
                    else -> println("Nope")
                }
            }

对于ArrayList,事实上还有更简单的初始化方法:listOf和mutableListOf

利用listOf()声明一个不可变的List,就是不能改变里面的任何元素

//手动声明类型
val fruit = listOf<String>("Apple","Banana","Peach")
//利用类型推导
val intList = listOf(1,2,3,4)

利用mutableListOf()声明一个可变的mutableList,就是能改变里面元素的List

//手动声明类型
val fruit = mutableListOf<String>("Apple","Banana","Peach")
fruit[0] = "Pear"
//利用类型推导
val intList = mutableListOf(1,2,3,4)
intList[0] = 5

还有一种不可存放重复元素的数据结构Set集合,当存放了多个元素,里面只会存在一份,使用方法

//不可变
val set = setOf("Apple", "Banana", "Orange", "Pear", "Grape")
//可变
val mutableSet = mutableSetOf("Apple", "Banana", "Orange", "Pear", "Grape")

还有存放键值对的数据结构Map

val map = HashMap<String, Int>()
//添加、读取方法方法一
//不建议使用
map.put("Apple", 1)
map.put("Banana", 2)
var number = map.get("Apple")
//添加、读取方法二
map["Grape"] = 5
number = map["Apple"] 
//添加方法三
//不可变map
val anotherMap = mapOf("Apple" to 1, "Banana" to 2, "Orange" to 3, "Pear" to 4, "Grape" to 5)
//这里的to并不是关键字, 而是infix函数
//同样的,用mutableMapOf创建可变的map

集合的Lambda编程

Lambda就是所谓的匿名函数,主流的语言中都有,Kotlin也不例外
有一个需求,找到水果集合中字符最长的水果名字,传统写法

val set = setOf("Apple", "Banana", "Orange", "Pear", "Grape")
var maxLen = 0
var fruitName : String
for(i in set){
    if(i.length > max_){
        maxLen = i.length 
        fruitName = i
    }
}

但利用lambda可以更简单,使用函数式API:

val set = setOf("Apple", "Banana", "Orange", "Pear", "Grape")  
val lambda = {str : String -> str.length}
val maxLength = set.maxBy(lambda)

maxBy函数可以根据传入的lambda的返回值来筛选最大的值 当然也可以不用专门定义lambda变量,直接传入函数

val set = setOf("Apple", "Banana", "Orange", "Pear", "Grape")  
val maxLength = set.maxBy({str : String -> str.length})

这段还可以更加简化,如下图
e1a0b152922ccf06dd2742bb01629507.jpg

图片来源: https://blog.csdn.net/PrisonJoker/article/details/114273946

对于集合还有些常用的接受Lambda的一堆API
比如.filter,当lambda返回true的时候就会把目标的元素加入一个新的集合,最后返回一个满足过滤条件的集合
map可以把集合里的元素按照Lambda所指定的方法映射,集合里的元素是lambda的返回值

val fruit = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
//返回一个集合里以A开头的元素组成的集合
var Afruit = fruit.filter { it.startsWith("A") }
//转换到大写,并且让元素的后面更上一句话
Afruit = Afruit.map{ it.uppercase() }.map{ it+" is my fruit" }

面向对象

类与对象

在IDEA/Android Studio可以用右键源文件夹,新建一个类;当然也可以手动在目录下新建文件 XXX.kt
用关键词class创建一个类,并且在main函数创建对象

class Person{
    var age = 0
    fun myAge(){
        println("I am "+age+" years old!")
    }
}
...
//in function main
val me = Person()
me.age = 20
me.myAge()

Kotlin创建对象的时候不用new关键字,这也是其语法的特点。
因为还没有构造函数,所以我们只能在类中初始化字段age

构造函数

Kotlin的构造函数分为主构造函数和次构造函数
每个类默认都有一个无参数的主构造函数 两种构造函数的定义方法:

class Person(var age : Int){
    init {
        age -= 2
    }
    //次构造函数
    constructor():this(0){
        println("Call to sub constructor")
    }
    //允许重写
    fun myAge(){
        println("I am "+age+" years old!")
    }
}

在这里,可以看到字段age的声明放到了Person类声明的括号里去了,这样在初始化类Person的时候就能够同时初始化age字段了
同时用init{}作为构造函数的一部分,可以把要执行的逻辑放进去。
次构造函数则必须调用主构造函数,用关键词constructor声明

在kotlin中类中所有的函数默认访问权限都是public,另附java,kotlin访问权限对照表
image.png

继承与接口

Kotlin的类默认是不能继承的,在前面加上关键字open用来允许其他类继承
在函数前加上open允许重写

class(mAge : Int) : Person(mAge){
    //注意,要在Person类的函数前加上open
    override fun myAge(){
        println("It's me! I am "+age+" years old!")
    }
}

用符号:表示继承的关系,同时必须调用父类的构造函数。

interface Study {     
    fun readBooks()     
    fun doHomework() 
}

让Me类实现接口,每个接口中的函数都必须实现!!!

class(mAge : Int) : Person(mAge){
    override fun myAge(){
        println("It's me! I am "+age+" years old!")
    }
    override fun readBooks() {         
        println(name + " is reading.")
    } 
    override fun doHomework() {         
        println(name + " is doing homework.")     
    }
}

也可以在接口中做一个默认实现,这样在类中就可以不需要强制实现这两个函数了

interface Study {     
    fun readBooks(){
        println("TO DO: readBooks")
    }     
    fun doHomework(){
        println("TO DO: doHomeWorks")
    } 
}

单例类

Kotlin对单例的实现做了巨大的简化,只需要用关键字object替换class就可以了

object Singleton{
    fun mPrint(){
        println("Nope.")
    }
}

调用方式

//in main function
Singleton.mPrint()

Kotlin会保证只有一份这个类存在.

数据类

数据类的声明为data class,在数据类中会自动覆写equals()、hashCode()、toString()方法..

非空检查

Kotlin中函数默认不能传入null,编译器会报错。
如果想突破这个限制,在类型后面加个?,这就是可空类型
但这时候,传入参数会为空,正常情况在调用参数时要做非空判断,Kotlin引入了简便的判空系统

fun study(man : Person?){
    man?.myAge()
    /*
    if(man != null){
        man.myAge()
    }
    */
}

但如果你非常确定它不会为空,而且希望编译器不报错,那么使用强制断言

fun study(man : Person?){
    man!!.myAge()
}

如果存在多个判空,在逻辑上视为多个if,可以用let函数来简化

fun study(man : Person?){
    man?.let {
        it.myAge()
        it.toString()
    }
}

同时let可以处理全局变量判空问题,保证线程安全

END