一篇就够——Kotlin快速入门

2020-08-11 11:39:13 浏览数 (1)

写在前面:

从3月份一直忙到5月份,现在,总算发版了,终于有时间再写点东西了。。。

之前零零散散的学过Kotlin,但是不成体系。最近,每天早起,利用上班之前的四个早晨,将传智播客的《Kotlin从零基础到进阶》又刷了一遍,并做了相应的笔记——也就是这篇文章。

这个视频的名字虽然有进阶字样,可我们能看到的视频部分真的没有进阶内容,余下的,大概就需要去购买了吧。不过,老话说的好:师傅领进门,修行靠个人。这部分内容虽然没有进阶,但是足以将我们领进门了。


文章内容主要是基于传智播客《kotlin从零基础到进阶》的视频做的笔记。 标题中的 V 是Video的缩写,V4 就是对应视频中的第四个视频。

V4、程序入口--main函数

V5、变量与输出

1、变量的声明:

代码语言:javascript复制
var name=“张三”  //变量声明关键词 var , 语句末尾不需要分号

2、数据额类型

V6、二进制基础

V7、类型推断和显示类型声明(变量和常量的声明)

1、类型推断

  • 在声明一个变量时,我们可以不需要显示声明数据类型,kotlin 会根据你为变量赋的值动态的推导出其类型。
  • 当一个变量被赋予了某个类型的数值之后,不能再赋给他其他类型的数值,否则,会报 类型错误。
代码语言:javascript复制
fun main(args:Array<String>){
    var a=10    //声明变量a , 将int类型的10 赋值给a, a 将只能接受int型数据
    a=15

    //a="字符串"   //将字符串类型的数据赋值给int型的a ,报错!!

    var s="字符串"
}

2、显示类型声明

格式: var变量名:变量类型=值

代码语言:javascript复制
var a:Int=10

3、变量声明的注意事项

  • 如果声明变量时就赋初值,可以不写类型,此时,类型推导将生效
  • 如果声明变量时没有赋初值,则必须写明类型,否则会报错。格式: vara:Int

4、常量的声明

关键字:val

格式: val常量名:常量类型=常量值

V8、变量的取值范围

获取某种类型数据的最大最小值

代码语言:javascript复制
fun main(args:Array<String>){
    val minByte:Byte=Byte.MIN_VALUE
    val maxByte:Byte=Byte.MAX_VALUE

    val a:Int=0b0011    //将二进制的0b0011 转换为10进制,并复制给a 
}
  • Byte、Int、Long 可以通过上述方式获取最大最小值
  • String 没有最大最小值,所以没有上述方法

V9、Kotlin函数入门

  • main() 程序入口
  • println() 打印

函数声明基本格式: fun函数名(参数:参数类型){函数体}

V10、Boolean运算

代码语言:javascript复制
Math.sqrt(5.0)         //sqrt()--开根号,获取5.0的开根值,即根号5的值
Math.pow(2.0,100.0)    //pow()--幂函数,计算2的100次方

V11、命令行交互式工具

不实用,内容省略。

V12、函数加强

函数命名格式说明:

代码语言:javascript复制
fun 函数名(参数:参数类型):返回值类型{
    函数体
}
  • 如果没有返回值,使用 :Unit 标识,也可以省略不写
  • 返回值也是使用 return 返回。

V13、函数声明练习题

内容省略

V14、字符串模板

  • “ ” ,被两个双引号包裹的内容是普通字符串,支持转义字符
  • “”“ ”“” ,被一对三引号包裹的内容是原样字符串,不支持转义字符,其中的内容被定义成什么样,输出的时候就是什么样。
  • 字符串模板格式:${占位字符串}

V15、条件控制--if、else

基本格式:

代码语言:javascript复制
if(条件){
    //DO STH 
}else{
    //DO STH
}

详细可参考:https://www.jianshu.com/p/b8eb0fe28dad

V16、字符串比较

代码语言:javascript复制
var str1="张三"    // = 是赋值
var str2="李四"

boolean flag=str1==str2  // == 是比较,等同于 Java中的 equals()
  • 可以使用 == 比较字符串
  • 也可以使用 equals(,) 方法比较字符串,该方法第二个参数表示是否忽略大小写,true--忽略大小写,false--不忽略大小写。
  • equals(,) 中第二个参数为 true时 效果等价于Java中的 equalsIgnoreCase()

