第一章 Kotlin基础语法

作者: Serenity那年 | 来源:发表于2017-06-27 18:21 被阅读153次

    一、常量与变量(val,var)

    • 1.什么是常量?

    1 .val = value ,值类型;
    2.类似Java的final;
    3.不能重复赋值;
    4.举例:
    运行时常量:val x = getX(); 也就是程序运行时才赋值;
    编译期常量:const val x = 2 ;也就是在编译时期就赋值了;

    • 2.什么是变量?

    1.var=variable ;
    2.可以再次赋值;
    3.举例:
    var x = "HelloWorld"//定义变量
    x = "HiWorld"//再次赋值

    示例如下:

    /**
     * 常量
     * 带const为编译期的常量
     * 不带const的val为运行期常量
     * 不能再次赋值
     */
    const val FINAL_HELLO_WORLD:String = "Hello world"
    
    //变量 能够再次赋值
    var helloworld:String = FINAL_HELLO_WORLD
    

    二、类型推导

    val string = "Hello"//推导出String类型
    val int = 5 //Int类型
    var x = getString()+5 //String类型,加号在这里是连接符

    三、函数--单一原则:

    1.任何函数都是以fun开头的,紧接着的是方法名字,紧接着的是参数列表,紧接着的是返回值,紧接着是函数体;
    fun <MethodName>(<ParamsName>:<ParamsType>):<returnValue>{}
    2.如果无返回值,可以写:Unit或者在参数列表后什么都写;Unit类型Java中的void;
    3.如果参数有多个,以逗号分开;
    4.如果一个函数只是返回一个函数的表达式的值,直接可以等于表达式即可;
    5.函数的名字不是必须的,如果有个变量接受返回值即可编译通过;--请看下面的例子
    6.函数作为类型的一种出现的,可以被用来赋值与传递的;

    /**
     * 函数
     * methodName
     * param1,param2是参数名
     * String Int 是参数类型
     * 备注:
     * 1.任何函数都是以fun开头的;
     * 2.如果参数有多个,以逗号分开;
     * 3没有返回值在参数后添加:Unit,也可以不写,这个Unit相当于java里面的void;
     * 4如果一个函数只是返回一个函数的表达式的值,直接可以等于表达式即可;
     */
    fun methodName(param1:String,param2:Int):Unit {
    
    }
    
    //带返回值的函数
    fun testMethod(args:String):String{
        return args
    }
    
    /**
     * 如果一个函数只是返回一个函数的表达式的值,直接可以等于表达式即可
     */
    fun returnFun0(param1: Int, param2: Int):Int {
        return param1+param2
    }
    //上面的等价于下面的写法:
    fun returnFun(param1:Int,param2:Int) = param1+param2
    
    /**
     * 函数可以没有名字的,如果有一个变量来接收的话即可
     */
    val resultValue = fun(x: Int):Int {
        return x
    }
    

    四、Lambda表达式

    1.也就是匿名函数;函数作为类型的一种出现的,可以被用来赋值与传递的;
    2.写法:{[参数列表] ->[函数体,最后一行是返回值]}
    3.() -> Unit
    --无参,返回值为Unit;
    4.(Int) -> Int
    --有参-传入Int类型,返回一个整数;
    5.(String,(String) -> String) -> Boolean
    --传入字符串、Lambda表达式,返回Boolean;

    • 1.Lambda表达式的调用

    1.用括号()来表示方法的调用,是Kotlin中的运算符,等价于invoke();这个invoke其实是运算符重载的方法
    2.Lambda表达式如果没有参数,箭头 (->)就不用写了;
    3.Lambda表达式不是只有一行的,它的返回值是它表达式的最后一行的值;

    val sum = {a:Int,b:Int ->a+b}
    //用括号()来调用,等价于invoke();
    sum(2,3)
    或者使用:sum.invoke(2,3),二者完全等价;
    
    //Lambda表达式没有参数,箭头可以不写,如下:
    val printHello = {
      println("hello")//相当于函数的函数体
    }
    
    //Lambda表达式不是只有一行的,它的返回值是它表达式的最后一行的值
    val sum = {a:Int,b:Int ->
      println("$a + $b = ${a+b}")//这一行也是表达式,不过返回的是Unit类型
      a+b//这一行最为Lambda的最后一行,它的值也是Lambda的返回值;
    }
    
    • 2.Lambda表达式的简化
    • 1.函数参数调用时最后一个Lambda可以移出去;也就是说如果一个函数调用时,最后一个参数是Lambda表达式,我们传入参数时,那么这个参数可以被移出去;
    • 2.函数的参数只有一个Lambda,调用时小括号可以省略;
    • 3.如果一个Lambda只有一个参数,可以不写,可默认为it;
    • 4.入参、返回值与形参一致的函数可以用函数引用的方式作为实参传入;
    val sum = {a:Int,b:Int ->a+b}
    //Lambda表达式在没有参数时,箭头就不用写了,如下:
    //如下面的写法,因为没有参数,箭头就不用写了:
    val printHello = {
       println("hello")//相当于函数的函数体
    }
    
    //看下面的数组遍历:
    arg.forEach{element -> println(element) }
    //如果一个Lambda传入的参数只有一个,可以不写,可默认为it;上面的等价于:
    arg.forEach{ println(it) }
    //函数的参数只有一个Lambda表达式时,调用时可以将大括号移到小括号外面;
    //上面的等价于下面的:
    arg.forEach(){println(it)}
    //这个小括号没有什么用,可以把这个小括号省略
    arg.forEach{println(it)}
    //如果函数的参数和返回值与action的参数和返回类型一致,就可以用函数引用的方式,如下:
    arg.forEach(::println)
    

    五、循环语句

    for循环如下:

    
        for (i in args) {
            println("输出的是:$i")
        }
    
    
        for ((index, value) in args.withIndex()) {
            println("$index -> $value")
        }
    
        for (indexValue in args.withIndex()) {
            println("${indexValue.index}---->${indexValue.value}")
        }
    //或者使用下面的方式
       args.forEach{
         println(it)
       }
    }
    
    //看下面的一个方法,return会把函数截断,无法执行到最后一行
    fun main(args: Array<String>) {
    
        args.forEach {
            if (it == "c") return
            println(it)
        }
        //你会发现如果上面的if条件触发了 ,将跳出for循环,并且执行不到下面的代码,原因是:
        //上面的是表达式,而不是一个简单的遍历,return会调出整个函数的执行
        println("The End")
    }
    //如果想只跳出it=="c"这个条件,循环继续进行,整个函数执行完毕,可以如下:
    fun main(args: Array<String>) {
    
        args.forEach ForEach@ {
            if (it == "c") return@ForEach
            println(it)
        }
        println("The End")
    }
    

    while循环

    var int = 5 
        while (int > 0) {
            println("int---->$int")
            int-- 
        }
        
        do {
            println("int---->$int")
            int--
        }while (int>5)
    

    如何跳出 或 跳过循环?
    跳出或终止循环:break
    跳过循环:continue
    多层嵌套循环的终止需要结合标签来实现

     Outter@ for (i in args) {
    
            Inner@while (i.toInt() > 0) {
                break@Outter  //这里只是跳出外层的for循环 break和@Outter之间不能有空格
            }
        }
    

    六、异常捕获(try catch finally)

    kotlin里面的异常捕获和Java中的异常捕获用法一样,最后都会调用finally;但Kotlin中的try、catch也是表达式,这点与Java中的不一样,它可以有返回值,返回值是表达式的最后一行的值;

    • catch分支匹配异常类型;
    • 它是一个表达式,可以用来赋值,与if else when 表达式是完全类似的;
    • finally 无论try catch 是否抛出异常都会执行到;

    注意此行代码代表的含义:
    return try{ x/y } catch(e:Exception){ 0 } finally{...}
    代表:先执行finally里面的代码,再返回;如果无异常,返回x/y;如果有异常,返回0;

      //kotlin中的try catch也是表达式,可以有返回值;返回值是表达式的最后一行的值;
      val result = try {
            args[0]
        } catch (e: Exception) {
            println("jinlaile")
            0
        } finally {
            println("进入finally代码块")
        }
        println(result)
    

    输出结果:
    jinlaile
    进入finally代码块
    0

    七、成员方法、成员变量

    • 属性:或者说是成员变量,类范围内的变量;构造方法中val/var修饰的都是属性;类内部也是可以定义属性的的;
    /**
     * aField和anotherField是类Hello的两个属性;
     * 而notField不是属性,只是普通构造方法的参数;
     */
    class Hello(val aField: Int, notField: Int){
        val anotherField:String = "hello"
    }
    
    • 方法:或者说是成员函数,类范围内的函数;
      在kotlin的类中,自动为var修饰成员变量实现了get、set方法,val修饰的只有get方法,因为val修饰的是不可变的;如果想要在get、set方法中注入部分逻辑,需要我们重写其get、set方法,如下;
    /**
     * Created by serenitynanian on 2017/6/27.
     */
    class ClassMemberAndField {
    
        //默认访问控制是public
        var b = 0
    
        get() {
            println("执行打印逻辑")
            //这个filed其实是上面b后真正的值0,这个filed只有在get、set方法中才能访问的到
            return field
        }
            /**
             * 类型java中的set方法
             * 
             * public void setB(int b){
             *      
             *      this.b = b ;
             * }
             */
        set(value) {
            println("执行打印逻辑")
            field = value
        }
    }
    

    如果想要控制get、set的访问权限,可以在get、set方法前加访问限制符,比如protected

      protected set(value) {
                println("logic")
            field = value
        }
    

    如果说get、set中没有部分逻辑,只是想要修改访问控制符,只需要如下写即可:

    class ClassMemberAndField {
    
        //默认访问控制是public
        protected var b = 0
    
    //    get() {
    //        //这个filed其实是上面b后真正的值0,这个filed只有在get、set方法中才能访问的到
    //        return field
    //    }
    //        /**
    //         * 类型java中的set方法
    //         *
    //         * public void setB(int b){
    //         *
    //         *      this.b = b ;
    //         * }
    //         */
    //    protected set(value) {
    //            println("logic")
    //        field = value
    //    }
       
    //如果只想要控制访问权限,只需要这样写即可:
        protected get
        
        protected set
    
    }
    

    在kotlin中声明成员变量时,编译器提醒你必须初始化,否则报错;与java不同的时,java会自动给你初始化初始值;但初始值也占内存的,所以有时我们只需要在使用时才想初始化,这样怎么办呢?
    只需要在声明成员变量时指定一个lateinit特殊字符就行了;但是在使用之前必须进行初始化,否则报错;

    var age:Int = 0 
        //如果不指定lateinit 编译器会提醒必须初始化或者abstract,
        //指定后就是告诉编译器,我后面会进行初始化;
        //如果你在初始化它之前就使用这个变量,会报错的;
        lateinit var name:String
    

    但是lateinit只能放在var修饰的可变变量前面,不能放在val修饰的变量前面;如果想要达到延迟初始化必须使用delegate,如下:

       /**
         * lazy需要传入一个lambda表达式,一个无参的表达式返回一个T类型,
         * 这个T类型就是下面的String类型;
         */
       val address:String by lazy {
           "dsfa"
       }
    

    总结下属性访问控制:
    var修饰的有get、set方法;
    val修饰的只有get方法,没有set方法,因为val修饰的变量是不可变的;

    总结下属性初始化:
    属性的初始化尽量在构造方法中进行初始化完成;
    无法在构造方法中初始化,尝试降级为局部变量;
    var用lateinit延迟初始化,val用lazy;
    可空类型谨慎使用null直接初始化;

    八、表达式(中缀表达式、分支表达式、when表达式)

    • 中缀表达式
    • 只有一个参数,且用infix修饰的函数;
    class Book {
       infix fun on(place: String) {
    
        }
    }
    
    fun main(args: Array<String>) {
        //函数前没有使用infix关键字,必须像平常调用函数一样使用
        Book().on("my desk")
        
        //如果函数前面使用了infix,不用在方法前加. 不用在方法后加括号
        Book() on "my dest"
    }
    
    • 2.使用了中缀表达式后,在调用时,可以直接使用[对象] <函数名> [参数对象] 如上示例;

    • 分支表达式(if),而不只是分支语句
      分支表达式是可以有返回值的,返回值是分支语句中最后一行的值 ;

    private const val DEBUG = 1
    private const val USER = 0
    fun main(args: Array<String>) {
    
    //    var mode = USER
    //    if (args.isNotEmpty() && args[0] == "1") {
    //        mode = DEBUG
    //    }
    
        /**
         * 从下面可以看出来if可不只是一个简单的分支语句,
         * 它还是一个表达式,它是有返回值的,返回值是是分支表达式中最后一行的值;
         * 在上面的分支语句中,只能定义var的mode,因为后面要修改;
         * 如果使用分支表达式,就直接可以使用val修饰的mode了,使用如下:
         */
        val mode =
                if (args.isNotEmpty() && args[0] == "1") {
                    DEBUG
                }else{
                    USER
                }
    
        println("请输入用户名:")
        val username = readLine()
        println("请输入密码:")
        val password = readLine()
    
        if (mode == DEBUG && username == USERNAME && password == PASSWORD) {
            println("超管登录成功")
        } else if (username == USERNAME && password == PASSWORD) {
            println("普通用户登录成功")
        } else {
            println("登录失败")
        }
    
    }
    
    • when表达式
      类似Java中的switch语句,由于case语句中只能判断String,enum,byte,int等,并且在每个分支语句中都要写break;在kotlin中没有了switch语句了,使用了when表达式来代替了;
        public void pause() {
            switch (state) {
                case BUFFERING:
                case PLAYING:
                    doPause();
                    break;
                default:
                    //什么都不做
            }
        }
    
    //下面是kotlin中的when表达式
        fun pause() {
            when (state) {
                PlayerKt.State.BUFFERING, PlayerKt.State.PLAYING -> doPause()
                else -> {
    
                }
            }
        }
    

    when分支表达式只要第一个分支语句执行了,不用使用break,后面的分支语句就不会执行了

    fun main(args: Array<String>) {
        var x = 5
        //when表达式只要第一个分支语句执行了,不用使用break,后面的就不会执行了
        when (x) {
            is Int -> println("Hello $x")
            in 1..100 -> println("$x is in 1..100")//x的值是否在[1,100]
            !in 1..100 -> println("$x is not in 1..100")//x的值不在[1,100]
            in 1 until 5 -> println("$x is in[0,5)")//x的值是否在[0,5)之间,取不到5
            args[0].toInt() -> println("x == args[0]")//这个是说args[0]是否与x一样
        }
    }
    
    //when与if一样,也有返回值,都是每一个分支最后一行表达式的值;
        val mode = when {
            args.isNotEmpty()&& args[0] == "1" -> 1
            else -> 0
        }
    

    表达式总结:

    • if ... else ...这个用法基本和Java中一致;
    • 表达式必须具备完整性,如果只有if没有else编译报错;
     var b = if (mode == 0) {
            0
        } 
        //如果没有下面的分支语句,编译报错
        //赋值时,分支必须完备
        else {
            1
        }
    
    • when表达式,加强版的switch,支持任意类型,也不用写break;
      支持纯表达式条件分支(类似if)
       val mode = when {
           args.isNotEmpty()&& args[0] == "1" -> 1
           else -> 0
       }
    

    必须具备完备性;

    九、具名参数,变长参数,默认参数

    • 具名参数:就是在给函数传入实参的时候,把形参也赋值上,也就是下面在调用sum函数时,在传入实参的时候,把arg1 和 arg2也附带上(arg1 = 1,arg2 = 2);

    具名参数的顺序可以不是固定的;

    class ForDemo {
    
        fun sum(arg1:Int,arg2:Int) = arg1 + arg2
    }
    
    fun main(args: Array<String>) {
    
    
        var forDemo = ForDemo()
        /**
         * 在使用函数传入实参的时候,把形参也赋上;
         * 因为是具名参数,所以形参的顺序可以不固定
         * 也就是明确告诉编译器这个2是给arg2的,1是给arg1的
         */
        forDemo.sum(arg2 = 2, arg1 = 1)
    
    }
    
    • 变长参数
      某个参数可以接收多个值;
      变长参数可以不为最后一个参数;但是传入实参时,可以放在最后用具名参数表示即可;
      如果传参有歧义,需要使用具名参数指定某个特定参数;
      最常见的就是main函数,它里面是个数组,我们可以将它改变为变长参数;它的使用和数组完全一样
       var array = intArrayOf(1,23,43,25,56)
    
        hello(3.0,1,123,321,2,string = "hello")
        //*:Spread Operator这个符号现在只支持变长参数,且只支持array,不支持list
        hello(3.0,*array,string = "hello")
    
    fun hello(double:Double,vararg ints: Int,string:String) {
        ints.forEach { ::println }
    }
    

    在Java中变长参数只能是函数的最后一个参数,否则编译器无法识别的;
    在kotlin中因为存在具名函数,因此它可以放在任意位置;在传入实参的时候可以用具名函数放在最后一个位置;

    *:Spread Operator 这个不是一般的运算符;
    只支持展开Array;
    只用于变长参数列表的实参;
    不能重载;

    • 默认参数
      1.就是在函数编写时,可以给函数任意位置的形参一个默认值;在实际调用函数时,这个参数可以不传;
      2.但是如果该默认参数不是最后一个,其他参数使用具名参数即可,不用写默认的;
      3.传参出现歧义时,也就是编译器报错,需要使用具名函数,具体请看第二条;
        hello(3.0,1,123,321,2,string = "hello")
        //* 这个符号现在只支持变长参数,且只支持array,不支持list
        hello(ints = *array,string = "hello")
    
    fun hello(double:Double = 3.0 ,vararg ints: Int,string:String) {
        ints.forEach { ::println }
    }
    

    相关文章

      网友评论

        本文标题:第一章 Kotlin基础语法

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