kotlin_kotlin- - CSDN
kotlin 订阅
Kotlin (科特林)是一个用于现代多平台应用的静态编程语言 [1]  ,由 JetBrains 开发。Kotlin可以编译成Java字节码,也可以编译成JavaScript,方便在没有JVM的设备上运行。除此之外Kotlin还可以编译成二进制代码直接运行在机器上(例如嵌入式设备或 iOS)。 [1]  Kotlin已正式成为Android官方支持开发语言。 展开全文
Kotlin (科特林)是一个用于现代多平台应用的静态编程语言 [1]  ,由 JetBrains 开发。Kotlin可以编译成Java字节码,也可以编译成JavaScript,方便在没有JVM的设备上运行。除此之外Kotlin还可以编译成二进制代码直接运行在机器上(例如嵌入式设备或 iOS)。 [1]  Kotlin已正式成为Android官方支持开发语言。
信息
中文名
科特林
性    质
编程语言
外文名
Kotlin
开源协议
Apache 2
发布时间
2016年2月15日
语言版本
1.3.72
开发公司
JetBrains
推出时间
2011年7月
Kotlin简介
2011年7月,JetBrains推出baiKotlin项目,这是一个面向duJVM的新语言 [2]  ,它已被开发一年之久。JetBrains负责人Dmitry Jemerov说,大多数语言没有他们正在寻找的特性,Scala除外。但是,他指出了Scala的编译时间慢这一明显缺陷。Kotlin的既定目标之一是像Java一样快速编译。 [1]  2012年2月,JetBrains以Apache 2许可证开源此项目。 [2]  Jetbrains希望这个新语言能够推动IntelliJ IDEA的销售。 [3]  Kotlin v1.0于2016年2月15日发布。这被认为是第一个官方稳定版本,并且JetBrains已准备从该版本开始的长期向后兼容性。 [2]  在Google I/O 2017中,Google宣布在Android上为Kotlin提供一等支持。 [2] 
收起全文
  • 第一阶段:Kotlin的基本语法,包括类和方法的定义及调用、数组、条件判断、循环控制、接口定义及实现、泛型等等; 第二阶段:用Kotlin创建Android项目,运用kotlin初始化界面、控件、网络请求及数据处理并展示等
  • Kotlin基础与Android实战

    2020-01-08 10:24:43
    - 带你学习Google强推的新静态语言Kotlin - 通俗易懂的囊括Kotlin语言基础 - 通过所学的Kotlin进行Android开发 - 所使用的的技术点  - Kotlin  - Android项目实战  - Retrofit网络请求框架
  • 征服Kotlin视频教程

    2018-11-29 17:30:09
    该课程包括Kotlin开发环境搭建、Kotlin基础知识、类和接口、枚举类、扩展、泛型、函数、lambdas表达式、对象、标准API等。
  • 随着Kotlin的推广,一些国内公司的安卓项目开发,已经从Java完全切成Kotlin了。虽然Kotlin在各类编程语言中的排名比较靠后(据TIOBE发布了 19 年 8 月份的编程语言排行榜,Kotlin竟然排名45位),但是作为安卓开发者...
  • Kotlin初探

    2017-04-26 17:56:42
    示例源码传送门前言Kotlin是一种在 Java虚拟机上执行的静态型别编程语言,它主要是由俄罗斯圣彼得堡的JetBrains开发团队所发展出来的编程语言。该语言有几个优势 1. 简洁 它大大减少你需要写的样板代码的数量。 ...

    示例源码传送门

    前言

    Kotlin是一种在 Java虚拟机上执行的静态型别编程语言,它主要是由俄罗斯圣彼得堡的JetBrains开发团队所发展出来的编程语言。该语言有几个优势
    1. 简洁
    它大大减少你需要写的样板代码的数量。
    2. 安全
    避免空指针异常等整个类的错误。
    3. 通用
    构建服务器端程序、Android 应用程序或者在浏览器中运行的前端程序。
    4. 互操作性
    通过 100% Java 互操作性,利用 JVM 既有框架和库。

    配置

    在我们的AndroidStudio开发工具中,要想使用Kotlin这个优秀的开发语言,我们需要安装插件,直接在安装插件界面搜索Kotlin然后安装。之后再gradle文件增加如下配置

    apply plugin:'kotlin-android'
    apply plugin:'kotlin-android-extensions'
    
    dependencies {
        compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    }
    

    项目gradle文件

    buildscript {
        ext.kotlin_version = '1.1.1'
        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
        }
    }

    完成上面的配置后,我们就可以愉快的玩耍了。

    Kotlin示例

    首先我们还和以前一样,创建一个Android项目,自动创建一个Activity之后我们再创建一个java类

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
            setSupportActionBar(toolbar);
    
            FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
            fab.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                            .setAction("Action", null).show();
                }
            });
        }
    
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            // Inflate the menu; this adds items to the action bar if it is present.
            getMenuInflater().inflate(R.menu.menu_main, menu);
            return true;
        }
    
        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            // Handle action bar item clicks here. The action bar will
            // automatically handle clicks on the Home/Up button, so long
            // as you specify a parent activity in AndroidManifest.xml.
            int id = item.getItemId();
    
            //noinspection SimplifiableIfStatement
            if (id == R.id.action_settings) {
                return true;
            }
    
            return super.onOptionsItemSelected(item);
        }
    }
    public class Test {
        private static String str = null;
    
        public static void main(String[] args) {
            str = "Code4Android";
            System.out.println(str);
        }
    }

    那上面的代码如果用kotlin实现是什么样子呢。尽管现在我们还不能写出Kotlin代码,但是在安装插件后AS中提供了自动转换Kotlin代码的功能

    这里写图片描述

    转换后的Kotlin代码

    class MainActivity : AppCompatActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            val toolbar = findViewById(R.id.toolbar) as Toolbar
            setSupportActionBar(toolbar)
    
            val fab = findViewById(R.id.fab) as FloatingActionButton
            fab.setOnClickListener { view ->
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show()
            }
        }
    
        override fun onCreateOptionsMenu(menu: Menu): Boolean {
            // Inflate the menu; this adds items to the action bar if it is present.
            menuInflater.inflate(R.menu.menu_main, menu)
            return true
        }
    
        override fun onOptionsItemSelected(item: MenuItem): Boolean {
            val id = item.itemId
            if (id == R.id.action_settings) {
                return true
            }
            return super.onOptionsItemSelected(item)
        }
    }
    
    
    object Test {
        private var str: String? = null
    
        @JvmStatic fun main(args: Array<String>) {
            str = "Code4Android"
            println(str)
        }
    }
    

    注意:AS提供的java代码自动转换功能,我们不要轻易使用,更不要转化我们成熟的项目,如果需要就需要我们自己去重构实现。否则会有意向不到的事情等着你,毕竟转换不是那么智能。上面的代码只是让你先简单熟悉下Kotlin代码时什么样子的,接下来我们先去学习一下Kotlin的基本语法。相信很容易上手。

    Hello World!

    我们由一个简单的”Hello World!”输出程序开始。与新建java文件类似,如下图,我们选择Kotlin File/Class.创建一个Kotlin文件。

    这里写图片描述

    package com.learnrecord
    
    /**
     *Created by Code4Android on 2017/4/21.
     */
    
    var str: String = ""
    
    fun main(args: Array<String>) {
        str = "Hello World!"
        println(str)
    }

    上述代码就是简单的输出一个字符串“Hello World”,package 后面跟的是包名,我们看出了和java文件的区别,在包名后面没有以分号“;”结尾。在Kotlin语法中,语句结尾都不在有分号“;”。

    在Kotlin中变量声明有两种类型,val修饰变量是只读变量即只能赋值一次,再次赋值时就会编译错误
    ,如果我们需要多次修改值就需要使用var。在上面的 var str: String = “”中,str是变量名,:String,表明该变量是String类型变量,后面就是赋值语句。我们也可以这样写var str= “”省略了生命变量类型,它可以根据赋的值而自动推断出类型。如果我们使用下面赋值语句str=null,发现null是不能赋值的,这就是Kotlin的特性,如果我们想赋值null,可以修改为 var str:String?=”“。
    fun就是函数生命,而这个main函数就和我们java中的main方法一样,是程序执行的入口。println就是一个打印输出。

    Kotlin声明类型

    在Kotlin中有如下几种Number类型,他们都是继承自Number抽象类。
    Float(32位),Double(64),Int(32),Byte(8),Short(16),Long(64,类型用大写L,如12L),Any(任意类型),数组类型Array 根据传入的泛型数据自动匹配类型,Kotlin还提供了指定类型的Array,如ByteArray,CharArray,ShortArray,IntArray,LongArray,FloatArray,DoubleArray,BooleanArray。在数组类型中都提供了get(index),set(index,value)及iterator()方法供我们使用。

        val iArray: IntArray = intArrayOf(1, 2, 3)
        val sArray: Array<String> = Array<String>(3, { i -> i.toString() })
        val anyArray: Array<Any> = arrayOf(1, "2", 3.0, 4.1f) // 可将类型进行混排放入同一个数组中
        val lArray: LongArray = longArrayOf(1L, 2L, 3L)

    函数

    我们先来实现一个简单的数值求和的函数,通用实现方法如下

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

    传入两个Int型数值,sum是函数名,括号后面的:Int表示该函数返回Int的值,函数体中对两个数字相加并返回。在Kotlin中表达式也可以作为函数体,编译器可以推断出返回类型,可以简化为

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

    为了更好理解表达式可以作为函数体,我们可以创建一个函数获取两个数的最大值,如下:

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

    需要注意的是若if后有多个表达式,如下

        fun max1(a:Int,b:Int)= if (a > b) {
            println(a)
            a
        } else {
            println(b)
            //如果我们将println(b)放到b的下面,运行会返回kotlin.Unit为无类型,返回值总是最后一个表达式的返回值或值
            b
        }
        println(max1(1,3))

    括号中的表达式顺序决定了返回的值及其类型。

    如果我们的方法体仅仅是打印字符串,并不返回值则

        fun printInt(a: Int): Unit {
            println(a)
        }

    Unit就类似我们java中的void,即没有返回值,此时我们可以省略

        fun printInt(a: Int) {
            println(a)
        }

    对于函数体,方法或者类等和java一样也有一些修饰符,如下

    • abstract //抽象类标示
    • final //标示类不可继承,默认属性
    • enum //标示类为枚举
    • open //类可继承,类默认是final的
    • annotation //注解类
    • private //仅在同一个文件中可见
    • protected //同一个文件中或子类可见,不可修饰类
    • public //所有调用的地方都可见
    • internal //同一个模块中可见,若类不加修饰符,则默认为该修饰符,作用域为同一个应用的所有模块,起保护作用,防止模块外被调用。

    操作符

    直接上代码如下

        //操作符  shl 下面对Int和Long
        var a: Int = 4
        var shl: Int = a shl (1)  //Java中的左移运算符 <<
        var shr: Int = a shr (1) //Java中的右移运算符 >>
        var ushr: Int = a ushr (3) //无符号右移,高位补0 >>>
        var and: Int = 2 and (4)   //按位与操作 &
        var or: Int = 2 or (4) //按位或操作 |
        var xor: Int = 2 xor (6)  //按位异或操作 ^
        print("\nshl:" + shl + "\nshr:" + shr + " \nushr:" + ushr + "\nand:" + and + "\nor:" + or + "\nxor:" + xor)

    输出信息为

    shl:8
    shr:2 
    ushr:0
    and0
    or:6
    xor:4

    在上面的部分操作符可达到逻辑操作符, 当我们使用Boolean时,or() 相当于 ||,and() 相当于 &&, xor() 当操作符两边相反时为true,否则为false ,not()时取反。

    数组遍历及控制语句

    遍历数组

        fun forLoop(array: Array<String>) {
            //第一种方式直接输出字符(类似java foreach)
            for (str in array) {
                println(str)
            }
            //Array提供了forEach函数
            array.forEach {
              println(it)
             }
            //array.indices是数组索引
            for (i in array.indices) {
                println(array[i])
            }
            //(类似javafor(int i=0;i<arry.length;i++))
            var i = 0
            while (i < array.size) {
                println(array[i++])
            }
        }

    使用when判断类型

    fun whenFun(obj: Any) {
            when (obj) {
                25 -> println("25")
                "Kotlin" -> println("Kotlin")
                !is String -> println("Not String")
                is Long -> println("Number is Long")
                else -> println("Nothing")
            }
        }

    is 和java中instanceof是一个作用判断是否为某个类型。!is即判断不是某个类型。

    //@定义label,一般用在内层循环中跳到外层循环:i in 0..2等价于java中 for(int i=0;i<=2;i++)效果
        loop@ for (i in 0..2) {
            for (j in 0..3) {
                println("i:" + i + "  j:" + j)
                if (j == 2)
                //continue@loop//跳到外层循环,继续往下执行
                    break@loop  //跳到外层循环label处,跳出改层循环
            }
        }

    倒序输出是downTo

        //倒序输出5 4 3 2 1 0
        for (i in 5 downTo 0) {
            println(i)
        }
        //设置输出数据步长
         for (i in 1..5 step 3) print(i) // 输出 14
         //step和downTo混合使用
         for (i in 5 downTo 1 step 3) print(i) //输出52

    类与枚举

    /*** constructor:构造函数
     * constructor无修饰符(如:private)时,constructor可以省略:
     * 当是无参构造函数时,整个构造函数部分也可以省略,省略的构造函数默认是public的
     * 由于primary constructor不能包含任何代码,因此使用 init 代码块对其初始化,
     * 同时可以在初始化代码块中使用构造函数的参数
     */
    open class People private constructor(var id: String, var name: String) {
        //可以类中初始化属性:
        var customName = name.toUpperCase() //初始化属性
        //次构造函数,使用constructor前缀声明,且必须调用primary constructor,使用this关键字
        constructor( id: String, name: String, age: Int) : this(id, name) {
            println("constructor")
        }
        init {
            println("初始化操作,可使用constructor参数")
        }
        //需要open修饰,子类才可以
        open fun study() {
            print("study")
        }
        //People前的冒号":"是继承的意思,实现类接口的时候也是冒号
    class Student(id: String, name: String) : People(id, name) {
        var test: Number = 3
        private var name1: String?
            get() {
                return name1
            }
            set(value) {
                name1 = value
            }
      //override修饰的方法,默认是可以被继承的。若希望不被继承,可以使用 final 关键词修饰
        override fun study() {
            super.study()
        }
    }
    
    }

    数据类用来保存数据,类似于POJO类,使用data关键词进行定义,编译器默认会为数据类生成以下四个方法

    • equals()
    • hashCode()
    • toString()
    • copy()
      通过数据类你会看到Kotlin的简洁性,我们创建一个Staff类,有String类型的name,position和泛型T(使用泛型仅仅是为了在Kotlin中接触以下泛型)
      java实现代码:
    public class StaffJ<T> {
        private String name;
        private String position;
        private T age;
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public String getPosition() {
            return position;
        }
    
        public void setPosition(String position) {
            this.position = position;
        }
        public T getAge() {
            return age;
        }
        public void setAge(T age) {
            this.age = age;
        }
    }
    

    Kotlin数据类:

    data class Staff<T>(var name: String,  val position: String,var age:T)

    通过对比我们就看出了优点了,一行代码就实现了,具体使用

     var staff = Staff("code4Android","Android工程师","22")  //实例化对象

    要获取某个属性如获取名字staff.name,赋值就是staff.name=”code4Android2”,既然说了这样可以赋值,但是动手的小伙伴说为什么我敲的报错啊,如下

        staff.position="前端"

    编译报错了,在前面我们说过val修饰的属性只能赋值一次,那在这里val修饰的属性我们是不能再次赋值的。

    fun main(arg:Array<String>){
        var staff = Staff("code4Android","Android工程师","22")  //实例化对象
        staff.name="code4Android2"
        var staff1=staff.copy()
        //使用copy的时候可以指定默认值,可以指定任意个用逗号","隔开
        var staff2=staff.copy(name="ccc",position = "kotlin")
        println("name:${staff2.name} position:${staff2.position} age ${staff2.age}")
        //staff.position="Kotiln" //val不能再次赋值
        var anotherStaff= Staff("code4Android","Android工程师",22) //实例化对象
    
        println("staff toString(): ${staff.toString()} anotherStaff toString(): ${anotherStaff.toString()}")
        println("staff hashCode(): ${staff.hashCode()} anotherStaff hashCode(): ${anotherStaff.hashCode()}")
        println("staff is equals another staff ? ${staff.equals(anotherStaff)}")
    }

    上面使用了字符模板,在Kotlin中有两种字符模板形式,$<变量>、${<变量>}

        var name:String="Code4Android"
        println("我的名字叫$name")
        println("我的名字叫${name}")
    /**
     * java允许使用匿名内部类;kotlin也有类似的概念,称为对象表达式-object expressions
     */
    open class KeyBord{
        open fun onKeyEvent(code:Int):Unit = Unit
    }
    
    /**匿名内部类**/
    var key=object :KeyBord(){
        override open fun onKeyEvent(code:Int):Unit = Unit
    }

    枚举

    enum class Color{
        RED,BLACK,BLUE,GREEN,WHITE
    }
    fun display(){
        var color:Color=Color.BLACK
        Color.valueOf("BLACK") // 转换指定name为枚举值,若未匹配成功,会抛出IllegalArgumentException
        Color.values() //已数组的形式,返回枚举值
        println(color.name)////获取枚举名称
        println(color.ordinal)//获取枚举值在所有枚举数组中定义的顺序,0开始
    }

    在Kotlin中枚举还支持方法。

    扩展

    /**
     * fun receiverType.functionName(params){
     *body
     *}
     * receiverType : 待扩展的类名
     * .            :修饰符为扩展符
     * functionName :为自定义的扩展函数名,
     * params       :为自定义的扩展函数参数,可为空
     * 扩展函数作用域,受函数的visibility modifiers影响
     * 扩展函数并没有对原类做修改,而是为被扩展类的对象添加新的函数。
     * 有一条规则,若扩展函数和类原有函数一致,则使用该函数时,会优先使用类本身的函数。
     */
    class Employee(var name: String) {
        fun print() {
            println("Employee")
        }
    }
    
    //扩展函数
    fun Employee.println() {
        print("println:Employee name is $name")
    }
    
    
    /**
     * 可以扩展一个空对象
     */
    fun Any?.toString1(): String {
        if (this == null)
            return "toString1:null"
        else {
            return "toString1" + toString()
        }
    }
    
    /**
     * 扩展属性
     * 由于扩展属性实际上不会向类添加新的成员,
     * 因此无法让一个扩展属性拥有一个后端域变量. 所以,对于扩展属性不允许存在初始化器.
     * 扩展属性的行为只能通过明确给定的取值方法与设值方法来定义,也就意味着扩展属性只
     * 能被声明为val而不能被声明为var.如果强制声明为var,即使进行了初始化,
     * 在运行也会报异常错误,提示该属性没有后端域变量。
     */
    val Employee.lastName: String
        get() {
            return "get:"+name
        }

    使用

    fun main(arg: Array<String>) {
        var employee = Employee("Code4Android")
        employee.print()
        employee.println()
        println(employee.toString1())
        println(employee.lastName)
    }

    代理

    **
     * 被代理接口
     */
    interface Base {
        fun display()
    }
    
    /**
     * 被代理类
     */
    open class BaseImpl : Base {
        override fun display() = print("just display me.")
    }
    
    /**
     * 代理类,使用:以及关键词by进行声明
     * 许代理属性和其他继承属性共用
     * class Drived(base: Base) : Base by base,BaseImpl()
     */
    class Drived(base: Base) : Base by base
    
    //使用
    fun show() {
        var b = BaseImpl()
        var drived = Drived(b)
        drived.display()
    
    }
    
    **
     * 代理类型:
     * 懒加载:Lazy
     * 观察者:Delegates.observable()
     * 非空属性:Delegates.notNull<>()
     */
    class Water {
    
        public var weight:Int by Delegates.notNull()
        /**
         * 代理属性
         */
        public val name: String by lazy {
            println("Lazy.......")
            "Code4Android"
        }
        public var value: String by Delegates.observable("init value") {
            d, old, new ->
            println("$d-->$old->$new")
        }
    }
    
    fun main(arg: Array<String>) {
        show()
        var water = Water()
        println(water.name)
        println(water.name)
        water.value = "11111"
        water.value = "22222"
        water.value = "33333"
        println(water.value)
        println(water.value)
        //必须先赋值否则IllegalStateException: Property weight should be initialized before get.
        water.weight=2
        print(water.weight)
    }

    操作符::

    val String.lastChar: Char
        get() = this[this.length - 1]
    
    class A(val p: Int)
       //1,反射得到运行时的类引用:
        val c = Student::class
        //2,函数引用
        fun isOdd(x: Int) = x % 2 != 0
        val numbers = listOf(1, 2, 3)
        println(numbers.filter(::isOdd)) 
    
        //3,属性引用(在此引用main函数主体外声明的变量)
        println(::x.get())
        ::x.set(2)
        println(x)
        //4,::x 表达式评估为 KProperty<Int> 类型的属性,它允许我们使用 get() 读它的值或者使用名字取回它的属性
        val prop = A::p
        println(prop.get(A(1))) 
    
        //5,对于扩展属性
        println(String::lastChar.get("abc")) 
    
        //6,与 java 反射调用
        println(A::p.javaClass),
        var f: Array<Field> = A::p.javaClass.declaredFields

    伴生对象

    伴生对象(companion object )类似于java中的静态关键字static。在Kotlin没有这个关键字,而是伴生对象,具体用法

    open class People constructor(var id: String,  var name: String){
        //可以类中初始化属性:
        var customName = name.toUpperCase() //初始化属性
    
        //使用constructor前缀声明,且必须调用primary constructor,使用this关键字
        constructor( id: String, name: String, age: Int) : this(id, name) {
            println("constructor")
        }
    
        init {
            println("初始化操作,可使用constructor参数")
        }
       //,Kotlin的class并不支持static变量,所以需要使用companion object来声明static变量,
       // 其实这个platformStatic变量也不是真正的static变量,而是一个伴生对象,
        companion object {
            val ID = 1
        }
    }

    使用的话直接People.ID。

    单例模式

    在Kotlin中使用object修饰类的时候,。该类是单例对象。

    /**
     * 使用object定义类,该类的实例即为单例,访问单例直接使用类名,不能通过构造函数进行访问,不允许有构造函数
     * Place.doSomething() // 访问单例对象
     */
    object Singleton {
        fun doSomething() {
            println("doSomething")
        }
    }
    
    
    /**
     * 实例化的时候,单例是懒加载,当使用的时候才去加载;而对象表达式是在初始化的地方去加载。
     *
     * 当在类内部使用 object 关键词定义对象时,允许直接通过外部类的类名访问内部对象进而访问其相关属性和方法,相当于静态变量
     * 可以使用companion修饰单例,则访问其属性或方法时,允许省略单例名
     * MyClass.doSomething() // 访问内部单例对象方法
     */
    class MyClass {
        companion object Singleton {
            fun doSomething() {
                println("doSomething")
            }
        }
    }

    好了,今天就介绍到这里,文中若有错误欢迎指出,Have a wonderful day.

    Kotlin英文官网
    Kotlin学习中文官网
    在线学习示例

    展开全文
  • 最近在学习Kotlin语言,今天依旧是写Kotlin了,上一篇中学的是Kotlin中的表达式相关的内容——《Kotlin真香系列第三弹:表达式》,今天咱们继续学习,来看一下Kotlin中的函数进阶相关的知识点!废话不多说,开干!

    目录

    写在前面

    一、高阶函数

    1.1、高阶函数的概念

    1.2、高阶函数的调用

    二、内联函数

    2.1、内联函数的概念

    2.2、定义内联函数

    2.3、内联高阶函数的return

    2.4、内联属性

    2.5、内联函数的限制

    三、常用的高阶函数

    四、集合变换与序列

    4.1、集合的映射操作

    4.2、集合的聚合操作

    五、SAM转换(Single Abstract Method)

    六、综合案例

    6.1、统计字符个数

    6.2、HTML DSL


    写在前面

    最近南京城天天下大雨,真不知道啥时候能结束,由于天气的原因也好久都没出门逛逛了,只能窝在家里撸代码了!

    最近在学习Kotlin语言,今天依旧是写Kotlin了,上一篇中学的是Kotlin中的表达式相关的内容——《Kotlin真香系列第三弹:表达式》,今天咱们继续学习,来看一下Kotlin中的函数进阶相关的知识点!废话不多说,开干!

    一、高阶函数

    1.1、高阶函数的概念

    前面的文章里面咱们讲了函数、匿名函数、Lambda表达式等内容,这些内容都是为了马上要说的这个高阶函数在做铺垫,所以今天高阶函数终于是千呼万唤始出来啊,那么高阶函数究竟是什么呢?它其实就是参数类型包含函数,或者是返回了一个函数类型,这样的函数就叫做高阶函数。

    这个概念还是很简单的,下面来看几个例子吧:

    比如之前见到的数组迭代用的forEach,传入的是一个Lambda表达式,其实它就是接收了一个函数,这个函数的参数是Int,返回值是Unit,在调用的时候直接调它的invoke,就是“()”:

    再比如有一个比较常见的map,它跟forEach不一样的是forEach迭代的是集合,同时把每个元素拿出来挨个执行了某个操作,map也是迭代了这个集合,把每个元素拿出来,但是它把每个元素又转成了另外的元素,得到了一个新的List,所以map实际上是从一个类型到另一个类型的映射:

    1.2、高阶函数的调用

    下图中的intArray在调用forEach的时候,没有传Lambda表达式,直接是括号里面传了一个函数引用进去,函数引用就是一个普通的函数作为一个值传了进去,这里的forEach接收的函数类型是该函数接收一个Int返回Unit,而println中有一个函数重载就是这样的,所以可以直接使用它的引用传进去:

    再来看下面这张图:

    之前在讲例子的时候不知道大家还记得吗,调用的时候并没有写括号,这是为什么呢?因为接收的参数就一个,所以可以把小括号直接拿到外面去,如果不是一个的话,只要见到高阶函数里面的参数类型是最后一个参数,就可以把它从小括号里面移出来:

    又因为它提到外面以后这个括号里面就是啥都没有的,所以按照Kotlin的套路可以咋办?对,可以直接省略,如下图所示,只有一个参数的Lambda表达式,它的形参可以直接使用it:

    下面来举个栗子,实际写段代码感受一下它的用法:

    fun main() {
        //调用cost,直接cost{...}就OK
        cost {
            //调用fibonacci()函数得到了一个函数,类型为()->Long
            val fibonacciNext = fibonacci()
            //[0,10]一共会输出11个值
            for (i in 0..10) {
                //然后调用fibonacciNext.invoke()即得到下一个斐波那契数值
                println(fibonacciNext())
            }
        }
    
        // region +折叠,折叠的快捷键为ctrl/commond加上减号,对应的展开就是加号
        //forEach的用法之前都说过了,几种不同的遍历方式,文章中已经介绍过了
        /*val intArray = IntArray(5) { it + 1 }
        intArray.forEach {
            println(it)
        }
    
        intArray.forEach(::println)
    
        intArray.forEach {
            println("Hello $it")
        }*/
        //endregion
    }
    
    //cost这个高阶函数接收一个block函数类型,block函数是0个入参返回Unit的类型
    //这个函数的作用就是计算block()执行的耗时时间
    fun cost(block: () -> Unit) {
        val start = System.currentTimeMillis()
        block()
        println("耗时时间:${System.currentTimeMillis() - start}ms")
    }
    
    //fibonacci这个高阶函数是返回值为函数类型,该函数类型接收0个参数返回Long类型
    fun fibonacci(): () -> Long {
        var first = 0L
        var second = 1L
        //return了一个lambda表达式,即作为返回值进行返回了,lambda表达式就是无需入参的
        return {
            val next = first + second
            val current = first
            first = second
            second = next
            //current:Long类型,它就是斐波那契数列的下一个值
            current
        }
    }

    执行结果如下:

    二、内联函数

    2.1、内联函数的概念

    内联函数这个概念其实很早就出现了,只不过在Java里面没有明确的提出而已,它究竟是个什么概念呢?来看下面这张图,这里面有个forEach的调用,打印了hello1...,然后再来看下这个forEach在源码中是如何定义的?它是IntArray.forEach(),然后使用inline关键字进行了标记,这其实就是一个内联函数:

    这个函数在调用的时候是个什么情况呢?看下图:

    其实就是把forEach里面定义的这个函数直接内联到真正的调用处了,也就是我们自己写的是ints.forEach{...}这样的,然后编译器实际上最后帮我们生成的是一个for循环,就是上面箭头指向的那个函数,这样做的好处是减少了函数的调用,在性能开销上起到了优化作用。

    2.2、定义内联函数

    一个普通函数想定义成内联函数该怎么定义呢?其实就是加上一个inline关键字就OK了:

    但是如果你随便一个什么函数都加上inline的话能不能真正起到性能优化的作用呢?也不一定。

    实际上高阶函数更适合作为内联函数,为什么呢?来看下图,先把它看做没有inline关键字标记的时候,只是一个高阶函数:

    每次调用这个cost是一次函数调用,在调用的时候会传进来一个lambda表达式,这个lambda表达式实际上是匿名函数的语法糖,也就是说这玩意本身也会是一个函数,所以当你调用一次cost的时候还得创建一个block,这个block在原生的调用处可能就是一个普通的println("hello"),如下图所示:

    这样的话相当于我调用一次cost本来就是想计算一下println的时间,结果发现调用cost和创建lambda表达式消耗的时间比println消耗的时间多了不少,这就有点不太好了吧,所以咱们把cost标记为inline之后,真正调用cost{println("")}的时候就相当于下面这样了,说的更直白点就是加上inline之后,就相当于把下图中的代码搬到了调用cost的地方:

    这样一来cost函数调用的开销没有了,创建lambda表达式的开销也没有了,可以看出内联函数本身还是比较厉害的。

    2.3、内联高阶函数的return

    ①、local return

    首先来看下面的代码,很多时候我们可能会有这样的需求,比如以前写for循环的时候,判断当你循环到某一个值的时候终止循环,像下面这种高阶函数你就没办法写continue或者break了,官方还没有支持这种特性,那应该怎么办呢?这段代码里面写了个@forEach,这是啥玩意啊,它实际上是Kotlin里面的一个标签,这个标签相当于Kotlin给这个lambda表达式加了一个标签叫forEach,这个forEach跟前面调用的高阶函数的名字是一模一样的,所以它表示的是我在等于3的时候的这一次调用return了,注意只是这一次:

    所以它等价与普通for循环中的continue:

    这种直接在Lambda表达式中直接return的写法又叫local return。

    ②、non-local return

    比如下图中的例子,定义了一个nonLocalReturn的函数,它接收一个lambda表达式作为参数,并在里面调用了block,如果在真正调用nonLocalReturn函数的时候,在lambda里面直接return了,那这个return的含义是什么呢?

    其实它并不是退出这个lambda表达式,而是直接退出这个函数调用所在的外部函数,比如下面图中这种情况,就是直接从main()函数中返回:

    但是不是所有的情况都有non-local return的,比如下面这种情况:

    因为Runnable中调用block的话编译器无法判断你究竟是什么时候调用的,也就是说在block中直接return的话其实return的应该是run,为了解决这个问题,需要明确告诉编译器,这个block里面要禁止non-local return,直接加一个关键字crossinline关键字:

    或者更彻底的使用noinline关键字,禁止函数参数被内联:

    2.4、内联属性

    属性在Kotlin中实际上就是field+getter+setter,当然如果它是在没有backing-field的情况下,那它就是一个函数,即getter+setter,这种情况就可以被内联。举个栗子如下图所示,比如:这里定义了一个pocket,一个money,然后对money的getter和setter进行内联,内联的方式也很简单,前面加上inline关键字,那这样一来会有什么效果呢?现在给money赋值money=5.0,然后将Kotlin编译完成的代码反编译为Java,就会发现代码变成了setPocket,那不就是左边的pocket=value吗,然后你就会发现,内联了之后实际上就没有money啥事了,直接跑到pocket这里访问了,这就是内联属性,跟上面说的内联函数的逻辑是一样的,就是把对应的函数里面的内容编译到调用处,真正使用的过程中运行时其实是没有money的,只有pocket:

    2.5、内联函数的限制

    • public/protected的内联方法只能访问对应类的public成员
    • 内联函数的内联函数参数不能被存储(赋值给变量)
    • 内联函数的内联函数参数只能传递给其他内联函数参数

    上面这几条看着有点绕哈,稳住,别晕,虽然我已经晕了,哈哈!😂

    说的通俗易懂点就是者三句话:public只能访问public,内联只能访问内联,参数不能被存!

    三、常用的高阶函数

    上图中一共列出了5个函数,前面四个其实是功能差不多的,这里按照它们返回值的类型分为了两组:

    第一组是let和run,返回的是r,这个r是高阶函数参数lambda表达式自己的返回值;

    第二组also和apply,它们的返回值就是这个receiver,它不管lambda表达式里面究竟返回的是什么;

    每一组里面的这两个函数的区别:比如let,receiver这个x作为参数传给lambda表达式R,而run是把this作为receiver传给lambda表达式,所以在这两个大括号里面想要访问到外面的receiver X,一个是用参数的形式,一个是用this,同样的also和apply也是一样的道理,所以这两组只有返回值结果不一样,其它的地方都是一样的模式。

    use就是在各种防止资源泄露的地方可以很轻松的帮助我们去关闭掉Closeable。

    这五个函数比较好用的就是let、also、use,不推荐使用receiver的方式,很容易造成receiver嵌套。

    下面举个栗子来看一下这几个常用的高阶函数的用法:

    class Person(var name:String,var age:Int)
    
    fun main() {
        val person = Person("Jarchie",18)
    
        //let和run都可以直接传递带参数的函数引用
        //public inline fun <T, R> T.let(block: (T) -> R): R {}
        //let里面接收的block类型是(T),在括号里面,以参数的形式传递
        person.let{
            println("let--->${it.name}")
        }
    
        //public inline fun <T, R> T.run(block: T.() -> R): R {}
        //run里面接收的block的类型是T.(),说明我这个函数有个receiver,实际上这两个类型是一样的
        person.run {
            println("run--->${age}")
        }
    
        //also带了个返回值,可以将person赋值给person2,并且可以修改它的成员
        val person2 = person.also {
            it.name = "jianqi"
        }
        println("also--->${person2.name}")
    
        //跟also类似,只不过这里将it改为了this
        val person3 = person.apply {
            age = 28
        }
        println("apply--->${person3.age}")
    
        //打开文件,使用一系列扩展方法非常方便,语法简洁,use里面做了一系列异常处理及最后的关闭
        File("gradle.properties").inputStream().reader().buffered()
            .use {
                println(it.readLines())
            }
    }

    执行结果如下:

    四、集合变换与序列

    4.1、集合的映射操作

    说到集合的话,咱们最常见的使用方式就是对它进行遍历,不管是在Java中还是在Kotlin中,对于集合的遍历都有很多不同的方式,下面就一起来看几种,左侧是Java的方式,右侧是Kotlin的方式:

    首先,最常见的for循环方式:

    for each...:

    forEach函数(高阶函数,Java8之后Java也是支持的):

    但是需要注意的是:这种方式跟传统的for循环有很大的不同,在lambda表达式里面不能break或者continue:

    那么这样一来,如果确实有continue或者break的需求,那该如何解决呢?其实啊,这个是有专用的方式来解决类似的需求,接下来就会说到这些。

    集合的映射操作举例

    一旦用到高阶函数,它就并不像传统的命令式的方式去调用代码,而是经过一系列连续的调用后变换形成一个完整链条,看上去像是一个水管一样,数据就是水流。如果只看图中这些函数名,你是不是感觉这个东西有点像rxjava里面的Observable,它也有filter、map、flatMap这些操作符,嗯的确挺像的,接下来就来具体的看一下图中列出的这几种操作具体的含义:

    ①、filter变换

    图中首先定义了一个集合[1,2,3,4],然后进行filter操作,过滤条件是e%2=0,这个操作需要依赖于集合的遍历,所有的变换都要依赖于对集合元素的遍历,遍历的过程中如果元素为偶数,就把它留下,最后形成一个新的集合[2,4],这就是filter的过程,实质上就是由一个集合到另一个集合的变换,中间使用了一个函数。

    具体写法如下,左侧是Java,右侧是Kotlin:

    Java中是从Java8开始支持了stream,把集合转成了一个流,数据流经过filter的时候进行判断如果是偶数就保留,否则丢弃。在Kotlin中就不需要stream了,它是对集合进行扩展,支持了filter这个高阶函数,写起来很简洁。那有人该问了,Kotlin里面有没有在操作上类似Java里面stream的这种写法呢?答案是有的,序列Sequence:

    这两种写法有什么区别呢,集合和集合转换成的序列二者之间的差异主要在于它们的操作是饿汉式的还是懒汉式的,这个放在下面说map的时候再去介绍吧。

    ②、map变换

    map就是映射,而且还是一一映射,同样的还是这个集合[1,2,3,4],然后映射条件为y=x*2+1,x就是前面的这个集合里面的元素1,2,3,4,y就是根据条件生成的结果3,5,7,9,这样就生成了一个新的序列,这就是map,中间这个条件实际上就是一个高阶函数接收的lambda表达式。

    具体写法如下,左侧Java,右侧Kotlin:

    在Java中同样是先把集合转成stream,然后传一个lambda表达式进去,在Kotlin里面集合直接支持map这个扩展,也是接收一个lambda表达式,同样的,也是可以把它转成序列:

    OK,说了map之后,那现在咱们就来看一下这个map跟前面原生的集合到底有啥区别呢?来看下面的代码:

    为了能够更加直观的理解Java中的流和Kotlin中的懒序列,这里分别放了Java的代码和Kotlin的代码,Java中的stream你就可以理解成水流,这个forEach就好比是水龙头,代码中调用了forEach之后就好比是打开了这个水龙头,水就可以从上往下流,如果不调用forEach,那上面这个filter和map就好比是堵在了水管里面出不来了,理论上来说应该是不会有任何的输出。这里触发了forEach之后,代码应该如何执行呢?先来看看执行的结果吧:

    咦?是不是跟你想的不太一样啊,它这个执行结果就跟它的名字一样就是个水流,首先forEach的时候相当于把阀门打开了,然后水开始流了,也就是咱们这里集合的数据1开始过来了,然后到filter这里一看不符合条件,直接下一个,2过来符合条件,然后到map里面,返回了5,接着又到了forEach这里,然后3和4依次都是这个流程,这就是Java中的stream,对应的就是Kotlin中的懒序列,它们的逻辑是一样的,其实写法也挺像的。

    那如果把asSequence()去掉,直接用list.filter().map().forEach()这个结果会有什么变化呢?

    对于list来说,其实就是饿汉式的,一旦filter的话就马上会遍历这个list,然后得到filter变换之后的结果,然后调用map的时候也是立即进行映射,映射出的结果到forEach这里输出,所以这段代码执行的结果就是下图中这个样子了:

    上面说了调用forEach相当于是开了阀门,才会执行,不调用就不会执行,那咱们来实验一下是不是这样呢?把代码改一下:

    然后运行一下看看结果:如下图所示,果然没有任何输出

    ③、flatMap变换

    说完了map再来看一下flatMap,这个可能有点难以理解,但是咱们还是老规矩通过画图看看能不能容易理解一点:

    上面说的map是一一映射,而flatMap按字面意思(flat嘛就是平的)是先把集合给打平了,完了之后执行某一个操作,使得每一个元素都映射成为一个集合,这一点和map不同,map是将集合中的每一个元素映射成为另外的元素,而flatMap是将元素映射成为集合,最后再把每个元素映射成的集合做一个拼接,就得到了一个新的集合。上图中IntArray(e){it},e就是集合中的每一个元素也就是要构造的集合的长度1,2,3,而it则是构造Int数组的下标index,所以得到的集合分别是[0]、[0,1]、[0,1,2],最后将这三个集合再进行一次拼接就得到了最后的结果集合[0,0,1,0,1,2]。

    下面来看一下具体的代码实现:

    Java的stream也可以flatMap,如果想要得到上面的集合的话,它的写法就比较麻烦,因为它的数组构造没有那么方便。而Kotlin写起来就相当简单了,直接就是0 until it就OK了,得到的是一个range,这个range本身源码中的实现是继承自一个iterable。关于变换这里就简单介绍了这三种最基础的,其实还有很多比如zip变换等等,刚接触这些可能会有点晕,实际多到项目中写写体会一下就会感觉好很多了。

    4.2、集合的聚合操作

    聚合操作就是需要把所有的元素拿过来进行运算了,常见的聚合操作如下图所示:

    这里就只介绍fold操作了,如果把fold搞清楚了,那么reduce自然也就清楚了,它就是fold的一个简化版,下面看一下fold的逻辑:

    fold:顾名思义就是把每个元素做一个折叠操作,上图中原始集合是[1,2,3],初始化的值为"",然后拿这个空字符串跟第一个元素1进行拼接(案例中是拼接即加法操作,实际可能是其它操作)得到了字符串"1",这里注意折叠的时候有两个东西是较为关键的,第一就是初始化的值,第二就是具体执行的操作,接着下一次拼接时就是由"1"去和元素2进行拼接,依次类推,最后得到一个值是"123",注意fold的结果是一个值,和初始化值的类型一致。

    reduce:没有初始化值,只有具体的操作,结果类型和元素类型一致。

    fold的具体写法如下:

    list.fold里面传入了初始化的值为new了一个StringBuilder,这个操作每次的acc的值就是上一次拼接的结果,i就是遍历list中的元素,所以这样就得到了最终的结果。

    下面通过一段代码来对上面说的这些内容做一个练习:

    fun main() {
        //原始集合
        val list = listOf(1, 2, 3, 4)
    
        //filter变换
        list.filter {
            it % 2 == 0
        }.joinToString().let(::println)
    
        //map变换
        list.map {
            it * 2 + 1
        }.joinToString().let(::println)
    
        //flatMap变换
        list.flatMap {
            0 until it
        }.joinToString().let(::println)
    
        //sum求和,从左到右相加求和
        //1+2+3+4=10
        list.sum().let {
            println("sum:$it")
        }
    
        //fold变换
        list.fold(StringBuilder()) { acc, i ->
            acc.append(i)
        }.let {
            println("fold:$it")
        }
    
        //reduce变换,将集合元素从左到右依次相乘求积
        //1x2x3x4=24
        list.reduce { acc, i ->
            acc.times(i)
        }.let {
            println("reduce:$it")
        }
    }

    执行结果为:

    五、SAM转换(Single Abstract Method)

    ①、SAM转换基本概念

    前面在说Lambda表达式的时候,不知道有没有提过哈,Java中的Lambda表达式它是没有自己的类型的,它是一个单一方法的接口类型,可以用Lambda表达式赋值给一个Runnable,在Java里面是这样的:

    所以这里的匿名内部类可以用一个Lambda表达式来替代,这就是Java的SAM:

    实际上就相当于是下面这两个东西是等价的,咱们说过哈在Java里面Lambda表达式是没有自己的类型的,必须有一个Java接口(在Java中访问的话,Kotlin接口和Java接口没有任何区别,因此Kotlin接口也是可以的)来接收它,这个接口必须是只有一个方法的,这就是Java中的SAM转换:

    再来看一下Kotlin,Kotlin中匿名内部类的写法是这个样子:

    它的Lambda表达式就是这样的了:

    同样的Runnable可以直接用lambda来替换,也就是说使用lambda表达式赋值给了一个Runnable,Kotlin跟Lambda的本质区别在于Kotlin中的Lambda表达式是有自己的类型的。所以这个转换就是下面这个样子的:

    这个转换的过程是什么样的呢?就是刚才submit的时候入参做了一个转换,转换之后相当于是构造了一个Runnable,并且在里面调用了lambda表达式:

    lambda表达式本身是可以内联的,所以就可以内联成这个样子:

    Lambda表达式跟Runnable是本质不同的两个东西,这个跟Java里面是不一样的,Java里面的Lambda表达式没有自己的类型,而Kotlin是有的,这一点一定要注意,Kotlin实际上是创建了一个Runnable包装了一下Lambda,并非直接转换。

    关于Kotlin的匿名内部类实际上它还有一种简写,就是下面右侧的写法,这两种写法是完全等价的:

    也就是说这里这个Runnable是编译器自动帮我们生成了一个函数,所以这里可以直接调用了,这个函数之前好像是写过,可以回去翻一下看看:

    好,说了这么多,来看一下SAM的具体定义吧:

    ②、SAM转换支持对比

    Java的Lambda是假的,本质就是SAM,Kotlin的Lambda是真的,支持SAM转换(注意只是支持)。

    ③、SAM转换的坑

    这是一个很容易遇到的坑,首先定义了一个EventManager,然后添加一个EventListener,EventListener就是一个Java接口,只有一个方法就是onEvent,对于这种情况add的时候肯定是可以添加一个lambda表达式进去,remove的时候又传了一个lambda表达式进去,那这俩是同一个吗?很显然不是,那你该怎么remove之前的那个呢?这就是个坑了,所以不能这么写啊:

    比较推荐的方式是使用匿名内部类的方式来写,因为这种方式最安全啊,不容易进坑,代码也就是下面这个样子了:

    关于SAM就介绍这么多吧,我理解的也不是太清楚,只能先记个笔记了,后面不对的再回来改吧!

    六、综合案例

    6.1、统计字符个数

    通过这个案例我们再来认识几个新的高阶函数,争取对常见的高阶函数做到活学活用。首先要做的这个案例是来统计字符的个数,要实现的效果如下:

    • 随便给一个文件,输出除空白字符以外的所有字符的个数,输出结果大概就是(a,13),(b,21)...,比如a字符出现了13次,b字符出现了21次,以此类推

    代码如下:

    fun main() {
        //读取根目录下的Kotlin.html文件,读取方法使用readText()直接读取文本,结果为String类型
        File("Kotlin.html").readText()
            .toCharArray()//toCharArray()将字符串转成字符数组
            //filterNot过滤不是xxx的条件,isWhitespace()判断是否为空白字符
            .filterNot { it.isWhitespace() } //也可以直接传函数引用{Char::isWhitespace}
            //groupBy用过数据库的话应该比较了解,意为分组,传入的是前面操作结果List<Char>的元素
            .groupBy { it } //所以这里直接以字符为单位进行分组,到这里得到的类型为Map<Char,List<Char>>
            //然后使用map进行映射,key就是原集合的key,value是原集合value的大小,即上面List<Char>的size
            .map {
                it.key to it.value.size
            }.let {
                println(it)
            }
    }

    执行结果如下:

    然后随便找一个字符来验证一下,发现结果是正确的:

    6.2、HTML DSL

    DSL:领域特定语言

    咱们平时接触的语言都是编程语言,但是它是通用的编程语言,领域特定语言就是在特定的场景下使用的特定的函数接口,那就是DSL了,数据库中的SQL语言就是典型的DSL,还有Android开发人员比较熟悉的Gradle也是DSL,它是基于Groovy的。

    那这个案例要实现什么效果呢?这一部分会通过实现一系列的函数让我们能够写出下面的这种代码,看上去就跟HTML标签一样,它运行之后真的就可以输出一个HTML文件:

    完整代码:

    package com.jarchie.kotlinpractices.advancefunctions
    
    import java.io.File
    
    /**
     * 作者:created by Jarchie
     * 时间:2020/7/24 16:18:42
     * 邮箱:jarchie520@gmail.com
     * 说明:HTML DSL
     */
    
    //定义一个节点的接口
    interface Node {
        //输出字符串
        fun render(): String
    }
    
    class StringNode(val content: String) : Node {
        override fun render(): String {
            return content
        }
    }
    
    //实现一个最基本的节点
    class BlockNode(val name: String) : Node {
    
        //子节点,节点的集合
        val children = ArrayList<Node>()
        //属性,key-value,属性:属性值
        val properties = HashMap<String, Any>()
    
        //各节点拼接,并闭合
        override fun render(): String {
            return """<$name ${properties.map { "${it.key}='${it.value}'" }.joinToString(" ")}>${children.joinToString(
                ""
            ) { it.render() }}</$name>"""
        }
    
        //运算符重载,构建自定义标签
        operator fun String.invoke(block: BlockNode.() -> Unit): BlockNode {
            val node = BlockNode(this)
            node.block()
            this@BlockNode.children += node //添加父节点,this@BlockNode拿到外层的receiver
            return node
        }
    
        //参数为字符串类型的运算符重载
        operator fun String.invoke(value: Any) {
            this@BlockNode.properties[this] = value
        }
    
        //运算符重载,构建标签中的内容
        operator fun String.unaryPlus() {
            this@BlockNode.children += StringNode(this)
        }
    
    }
    
    //html函数,接收一个lambda表达式作为参数,需要在lambda表达式里面初始化子节点
    //所以block里面应该是带有receiver的,BlockNode作为它的父节点
    fun html(block: BlockNode.() -> Unit): BlockNode {
        val html = BlockNode("html") //创建html节点
        html.block() //html传给这个block,内部可以初始化成员
        return html
    }
    
    fun BlockNode.head(block: BlockNode.() -> Unit): BlockNode {
        val head = BlockNode("head")
        head.block() //head也需要调用它的block,可以初始化子节点
        this.children += head //添加到父节点
        return head
    }
    
    //跟head差不多
    fun BlockNode.body(block: BlockNode.() -> Unit): BlockNode {
        val head = BlockNode("body")
        head.block()
        this.children += head
        return head
    }
    
    fun main() {
        val htmlContent = html {
            head {
                "meta" { "charset"("UTF-8") }
            }
            body {
                "div" {
                    "style"(
                        """
                        width: 200px; 
                        height: 200px; 
                        line-height: 200px; 
                        background-color: #8EEBE0;
                        text-align: center
                        """.trimIndent()
                    )
                    "span" {
                        "style"(
                            """
                            color: white;
                            font-family: Microsoft YaHei
                            """.trimIndent()
                        )
                        +"Hello HTML DSL!!"
                    }
                }
            }
        }.render()
    
        File("KotlinDSL.html").writeText(htmlContent)
    }

    执行结果:

    生成KotlinDSL文件

    文件内容为:

    <html>
    <head>
        <meta charset='UTF-8'></meta>
    </head>
    <body>
    <div style='width: 200px; 
    height: 200px; 
    line-height: 200px; 
    background-color: #8EEBE0;
    text-align: center'><span style='color: white;
    font-family: Microsoft YaHei'>Hello HTML DSL!!</span></div>
    </body>
    </html>

    浏览器中打开:

    官方提供了一个HTML DSL的库:https://github.com/Kotlin/kotlinx.html

    A kotlinx.html library provides DSL to build HTML to Writer/Appendable or DOM at JVM and browser (or other JavaScript engine) for better Kotlin programming for Web.

    好了,今天的内容先写到这里吧,头有点懵!下期再会!

    祝:工作顺利!

    展开全文
  • 运算符计算机程序中最小的程序单位成为表达式,每个表达式都可以由两部分组成,即操作数和运算符...Kotlin语言包含了Java语言中的所有运算符的特性,并结合C语言的优点,增加自定义运算符的逻辑。这些运算符之中,主要

    运算符

    计算机程序中最小的程序单位成为表达式,每个表达式都可以由两部分组成,即操作数和运算符。操作数可以是变量、常量、类、数组、方法等,甚至是其他表达式。而运算符则用于支出表达式中单个或者多个操作数参与运算的规则,表达式通过运算之后产生的值依赖于表达式中包含的运算符的优先级和结核性。Kotlin语言包含了Java语言中的所有运算符的特性,并结合C语言的优点,增加自定义运算符的逻辑。这些运算符之中,主要包括有:算数运算符、区间运算符、逻辑运算符、关系运算符、赋值运算符、自增自减运算符等。

    根据操作数的数量来划分,运算符又可以分为一目运算符、双目运算符。
    - 一目运算符用于单一操作对象,又称单目运算符,如:++a、!b、i–等。
    - 双目运算符是中置的,它拥有两个操作数,比如:a+3、a*b

    Kotlin中没有三目运算符

    基础运算符

    基础运算符中包含了我们在编码工程中常用的一系列运算符,使我们编写程序的基本组成部分,了解基础运算符的用法可以尽可能的避免一些语法和逻辑上的基础性错误。

    赋值运算符(=)

    赋值运算a=b,表示等号右边的b初始化或者维护等号左边的a,b可以是变量、常量、字面量或表达式,如:

    var IntA:Int = 5
    val IntB:Int = 10
    
    IntA = 2 + 1;
    IntA = IntB

    在Kotlin语言中还有另一种赋值运算符,叫做算术自反赋值运算符。它是一种由两个普通运算符组成的符合运算符,它包括:“+=”、“-=”、“*=”、“/=”、“%=”。使用方式如下:

    var IntA:Int = 5
    val IntB:Int = 10
    
    IntA += IntB // 作用等于 IntA = IntA + IntB
    IntA -= IntB // 作用等于 IntA = IntA - IntB
    IntA *= IntB // 作用等于 IntA = IntA * IntB
    IntA /= IntB // 作用等于 IntA = IntA / IntB
    IntA %= IntB // 作用等于 IntA = IntA % IntB

    算数运算符

    算术运算符用于数值类型的运算,Kotlin语言支持基本的算术运算:加法“+”、减法“-”、乘法“*”、除法“/”、取余“%”、以及自增自减运算,如:

    var IntA:Int = 5 + 5  // 10
    val IntB:Int = 10 - 2 // 8
    val IntC:Int = 3 * 4  // 12
    val IntD:Int = 10 / 5 // 2
    val IntE:Int = 10 % 3 // 1,除不尽,保留余数
    val IntF:Int = 10 / 6 // 1,除不尽,仅保留整数部分
    
    IntA = IntA / 0 // 报错,除数不能为0

    自增自减运算符(++、–)

    自增和自减运算符也是单目运算符,因为它只有一个操作数。自增运算符 “++” 表示使操作数加1,自减运算符 “–” 表示使操作数减1,其操作数可以使整数和浮点型等数字类型,如:

    var intA : Int = 5
    
    intA++ // 等于 intA = intA + 1
    println("intA = " + intA)  // 输出 intA = 6

    值得注意的是,自增运算符和自减运算符还会分为前置自增、后置自增、前置自减和后置自减,放在操作数前面的是前置,放在操作数后面的是后置运算符。

    后置运算,则为先进性表达式返回,才进行自增、自减运算。前置运算符,则先进行自增、自减运算,在进行表达式返回。如:

    var intIncA: Int = 5
    var intIncB: Int = 5
    var intIncC: Int = 5
    var intIncD: Int = 5
    
    println(++intIncA) // 先自增, 后返回。 输出 :6
    println(--intIncB) // 先自减, 后返回。 输出 :4
    println(intIncC--) // 先返回, 后自减。 输出 :5
    println(intIncD++) // 先返回, 后自增。 输出 :5

    字符串连接符(+)

    两个字符串可以连接在一起成为一个新字符串,这种操作被成为字符串连接,在Kotlin语言中连接字符串可以用 “+”。如:

    "hello " + "world" // 等于 "hello world"

    字符串连接操作两边都是字符串,而很多情况下我们使用连接符仅有一侧是字符串,另一侧是其他类型。这个时候,系统则会自动调用toString方法转化为字符串,进行拼接。这个时候则调用则是String重载的plus方法,后面我们会具体介绍运算符重载,Kotlin中String的源码如下:

    image

    故此,进行字符串与其他类型拼接我们都将String类型的操作符至于连接符 “+” 左侧。

    var intA : Int = 1
    var StringA : String = "String Head "
    
    println(intA + StringA) // 报错,调用的是Int.plus方法
    println(StringA + intA) // 输入内容:String Head 1

    关系运算符

    关系运算符是指:使用关系运算符对两个操作数或表达式进行运算,产生的结果为真或者假。

    运算符 名称 示例 功能 缩写
    < 小于 a
    println(10 == 10) // true
    println(1 != 5)   // true
    println(1 < 5)    // true
    println(1 > 5)    // false
    println(4 <= 5)   // true
    println(4 >= 5)   // false

    注意:
    1. 关系运算符的优先级低于算术运算符。
    2. 关系运算符的优先级高于赋值运算符。

    区间运算符(a..b)

    区间运算符,顾名思义就是可以用来表示两个操作数之间的范围集合。a..b也就我们平时所说的,从a到b所有的数字集合。在Kotlin语言之中,有两种区间运算符:闭区间运算符和开区间运算符。
    - 闭区间运算符 : “a..b”从a到b范围内所有的值,包括a和b。
    - 半闭区间运算符 : “a until b”从a到b范围内所有的值,包括a和不包括b。

    区间表达式由具有操作符形式 “..”rangeTo 辅以 in!in 而得。区间是为任何可比较类型定义的,但对于整型原生类型,它有一个优化的实现。以下是使用区间的一些示例:

    for (i in 1..10) { // 等同于 1 <= i && i <= 10
        println(i)
    }
    
    for (i in 1.rangeTo(10)) {  // 等同于 1 <= i && i <= 10
        println(i)
    }
    
    for (i in 'a'..'z') { // 等同于 'a' <= i && i <= 'z'
        println(i)
    }

    in 代表在区间内,!in表示不在。

    整型区间有一个额外的特性:它们可以迭代。 Kotlin编译器负责将其转换为类似 Java 的基于索引的 for循环而无额外开销。

    for (i in 1..4) print(i) // 输出“1234”
    
    for (i in 4..1) print(i) // 什么都不输出

    运行上述例子,我们可以发现如果只写 “..”,这个区间值只是顺序。如果你想倒序迭代数字呢?也很简单。你可以使用标准库中定义的 downTo 方法:

    for (i in 4 downTo 1) print(i) // 输出“4321”

    能否以不等于 1 的任意步长迭代数字? 当然没问题, 使用step方法就可以做到:

    for (i in 1..4 step 2) print(i) // 输出“13”
    
    for (i in 4 downTo 1 step 2) print(i) // 输出“42”

    那么我们如何创建一个半闭区间呢?可以使用 until方法 :

    for (i in 1 until 10) {   // i in [1, 10) 排除了 10
         println(i)
    }

    逻辑运算符

    逻辑运算使用等式表示判断,把推理看做等式运算,这种变换的有效性不依赖人们对符号的解释,只依赖符号组合的变换。Kotlin语言和Java一样,支持三个标准逻辑运算符,逻辑与、逻辑或、逻辑非。

    • && : 逻辑与,可以理解为并且的意思.
    • || : 逻辑或,可以理解为或者的意思,也就是条件可以二取一
    • : 逻辑非,取反

    逻辑运算表达式中,操作数值的组合不同,整个表达式的值也不同。在这里我们给出一个逻辑运算的值搭配总结表:

    a b a&&b a||b !a
    false false false false true
    true false false true false
    false true false true true
    true true true true false

    空安全操作符(?、!!)

    在Java开发的过程中遇到的最多的异常就是NullPointException(NPE),空异常的问题很多是不可预见的。一直以来,NullPointException空指针异常在开发中是最低级也最致命的问题。我们往往需要进行各种null的判断以试图去避免NPE的发生。在Kotlin语言中一切皆对象,出现NPE则是致命性的问题。所提,在Kotlin语言中提出了预先判空处理,为此引用了两个操作符:判空操作符“?”强校验“!!”操作符

    预定义,是否能容纳空(?)

    在Kotlin中,类型系统区分一个引用可以容纳null,还是不能容纳null。 Kotlin中绝大部分的对象都是不能够容纳null的,例如,基础类型中的常规变量不能容纳null:

    var a: String = "abc"
    a = null // 编译错误

    如果要允许为null,我们可以声明一个变量为可空字符串,需要在定义的类型后面紧跟一个问号 “?”,如上述例子则写作* String?*

    var b: String? = "abc"
    b = null // 这样编译没问题

    对于无法容纳null的类型,我们可以放心的对它的属性进行调用。

    var a: String = "abc"
    var aLength = a.length // 放心调用,a肯定不会为null

    同样的操作,我们则不能够对b字符串进行操作,对于可能为空的类型进行操作,我们就必须判空。

    判空(?)

    在Kotlin语言中判断一个对象是否为空有两种方式,第一种就是如同Java语言一样,使用if-else进行判空;另一中就还是使用操作符 “?” 进行判断。

    // 在Java语言中我们使用的判空方法。
    if (b != null && b.length > 0) {
        println("String of length ${b.length}")
    } else {
        println("Empty string")
    }
    
    // Kotlin,可空类型的判断
    println("String of length ${b?.length}")

    咋一看,差别不是很大,但,null安全,在链式调用中很有用。例如,如果一个员工 Bob 可能会(或者不会)分配给一个部门, 并且可能有另外一个员工是该部门的负责人,那么获取 Bob 所在部门负责人(如果有的话)的名字,我们写作:

    bob?.department?.head?.name

    如果任意一个属性(环节)为空,这个链式调用就会返回 null。如果要只对非空值执行某个操作,安全调用操作符可以与 let 一起使用:

    val listWithNulls: List<String?> = listOf("A", null)
    for (item in listWithNulls) {
         item?.let { println(it) } // 输出 A 并忽略 null
    }

    !! 操作符

    很多情况下,NullPointerException对我们来说还是有一定意义的,我们必须catch住此异常。那么,Kotlin中的又有空安全的机制存在,我们就必须对null进行强校验。这里,Kotlin给我们提供的操作符为两个引号 “!!”,如:

    var a : String? = null // 必须是可空类型,不然强校验没有意义
    val lenC = a!!.length // 这样做就会报错

    如果,希望强校验希望系统抛出一个NullPointerException,那么必须让定义的变量可容纳null,不然强校验就失去意义了。

    安全的类型转换

    如果对象不是目标类型,那么常规类型转换可能会导致 ClassCastException。 另一个选择是使用安全的类型转换,如果尝试转换不成功则返回 null:

    val aInt: Int? = a as? Int

    可空类型的集合

    如果你有一个可空类型元素的集合,并且想要过滤非空元素,你可以使用 filterNotNull 方法来实现。

    val nullableList: List<Int?> = listOf(1, 2, null, 4)
    val intList: List<Int> = nullableList.filterNotNull()

    Elvis 操作符(?:)

    Elvis操作符很像是Java语言中的三目表达式,然而由于三目表达式的对于很多开发者来说都比较难懂,导致经常用错。Kotlin对三目表达式进行了升级,即elvis表达式的来源,Kotlin中不再支持三目表达式。Elvis操作符的用法如下:

    <结果> = <表达式1> ?: <表达式2>

    如果表达式1为null,则返回表达式2的内容,否则返回表达式1。请注意,当且仅当左侧表达式1为空时,才会对右侧表达式求值。如:

    // Elvis操作符获取b字符串的长度,如果b为null则返回-1
    val lenB = b?.length ?: -1
    
    // 等同于逻辑
    val lenA: Int = if (b != null) {
        b.length
    } else {
        -1
    }

    位运算符

    运算符优先级

    运算符的优先级使得一些运算符优先于其他运算符,从而是的高优先级的运算符会先被计算。而运算符的结合性用于定义相同优先级的运算符在一起的时和表达式结合或关联规则,在混合表达式中,运算符的优先级和结合性是非常重要的。如:

    2 + 3 - 4 * 5 // 等于 -15

    如果严格地从左到右,计算过程会是这样:

    2 + 3 = 5
    5 - 4 = 1
    1 * 5 = 5

    但实际上乘法优先于减法被计算,所以实际上的运算过程是:

    2 + 3 = 5
    4 * 5 = 20
    5 - 20 = -15

    这类似的情况和我们在数学中一样:右括号先算括号里的, 然后先乘除后加减。在Kotlin语言中也拥有自己运算符的优先级别和结合性。这里我们把所有的运算符总结为下表:

    优先级 运算符 结核性
    1 ()、[] 从左到右
    2 !、+(正)、-(负)、~、++、– 从右到左
    3 *、/、% 从左到右
    4 +(加)、-(减) 从左到右
    5 <<、>>、>>> 从左到右
    6 <、<=、>、>= 从左到右
    7 ==、!= 从左到右
    8 &(按位与) 从左到右
    9 ^ 从左到右
    10 | 从左到右
    11 && 从左到右
    12 || 从左到右
    13 ?: 从右到左
    14 =、+=、-=、/=、%=、&=、|=、^=、~=、<<=、>>=、>>>= 从右到左

    运算符重载

    预定义的运算符的操作对象只能是基本数据类型,实际上,对于很多我们自定义的对象也需要有类似的运算操作。运算符重载是对已有的运算符赋予多重含义,使同一个运算符作用于不同类型的数据导致不同类型的行为。

    运算符重载是自C++语言器就支持的特性,然而在Java语言之中这个特性就不在支持,在很多高级科学运算上很不方便,Kotlin语言又从新支持此特性。不同于符合赋值运算符,我们可以定义/、==、-、+、*、%、<<、>>、!、&、|、^等运算符的逻辑功能。重载一个运算符,我们必须重写它所对应的operator方法,如下就是一个针对 “+” 运算符的定义:

    public operator fun plus(other: Byte): Float

    这里,我们可以看得出来运算符重载的实质是方法的重载。在实现过程中,首先把指定的运算表达式转化为对运算方法的调用,运算对象转化为运算符方法的实参,然后根据实参的类型来确定需要调用达标函数,之后Kotlin会将对应的符号运算切换到方法之中。如Float类型针对 “+” 运算符所定义的:
    image

    重载一元运算符

    一元前缀操作符

    表达式 转换方法
    +a a.unaryPlus()
    -a a.unaryMinus()
    !a a.not()

    这个表是说,当编译器处理例如表达式 +a 时,它执行以下步骤:
    1. 确定 a 的类型,令其为 T。
    2. 为接收者 T 查找一个带有 operator 修饰符的无参函数 unaryPlus(),即成员函数或扩展函数。
    3. 如果函数不存在或不明确,则导致编译错误。
    4. 如果函数存在且其返回类型为 R,那就表达式 +a 具有类型 R。

    递增和递减

    表达式 转换方法
    a++ a.inc()
    a– a.dec()

    inc() 和 dec() 函数必须返回一个值,它用于赋值给使用 ++ 或 – 操作的变量。它们不应该改变在其上调用 inc() 或 dec() 的对象。

    编译器执行以下步骤来解析后缀形式的操作符,例如 a++:
    1. 确定 a 的类型,令其为 T。
    2. 查找一个适用于类型为 T 的接收者的、带有 operator 修饰符的无参数函数 inc()。
    3. 检查函数的返回类型是 T 的子类型。

    计算表达式的步骤是:
    1. 把 a 的初始值存储到临时存储 a0 中,
    2. 把 a.inc() 结果赋值给 a,
    3. 把 a0 作为表达式的结果返回。
    4. 对于 a–,步骤是完全类似的。

    对于前缀形式 ++a 和 –a 以相同方式解析,其步骤是:
    1. 把 a.inc() 结果赋值给 a,
    2. 把 a 的新值作为表达式结果返回。

    二元操作符

    算术运算符

    表达式 转换方法
    a + b a.plus(b)
    a - b a.minus(b)
    a * b a.times(b)
    a / b a.div(b)
    a % b a.mod(b)
    a..b a.rangeTo(b)

    对于此表中的操作,编译器只是解析成翻译为列中的表达式。

    请注意,自 Kotlin 1.1 起支持 rem 运算符。Kotlin 1.0 使用 mod 运算符,它在 Kotlin 1.1 中被弃用。

    “In”操作符

    表达式 转换方法
    a in b b.contains(a)
    a !in b !b.contains(a)

    对于 in 和 !in,过程是相同的,但是参数的顺序是相反的。

    索引访问操作符

    表达式 转换方法
    a[i] a.get(i)
    a[i, j] a.get(i, j)
    a[i_1, …, i_n] a.get(i_1, … , i_n)
    a[i]=b a.set(i, b)
    a[i,j]=b a.set(i, j, b)
    a[i_1, … , i_n]=b a.set(i_1,… ,o_n,b)

    方括号转换为调用带有适当数量参数的 get 和 set。

    调用操作符

    表达式 转换方法
    a(i) a.invoke(i)
    a(i, j) a.invoke(i, j)
    a(i_1, … , i_n) a.invoke(i_1, …, i_n)

    圆括号转换为调用带有适当数量参数的 invoke。

    广义赋值

    表达式 转换方法
    a += b a.plusAssign(b)
    a -= b a.minusAssign(b)
    a *= b a.timesAssign(b)
    a /= b a.divAssign(b)
    a %= b a.modAssign(b)

    对于赋值操作,例如 a += b,编译器执行以下步骤:
    1. 如果右列的函数可用
    2. 如果相应的二元函数(即 plusAssign() 对应于 plus())也可用,那么报告错误(模糊)。
    3. 确保其返回类型是 Unit,否则报告错误。
    4. 生成 a.plusAssign(b) 的代码
    5. 否则试着生成 a = a + b 的代码(这里包含类型检查:a + b 的类型必须是 a 的子类型)。

    注意:赋值在 Kotlin 中不是表达式。

    相等与不等操作符

    表达式 转换方法
    a == b a?.equals(b) ?: (b === null)
    a != b !(a?.equals(b) ?: (b === null))

    这个 == 操作符有些特殊:它被翻译成一个复杂的表达式,用于筛选 null 值。 null == null 总是 true,对于非空的 x,x == null 总是 false 而不会调用 x.equals()。

    注意:=== 和 !==(同一性检查)不可重载。

    比较操作符

    表达式 转换方法
    a > b a.compareTo(b) > 0
    a < b a.compareTo(b) < 0
    a >= b a.compareTo(b) >= 0
    a <= b a.compareTo(b) <= 0

    所有的比较都转换为对 compareTo 的调用,这个函数需要返回 Int 值。

    位运算

    对于位运算,Kotlin 并没有提供特殊的操作符, 只是提供了可以叫中缀形式的方法, 比如:

    // 给 Int 定义扩展
    infix fun Int.shl(x: Int): Int {
    ……
    }
    
    // 用中缀表示法调用扩展方法
    1 shl 2
    
    // 等同于这样
    1.shl(2)

    下面是全部的位运算操作符(只可以用在 Int 和 Long 类型):

    表达式 转换方法
    shl(bits) 有符号左移 (相当于Java <<)
    shr(bits) 有符号右移 (相当于Java >>)
    ushr(bits) 无符号右移(相当于Java >>>)
    and(bits) 按位与
    or(bits) 按位或
    xor(bits) 按位异或
    inv() 按位取反

    展开全文
  • Kotlin中常见的符号

    2018-07-18 10:10:16
    在今年的Google I/O大会上,Google正式宣布,Kotlin将会成为Android开发的官方支持语言。除了Android外,Kotlin还可以完全作为服务端开发的语言,比如在未来的Spring 5就将对Kotlin提供强大的支持。以及浏览器编程...

    在今年的Google I/O大会上,Google正式宣布,Kotlin将会成为Android开发的官方支持语言。除了Android外,Kotlin还可以完全作为服务端开发的语言,比如在未来的Spring 5就将对Kotlin提供强大的支持。以及浏览器编程语言,与JS进行交互。

    Kotlin是一门静态语言,支持多种平台,包括移动端、服务端以及浏览器端,此外,Kotlin还是一门融合了面向对象与函数式编程的语言,支持泛型、安全的空判断,并且Kotlin与Java可以做到完全的交互。

    现在介绍Kotlin的文章已经是铺天盖地,大部分都是从Kotlin的基本数据类型介绍起,本文不想重复这样的事情,这里从另一面来开始,我们来看看Kotlin中的各种符号。

    这里写图片描述

    • $符合和多行输入符
    println("itemB:$itemB")
    
    //字符串模板
    var userInfo = "name:${user.name},  age:$age"
    
    //三引号的形式用来输入多行文本
    val str = """ 
        one
        two
            """
    //等价于          
    val str = "one\ntwo"

    三引号之间输入的内容将被原样保留,之中的单号和双引号不转义,其中的不可见字符比如/n和/t都会被保留。

    • ?问号

    表示这个对象可能为空

    //在变量类型后面加上问号,代表该变量是可空变量  
    var name: String? = "zhangsan"  
    /**
     * 如果str不能转为Int类型,则返回null
     */
    fun parseInt(str: String): Int? { 
      // (代码略)
    }
    
    b?.length //如果 b非空,就返回 b.length ,否则返回 null,这个表达式的类型是 Int? 。
    • Elvis操作符(?:)

    如果r非空,我使用它;否则使⽤某个非空的值 x ”:

     val l: Int = if (b != null) b.length else -1 

    除了完整的 if-表达式,这还可以通过 Elvis 操作符表达:

     val l = b?.length ?: -1 

    如果 ?: 左侧表达式非空,elvis操作符就返回其左侧表达式,否则返回右侧表达式。请注意,当且仅当左侧为空时,才会对右侧表达式求值。

    • !! 操作符

    对于NPE 爱好者,我们可以写 b!! ,这会返回一个非空的 b 值 或者如果 b 为空,就会抛出一个 NPE 异常:

    val l = b!!.length

    因此,如果你想要一个 NPE,你可以得到它,但是你必须显式要求它,否则它不会不期而至。

    • ==号与===号

    ==判断值是否相等,===判断值及引用是否完全相等。

    val num: Int = 128;
    val a:Int? = num
    val b:Int? = num
    println(a == b)
    print(a === b)
    • ..符号

    Kotlin中有区间的概念,区间表达式由具有操作符形式 .. 的 rangeTo 函数辅以 in 和 !in 形成。 区间是为任何可比较类型定义的,但对于整型原生类型,它有一个优化的实现。以下是使用区间的一些示例:

    if (i in 1..10) { // 等同于 1 <= i && i <= 10
        println(i)
    }
    //使用until函数,创建一个不包括其结束元素的区间
    for (i in 1 until 10) {   // i in [1, 10) 排除了 10
         println(i)
    }
    
    for (i in 1..4) print(i) // 输出“1234”
    
    for (i in 4..1) print(i) // 什么都不输出

    如果你想倒序迭代数字呢?也很简单。你可以使用标准库中定义的 downTo() 函数

    for (i in 4 downTo 1) print(i) // 输出“4321”

    step()函数,可以指定任意步长

    for (i in 1..4 step 2) print(i) // 输出“13”
    
    for (i in 4 downTo 1 step 2) print(i) // 输出“42”
    • _(下划线)
     data class Book(var id: Int, var name: String)

    上面的示例中,Book声明了 id,name两个变量。解构时如果只需要id这一个变量时,可以这么做:

    val book = Book(1, "英语")
    val (id, _) = book
    • ::符号
    //得到类的Class对象
    startActivity(Intent(this@KotlinActivity, MainActivity::class.java))
    
    //内联函数和reified后续介绍
    inline  fun <reified T> Gson.fromJson(json:String):T
    {
        return fromJson(json, T::class.java)
    }

    方法引用

    var setBook = setOf<String>("hello", "hi", "你好")
    //    setBook.forEach { print(it)}
        setBook.forEach(::print)
    • @符号

    1、限定this的类型

    class User {
        inner class State{
            fun getUser(): User{
                //返回User
                return this@User
            }
            fun getState(): State{
                //返回State
                return this@State
            }
        }
    }
    

    2、作为标签

    跳出双层for

    loop@ for (itemA in arraysA) {
         var i : Int = 0
          for (itemB in arraysB) {
             i++
             if (itemB > 2) {
                 break@loop
             }
    
             println("itemB:$itemB")
         }
    
    }

    命名函数自动定义标签:

    fun fun_run(){
        run {
            println("lambda")
        }
        var i: Int = run {
            return@run 1
        }
        println("$i")
        //匿名函数可以通过自定义标签进行跳转和返回
        i = run (outer@{
            return@outer 2
        })
        println(i)
    }

    从forEach函数跳出

    fun forEach_label(ints: List<Int>)
    {
        var i =2
        ints.forEach {
            //forEach中无法使用continuebreak;
    //        if (it == 0) continue //编译错误
    //        if (it == 2) /*break //编译错误 */
            print(it)
        }
         run outer@{
             ints.forEach {
                 if (it == 0) return@forEach //相当于在forEach函数中continue,实际上是从匿名函数返回
                 if (it == 2) return@outer //相当于在forEach函数中使用break,实际上是跳转到outer之外
             }
         }
    
        if (i == 3)
        {
            //每个函数的名字代表一个函数地址,所以函数自动成为标签
            return@forEach_label //等同于return
        }
    }
    • {}符号

    这里指的是lambda表达式的符号。

    // 一个参数
    var callback: ((str: String) -> Unit)? = null
    callback = { println(it)}
    // 判断并使用
    callback?.invoke("hello")
    
    //两个参数
    var callback2: ((name: String, age: Int) -> Unit)? = null
    callback2 = { hello: String, world: Int -> println("$hello's age is $world") }
    callback2?.invoke("Tom", 22)
    
    var callback3 :((num1:Int, num2: Int)->String)? = null
    //类型可以推断
    callback3 = { num1, num2 ->
        var res:Int = num1 + num2
        res.toString()
    }
    
    println(callback3?.invoke(1, 2))
    

    kotlin中{}里面整个是lambda的一个表达式,而java8中{}部分只是lambda表达式的body部分。

    还要 :符号,用于类名后表示继承,用于变量后限定变量类型,是Kotlin中最常用的符号,这里基本囊括了Koltin中一些常见的符号,关于符号就介绍这么多,如有遗漏请再下方留言提出,我将补充上。

    展开全文
  • RecyclerView用于列表的显示,大家都很熟悉了,这里主要介绍下RecyclerView在kotlin中结合Databinding的使用 1.创建项目后需要在gradle中引入: implementation'...

    RecyclerView用于列表的显示,大家都很熟悉了,这里主要介绍下RecyclerView在kotlin中结合Databinding的使用

    1.创建项目后需要在gradle中引入:

    implementation 'com.android.support:recyclerview-v7:28.0.0-alpha1'

    否则当你使用RecyclerView时会发现没办法引入。

    2.布局文件:

    这里因为我们使用的是Databinding所以使用的是layout开头的布局:

    <layout  xmlns:android="http://schemas.android.com/apk/res/android"
        >
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">
            <android.support.v7.widget.RecyclerView
                android:id="@+id/rv_person"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">
            </android.support.v7.widget.RecyclerView>
        </LinearLayout>
    
    </layout>

     

    2.RecyclerView的初始化:

    private fun initView(){
        val layoutManager = LinearLayoutManager(this)
        mBinding!!.rvPerson.layoutManager = layoutManager
        mAdapter = PersonAdapter()
        mBinding!!.rvPerson.adapter = mAdapter
    
    
    }

    可以看到我们直接使用mBinding!!.rvPerson 来得到RecyclerView就相当于java的findviewbyid,

    mBinding!!.rvPerson.layoutManager = layoutManager就相当于java的setLayoutManager

    mBinding!!.rvPerson.adapter = mAdapter就相当于java的setAdapter

    3.adapter:

    (1)BaseAdapter:

    abstract class BaseAdapter<T>(var data: List<T> = listOf()) : RecyclerView.Adapter<BaseViewHolder>() {
    
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
            return BaseViewHolder(DataBindingUtil.inflate(LayoutInflater.from(parent?.context), viewType, parent, false))
        }
    
        override fun getItemCount(): Int {
            return data.size
        }
    
        fun refreshData(newData: List<T>) {
            this.data = newData
            this.notifyDataSetChanged()
        }
    }
    
    
    open class BaseViewHolder(var dataBinding: ViewDataBinding) : RecyclerView.ViewHolder(dataBinding.root) {
    
    
    }

    BaseAdapter:通过这句话来绑定itemview BaseViewHolder(DataBindingUtil.inflate(LayoutInflater.from(parent?.context), viewType, parent, false))

     

    (2)adapter:

    class PersonAdapter : BaseAdapter<PersonBean>(){
        override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
            var binding : ViewDataBinding = holder.dataBinding
            binding.setVariable(BR.personBean,data.get(position))
        }
    
        override fun getItemViewType(position: Int): Int {
            return R.layout.item_person
        }
    
    
    }

    adpater中主要通过:

    var binding : ViewDataBinding = holder.dataBinding

            binding.setVariable(BR.personBean,data.get(position))

    来设置itemview的数据显示。

    (2)布局文件:

    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android"
        >
        <data>
            <import type="com.example.linwb.childrenstudy.R"/>
            <variable
                name="personBean"
                type="com.example.linwb.childrenstudy.bean.PersonBean"/>
        </data>
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            >
            <TextView
                android:id="@+id/tv_name"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{personBean.nickname}"
                />
        </LinearLayout>
    
    
    </layout>
    
    

    布局文件也是使用Databinding的。

    项目代码地址:

    https://github.com/linwenbing/KotlinMVVMdemo

    展开全文
  • kotlin ++ -- 葡萄牙 马夫拉宫的18世纪图书馆 Java开发人员应该考虑Kotlin的原因有很多。 具有完整的Java互操作性,内置的Null安全性,精简的样板文件-用更少的代码做更多的事情-因为该语言确实很简洁,并且从...
  • kotlin是啥?这里就不用多说了,想必看这篇文章的童鞋肯定是有所了解的。 那么这篇文章你可以收获什么? 答:本文主要通过本人如何从java转战到kotlin并应用在实际项目中的个人经历,给大家提供一些学习思路、...
  • 点击上方“CSDN”,选择“置顶公众号”关键时刻,第一时间送达!毫无疑问,Kotlin 目前很受欢迎,业界甚至有人认为其将取代 Java 的霸主地位。它提供了 Null ...
  • 本文将展示在Android中会遇到的实际问题,并且使用Kotlin怎么去解决它们。一些Android开发者在处理异步、数据库或者处理Activity中非常冗长的listener时发现了很多的问题。通过一个个真实的场景,我们一边解决问题...
  • 简介:【静态、效率、表现力、安全、互动】 ● 新型静态类型编程语言 ● 提高工作效率和开发者满意度 ●现代并富有表现力(专注表达自己的想法、便于测试和维护...● Kotlin 可完全与 Java 编程语言互操作 ...
  • Kotlin世界中,无任何修饰的class、方法,等等默认就是public的,所以是隐式的 public的class class MyActivity { } public 的方法 fun methord() { } public 的变量 var temp: String? = null public的...
  • 为什么我要改用Kotlin

    2017-05-18 08:40:19
    写在前面的话,作为一个不熬夜的人,一觉醒来发现Kotlin成为了Android的官方语言,可谓是大喜过望。为了趁热打铁,我决定提前三天放出原定本周日Release的文章。希望能及时让大家了解一下Kotlin。相信很多开发人员,...
  • 作为一个假的Android开发者,并没有通宵去看 Google I/O 2017开发者大会。早上和往常 一样起床坐地铁上班,习惯性掏出手机看看...别人的项目重构也直接上的Kotlin,习惯了Kotlin写代码的简洁,高效,酷炫,写回Java
  • 标签: Kotlin 常用技巧 目录: 一、回调函数的Kotin的lambda的简化 二、内联扩展函数之let 三、内联函数之with 四、内联扩展函数之run 五、内联扩展函数之apply 六、内联扩展函数之also 七、let,with,run,...
  • Kotlin搞起来 —— 1.Kotlin学习资料与环境配置 学习资料 环境配置 附1:Java转Kotlin 附2:Kotlin转Java 附3:写Kotlin代码的小贴士
  • (新) Kotlin搞起来 —— 4.类与对象 1.Any类 2.类定义与对象实例化 3.构造器(构造方法) 主构造器 辅助构造器 私有主构造器 4.继承 5.接口 6.抽象类 7.内部类,匿名内部类的创建 8.单例对象 9.伴生对象 10.数据类 11....
1 2 3 4 5 ... 20
收藏数 57,188
精华内容 22,875
关键字:

kotlin