V17、空值处理

  • 以 null 表示空值
  • kotlin 中定义方法时,默认接收的是 非 null 参数。
  • 如果定义某个方法可以接收 null参数,则在声明方法参数时在参数后面加上?。示例如下:
代码语言:javascript复制
fun test(str1:String?){    //String 后面的 ? 就表示该方法可以接收 null 作为参数
    //DO STH 
}

V18、When表达式

类似于Java中的switch

基本使用格式:

代码语言:javascript复制
when(变量){
    分支A -> 表达式
    else -> 表达式
}

带有返回值的When表达式:

代码语言:javascript复制
var result=when(变量){
    分支A -> 表达式(要有返回值,最终将值赋给result)
    else -> 表达式(要有返回值,最终将值赋给result)
}

详细参考:https://www.jianshu.com/p/b8eb0fe28dad

V19、Loop和Range

  • 声明一个区间数组
代码语言:javascript复制
var nums1=1..100  //表示我们声明了一个闭区间数组,其中包含的数值为 1-100。 .. 表示闭区间
var nums2=1 util 100 //前闭后开区间,取值 1-99. util 表示前闭后开区间
  • for 基本循环格式
代码语言:javascript复制
for(变量 in 数组或字符串){
    //DO STH 
}
  • 带有步进的for循环
代码语言:javascript复制
for (变量 in 数组 step 步进数量){    //所谓步进,就是递增幅度。默认步进为1
    //DO STH 
}
  • 数组.reversed() //数组内容反转
  • 数组.count() //获取数组的容量,等价于Java中的 数组.length

详细参考:https://www.jianshu.com/p/b8eb0fe28dad

V20、List 和 Map

1、List

  • List的基本声明格式:
代码语言:javascript复制
var list1=listOf(元素1,元素2,元素3)    //声明List时主要是通过 listOf()实现
  • 使用 for 循环同时遍历索引和索引对应的数值
代码语言:javascript复制
for((index,value) in list.withIndex()){    //重点是 withIndex() 方法,index 接收索引,value 接收对应的值
    //DO  STH 
}

2、Map (词典)

  • 基本声明格式:
代码语言:javascript复制
var map=TreeMap<键类型,值类型>()
map[key]=value
  • 示例代码:
代码语言:javascript复制
var map=TreeMap<String,String>()    //声明 map
map["好"]=good
map["学习"]=study    //添加键值对元素

println(map["好"])    //取值并打印

V21、函数和函数表达式

1、函数的简化

代码语言:javascript复制
//原函数:
fun sum(a:Int , b:Int):Int{
    return a b
}

//简化后
fun sum(a:Int , b:Int):Int=a b

2、使用 var 声明函数——函数表达式1(重点!)

kotlin 中除了使用基本的 fun 关键字声明函数外,还可以使用 var 声明。示例如下:

代码语言:javascript复制
var i={x:Int , y:Int -> x y} //声明函数i,接收两个Int类型参数 x、y,返回 x y 的值
i(3,5) //调用使用 var 声明的函数 i

3、使用 var 声明函数——函数表达式2(重点!)

代码语言:javascript复制
var j:(Int,Int)->Int={x,y -> x y}   //声明函数j,它接收的参数是两个Int, 返回一个Int,对应的表达式是 {x,y->x y}
j(4,4)    //调用函数

详细参考:https://www.jianshu.com/p/da4a93a356d6

V22、默认参数和具名参数

  • 具有默认参数值得函数声明
代码语言:javascript复制
val Pi=3.1415926
fun getRoundArea(PI:Float=Pi , radius:Float):Float{    //为变量PI赋予了默认值 Pi,这样,调用该方法时可以不再传递PI。但,如果我们想传入的值和默认值不一致时还是需要传入的
    return PI*radius*radius
}
  • 调用带有默认参数值的函数
代码语言:javascript复制
var a=getRoundArea(3.14f,5.0f) //因为我们相传入的PI和默认值不一致,所以,需要将3.14f传入
  • 具名参数的使用

所谓具名参数,就是调用某个方法时指明传入的参数是给哪个变量的。

代码语言:javascript复制
var a=getRoundArea(radius=5.0f) //我们需要的PI值与默认值一致,此时不需要再传入PI值。只需要通过 radius=5.0f 声明我们传入了半径值

v23、字符串和数字的转换

代码语言:javascript复制
var a="13"
a.toInt()    //字符串转换为Int

var b=13
b.toString()    //Int转换为字符串

V24、人机交互--根据用户输入的数字动态计算Sum

意念交互、语音交互、眼动跟踪、体感交互、打字交互

代码语言:javascript复制
fun main(array:ArrayList<String>){
    println(“请输入第一个数值”)
    var a=readLine()    //读取键盘输入的字符串内容,并赋值给a 
    println(“请输入第二个数值”)
    var b=readLine()

    var num1=a!!.toInt()  //readLine()得到的可能是null,所以此处通过 !! 断言输入不为null
    var num2=b!!.toInt() 

    println("${num1} ${num2}=${num1 num2}")
}

V25、异常处理

异常处理格式:--同Java

代码语言:javascript复制
try{
    //可能会出错的代码块
}catch(e:Excepiton){
    //出错之后的处理逻辑
}

V26、递归

1、利用递归实现 阶乘函数

代码语言:javascript复制
fun fact(a:Int):Int{
    if(a==1){
        return 1
    }else{
        return a*fact(a-1) //函数内调用函数本身就成为了递归
    }
}

2、BigInteger

在上面的方法中,我们用 Int 来接收阶乘的值,但是如果我们输入 100 ,会发现最终返回的值是 0, 是因为 100的阶乘超过了 Int的范围。所以,此时我们就需要使用到 BigInteger。

BigInteger 用来表示一个超大值。

代码语言:javascript复制
import java.math.BigInteger

fun main(array: Array<String>) {
    val num = BigInteger("50")    //声明BigInteger常量时传入一个字符串类型的数值
    println(fact(num))
}

fun fact(num: BigInteger): BigInteger {
    return if (num == BigInteger.ONE) {
        BigInteger.ONE
    } else {
        num * fact(num - BigInteger.ONE)
    }
}

V27、尾递归优化

1、什么是尾递归?

  • 尾递归 :是指某个函数的最后一步依旧是调用自身
  • kotlin中尾递归关键字 tailrec

参考:阮一峰老师关于尾递归的介绍 http://www.ruanyifeng.com/blog/2015/04/tail-call.html

2、为什么需要尾递归优化?

递归非常耗费内存,因为需要同时保存成千上百个调用记录,很容易发生"栈溢出"错误(stack overflow)。但 对于尾递归来说,由于只存在一个调用记录,所以永远不会发生"栈溢出"错误

3、使用尾递归实现累加

代码语言:javascript复制
fun main(args: Array<String>) {
    println(accumulation(5, 1))
}

/**
 * tailrec 是尾递归函数的关键字
 * 尾递归函数是指,在该函数的最后一步操作中依旧是调用函数本身
 * 为了实现尾递归,我们定义了该方法接收两个参数:num 是我们传入的需要计算累加值得的变量,total用来接收最终的返回值
 */
tailrec fun accumulation(num: Int, total: Int): Int {
    return if (num == 1) {
        total
    } else {
        accumulation(num - 1, num   total)  //此时,该调用的含义是:先计算 total=num total,然后计算 num=num-1
    }
}

V28、IDEA介绍

内容省略。 IDEA开发环境的搭建可以参考:https://www.jianshu.com/p/5a3e56f90b7f

V29、面向对象入门——定义一个类并构建对象

代码语言:javascript复制
//定义一个类,包含两个成员变量 height和width
class Rect(var height:Int,var width:Int)

fun main(args: Array<String>) {
    var rect=Rect(5,10) //构建Rect对象,不需要new
    println("矩形的宽${rect.width}高${rect.height}") //引用Rect类中成员变量
}

V30、静态属性和动态行为/方法——为类定义成员方法

代码语言:javascript复制
//定义一个类,包含两个成员变量 height和width.并定义一个成员方法
class Rect(var height: Int, var width: Int) {
    fun getArea(a: Int, b: Int): Int = a * b
}

fun main(args: Array<String>) {
    var rect = Rect(5, 10) //构建Rect对象,不需要new
    println("矩形的宽${rect.width}高${rect.height}") //引用Rect类中成员变量
    println("矩形的面积是${rect.getArea(rect.width, rect.height)}") //引用Rect类中成员变量

}

V31、面向对象的概念解释

没有实际内容,所以省略

V32、面向对象--洗衣机洗衣服案例

1、新建 WashMachine 类文件

代码语言:javascript复制
//定义成员变量和成员方法
class WashMachine(var brand: String, var size: Int) {    //品牌,容量
    fun openTheDoor() {
        println("打开洗衣机的门")
    }

    fun closeTheDoor() {
        println("关上洗衣机的门")
    }

    fun start() {
        println("启动洗衣机洗衣服")
    }
}

2、引用WashMachine类执行洗衣服的操作

代码语言:javascript复制
fun main(args: Array<String>) {
    var washMachine = WashMachine("小天鹅", 15)    //构建对象
    washMachine.openTheDoor()
    washMachine.closeTheDoor()
    washMachine.start()
}

V33、面向对象——升级版洗衣机案例

1、升级版洗衣机逻辑--检测门的开启状态和转速模式

代码语言:javascript复制
class WashMachine(var brand: String, var size: Int) {

    var isDoorOpen = false
    var curMode = 0

    fun openTheDoor() {
        println("打开洗衣机的门")
        isDoorOpen = true
    }

    fun closeTheDoor() {
        println("关上洗衣机的门")
        isDoorOpen = false
    }


    fun selectMode(mode: Int) {
        curMode = mode
        when (mode) {
            0 -> println("慢速模式")
            1 -> println("标准模式")
            2 -> println("快速模式")
            else -> println("初始状态")
        }
    }

    fun start() {
        if (isDoorOpen) {
            println("洗衣机的门还没有关闭")
        } else {
            when (curMode) {
                0 -> {
                    println("您选择的是慢速模式,发动机转速慢")
                    println("启动洗衣机洗衣服")
                }
                1 -> {
                    println("您选择的是标准模式,发动机常速运转")
                    println("启动洗衣机洗衣服")
                }
                2 -> {
                    println("您选择的是快速模式,发动机转速快")
                    println("启动洗衣机洗衣服")
                }
                else -> println("您还没有选择转速模式,发动机不会运转,不执行洗衣操作")
            }

        }
    }
}

2、引用洗衣机对象洗衣服

代码语言:javascript复制
fun main(args: Array<String>) {
    var washMachine = WashMachine("小天鹅", 15)
    washMachine.openTheDoor()
    washMachine.closeTheDoor()
    washMachine.selectMode(1)    //加入了模式选择
    washMachine.start()
}

V34、面向对象——封装

  • 隐藏内部实现细节即为封装——Java系的程序员都知道!
  • 私有关键字 :private —— 同Java。私有即是封装的一种体现

V35、面向对象——继承

  • 一个对象直接使用另一个对象的属性或方法 —— 同Java
  • 被继承的父类必须用 open 修饰,表示允许其他类继承该类
  • 父类中的方法如果允许子类重写,也需要用 open 修饰
  • 重写父类方法时需要用 overRide 修复重写后的方法
  • 继承的格式: class子类:父类()

父类

代码语言:javascript复制
open class Father {    //用 open 修饰,允许被继承
    var character = "性格内向"
    open fun action() {    //用open修饰,允许被重写
        println("喜欢读书")
    }
}

子类:

代码语言:javascript复制
class Son : Father() {    //继承。 Son 继承自 Father
    override fun action() {    //重写父类方法
        //super.action()
        println("儿子的性格是$character")   
        println("儿子不喜欢看书,但是喜欢唱歌")
    }
}

V36、抽象类及其实现

  • 抽象的关键字 abstract —— 同Java
  • 抽象类和方法不需要用 open 声明可以被继承/实现

抽象类Human

代码语言:javascript复制
abstract class Human (var name: String){      //定义抽象类,使用 abstract 修饰。包含成员变量name
    abstract fun eat()      //定义抽象方法, 使用 abstract 修饰
}

抽象类的子类Man

代码语言:javascript复制
class Man(name: String) : Human(name) {    //继承自Human抽象类
    override fun eat() {    //必须重写抽象方法
        println("${name}是男人,是家中劳力,所以吃的多")
    }
}

调用子类

代码语言:javascript复制
fun main(args: Array<String>) {
    var man=Man("张三")
    man.eat()
}

V37、多态

  • 同种功能,不同的表现形式 即为 多态

视频中用男人和女人的尿尿姿势来解释多态,很形象哈

抽象类Human

代码语言:javascript复制
abstract class Human(var name: String) {      //定义抽象类,使用 abstract 修饰。包含成员变量name
    abstract fun eat()      //定义抽象方法, 使用 abstract 修饰
    abstract fun pee()  //定义抽象方法
}

子类Man

代码语言:javascript复制
class Man(name: String) : Human(name) {
    override fun pee() {
        println("${name}是男人,是站着尿尿的")
    }

    override fun eat() {
        println("${name}是男人,是家中劳力,所以吃的多")
    }
}

子类Woman

代码语言:javascript复制
class Woman(name: String) : Human(name) {
    override fun eat() {
        println("${name}是女人,饭量比较小")
    }

    override fun pee() {
        println("${name}是女人,是蹲着尿尿的")
    }
}

外部调用

代码语言:javascript复制
fun main(args: Array<String>) {
    var man=Man("张三")
    var woman=Woman("小花")

    var list= listOf<Human>(man,woman)    //定义集合
    for (human in list){    //遍历集合
        human.pee()
    }
}

V38、接口及其实现

  • 接口--数据有进有出的交互方式、
  • 接口关键字:interface —— 同Java
  • 接口是事物的能力(代表某种事物的特性),抽象类是事物的本质(代表的是一类事物的共性)
  • 子类实现接口时,接口名后面不需要()

定义接口IMan

代码语言:javascript复制
interface IMan {    //定义一个男人的接口
    fun xiaodidi()
}

Man类实现IMan接口

代码语言:javascript复制
class Man(name: String) : Human(name) ,IMan{  //男人属于人,所以继承Human;男人有小弟弟,所以实现 IMan接口
    override fun xiaodidi() {
        println("这是重写IMan接口中的方法——男人有小弟弟")
    }

    override fun pee() {
        println("${name}是男人,是站着尿尿的")
    }

    override fun eat() {
        println("${name}是男人,是家中劳力,所以吃的多")
    }
}

太监不能实现IMan接口

代码语言:javascript复制
class TaiJian(name: String) : Human(name) {  //太监属于人,但是太监没有小弟弟,所以不能实现 IMan接口
    override fun eat() {
        println("太监也能吃饭")
    }

    override fun pee() {
        println("太监也会尿尿")
    }
}

V39、代理和委托——大头儿子和小头爸爸的洗碗案例

  • 委托,把自己不干的事情交给别人做
  • 代理,做别人委托的事情
  • kotlin中接口代理关键字:by

1、场景说明

代码语言:javascript复制
围裙妈妈只负责做饭,不负责洗碗
小头爸爸洗一次碗可以赚到10元
大头儿子洗一次碗可以赚到1元
小头爸爸承揽了洗碗的活,最终交给大头儿子做,中间赚了9元差价

2、代码实现——完全委托

(1)、定义洗碗的接口
代码语言:javascript复制
interface IWashBow {    //定义一个洗碗接口,包含一个洗碗方法
    fun washBow()
}
(2)、大头儿子实现接口
代码语言:javascript复制
class BigHeadSon:IWashBow {    //被实现的接口后面不需要加()
    override fun washBow() {
        println("我是大头儿子,每次洗碗赚1元钱")
    }
}
(3)、小头爸爸实现接口并委托事件给小头儿子
代码语言:javascript复制
class SmallHeadFather:IWashBow by BigHeadSon(){     //委托关键字 by;被委托方(即代理方)如果不是单例类,则后面需要跟()

}
(4)、程序调用及输出结果
代码语言:javascript复制
fun main(args: Array<String>) {
    var father=SmallHeadFather()
    father.washBow()    //小头爸爸已经将洗碗的操作委托为小头儿子了,所以,此处本质是调用的小头儿子的洗碗操作
}

V40、单例模式

  • 单例关键字:object 我们在定义一个类时,使用object替换class来修饰这个类,就表示,这是一个单例类
  • 单例类作为代理人时,不需要()

1、场景说明

代码语言:javascript复制
小头爸爸为了增进父子感情,想和小头儿子一起洗碗

3、代码实现

(1)、小头爸爸重写接口方法 —— 未使用单例时的错误写法
代码语言:javascript复制
class SmallHeadFather:IWashBow by BigHeadSon(){    

    override fun washBow() {
        println("我是小头爸爸,我把洗碗事件委托给了大头儿子")
        BigHeadSon().washBow()  //委托方重写了事件之后,需要手动调用代理方的方法。但是,此处又通过()构建了一个小头儿子对象,已经不再是我们初始委托的那个大头儿子了。所以,此处是有问题的。
        println("我是小头爸爸,大头儿子洗完碗之后,我赚了9元")
    }
}
(2)、使用单例后的正确写法:

大头儿子单例类

代码语言:javascript复制
object BigHeadSon:IWashBow {    //单例关键字object,声明为单例类之后会立即在内存中创建单例对象,并一直存在
    override fun washBow() {
        println("我是大头儿子,每次洗碗赚1元钱")
    }
}

小头爸爸委托事件给单例的大头儿子

代码语言:javascript复制
class SmallHeadFather:IWashBow by BigHeadSon{     //被委托方(即代理方)是单例类,不需要通过()构建对象

    override fun washBow() {
        println("我是小头爸爸,虽然我把洗碗事件委托给了小头儿子,但是我要和他一起洗碗")
        BigHeadSon.washBow()  //委托方重写了事件之后,需要手动调用代理方的方法。由于 BigHeadSon是单例的,所以,这还是我们之前委托的那个儿子
        println("我是小头爸爸,我和小头儿子洗完碗之后,我赚了9元")
    }
}
(3)、外部调用
代码语言:javascript复制
fun main(args: Array<String>) {
    var father=SmallHeadFather()
    father.washBow()    //小头爸爸已经将洗碗的操作委托为小头儿子了,但因为重写了洗完事件,所以,本子是调用的父亲的洗完事件,父亲的洗完事件中有一部分是自己做的,另一部分是儿子做的
}
(4)、输出结果

V41、枚举--enum

枚举示例代码

代码语言:javascript复制
enum class Week {   //枚举关键字 enum 
    星期一, 星期二, 星期三, 星期四, 星期五, 星期六, 星期天
}

fun main(args: Array<String>) {
    println(Week.星期一)
    println("${Week.星期一}在Week中的索引是${Week.星期一.ordinal}")
}

运行结果

V42、印章类/密封类 (Sealed Class)

1、印章类的特点

  • 子类类型有限的类成为 印章类/密封类
  • 印章类使用 sealed 作为修饰符
  • 印章类本身没有构造方法

2、印章类与枚举的区别

  • 都是有限的数据
  • 枚举更注重具体的数据
  • 印章类更注重数据的类型

3、印章类示例代码

(1)、场景说明
代码语言:javascript复制
假设你家有一头公驴、一头母驴、一头公马。那么,
它们可能会生出一头小驴,
也可能会生出一头小骡子。
(2)、代码示例

在上述场景中,由于他们能生出的儿子类型时固定的,所以,我们可以使用印章类来标识。

声明印章类

代码语言:javascript复制
sealed class Son {  //使用 sealed 声明 Son 为印章类/密封类

    class SmallMule() : Son()   //声明小骡子 SmallMule 为 Son的子类。
    class SmallDonkey() : Son() //声明小驴子 SmallDonkey 为 Son的子类

    fun sayHello(son: Son) {
        if (son is SmallMule) {     //判断是不是XX的实例的关键字 is 
            println("小骡子对大家说大家好")
        } else if (son is SmallDonkey) {
            println("小驴子对大家说大家好")
        }
    }
}

调用印章类

代码语言:javascript复制
fun main(args: Array<String>) {
    var mule = Son.SmallMule()
    var donkey = Son.SmallDonkey()

    var list = listOf<Son>(mule, donkey)
    for (son in list) {
        son.sayHello(son)
    }
}

运行结果


Kotlin基础到此完结,之后需要研究的内容

  • 游戏开发和游戏引擎(lwjgj)
  • Lambda 表达式
  • 高阶函数
  • DSL (特定领域语言)
  • 使用Kotlin进行Android开发
  • 使用Kotlin开发SpringBoot引用

0 人点赞