美文网首页
Kotlin 笔记(一) 基础知识点--java对比

Kotlin 笔记(一) 基础知识点--java对比

作者: Boyko | 来源:发表于2017-07-06 17:12 被阅读0次

    创建对象

    Java Kotlin
    A a = new A() var a = A()

    类型声明

    Java Kotlin
    String s var s : String
    final String s val s : String
    class A extends B class A : B
    class A implements B class A : B

    字符串模板

    Java Kotlin
    String.format("%s今年%d岁", name, age); "${name}今年${age}岁"

    方法

    Java Kotlin
    String text(int para1,String para2) { ... } fun test(para1: Int, para2: String): String { ... }

    Kotlin 特性 : 函数参数默认值和可变参数

    对Kotlin函数中的某个参数可以用“=”号指定其默认值,调用函数方法时可不不传这个参数,但其他参数需要用“=”号指定。下文例子中没有传递参数para2,其实际值为默认值"para2"

    fun test(para1: Int, para2: String = "para2", para3: String): String { ... }
    
    test(22, para3 = "hello")
    

    可变参数值的话,需要用关键字vararg来定义。这里需要注意的是,一个函数仅能有一个可变参数。该可变参数不一定得是最后一个参数,但当这种情况下时,调用该方法,需要给其他未指明具体值的参数传值。

    fun test(vararg para1: String, para2: String): String { ... }
    
    test("para1", "para4", "para5", para2 = "hello")
    

    构造函数

    Kotlin

    类的构造函数分为==primary constructor==和==secondary constructor==,前者只能有一个,而后者可以有多个。如果两者都未指定,则默认为无参数的primary constructor。
    primary constructor是属于类头的一部分,用constructor关键字定义,无可见性字段定义时可省略。初始化代码需要单独写在init代码块中,==primary constructor的参数只能在init代码块和变量初始化时使用。==
    secondary constructor也是用constructor关键字定义,必须要直接或间接代理primary constructor。

    class Student(name: String) { // primary constructor
        var mName: String = name
        init {
            println("Student is called " + name)
        }
    
        constructor(age: Int, name: String):this(name) {
            println("Student is ${age} years old")
        }
    }
    

    继承

    Java Kotlin
    Java中类默认可被继承,只有被final关键字修饰的类才不能被继承 仅有被open修饰的类才可以被继承

    单例

    Java Kotlin
    用代码实现单例 用关键词 object 定义单例类
    object Shop { // 不能有 primary constructor的参数  ,
        fun buySomething() {
            println("Bought it")
        }
    }
    
    Shop.buysomething() // 调用
    

    静态标识与调用

    Java Kotlin
    static标识一个类里的静态属性或方法 用companion修饰单例类object,来实现静态属性或方法功能
    class Mall(name: String) {
    
        // companion 伴随对象 在一个类中 [唯一] 
        companion object Shop { // 与 java 不同,Kotlin 所有静态方法和字段都写在内部单例类中
            val SHOP_NAME: String = "McDonald" // 等同于Java中写public static String
            fun buySomething() { // 等同于Java中写public static void
                println("Bought it")
            }
        }
        
    }
    
    
    Mall.buySomething() // 可直接调用,
    

    if-else语句

    Kotlin

    Kotlin中的if-else语句与Java一致,结构上都是if (条件A) { 条件A为真要执行的操作 } else { 条件A为假要执行的操作 }
    这里主要要介绍的是Kotlin的一个特点,即if-else语句可以作为一个逻辑表达式使用。不仅如此,逻辑表达式还可以以代码块的形式出现,代码块最后的表达式作为该块的值。

    // 逻辑表达式的使用
    fun maxNum(x: Int, y: Int): Int {
        var max = if (x > y) x else y
        return max
    }
    // 代码块形式的逻辑表达式
    fun maxNumPlus(x: Int, y: Int): Int {
        var max = if (x > y) {
            println("Max number is x")
            x
        } else {
            println("Max number is y")
            y
        }
        return max
    }
    

    when语句

    Java Kotlin
    switch-case语句 有多种形式的条件表达。与if-else一样Kotlin中的when也可以作为逻辑表达式使用
    // 逻辑表达式的使用
    fun judge(obj: Any) { // Any 相当于 java中 Object 
        when (obj) {
            1 -> println("是数字1")
            -1, 0 -> println("是数字0或-1")
            in 1..10 -> println("是不大于10的正整数")
            "abc" -> println("是字符串abc")
            is Double -> println("类型是双精度浮点数")
            else -> println("其他操作")
        }
    }
    

    标签

    Kotlin中可以对任意表达式进行标签标记,形式为abc@,xyz@。而这些标签,可以搭配return、break、continue等跳转行为来使用。

    fun labelTest() {
        la@ for (i in 1..10) {
            println("outer index " + i)
            for (j in 1..10) {
                println("inner index " + j )
                if ( inner % 2 == 0) {
                    break@la
                }
            }
        }
    }
    

    符号“?”

    Kotlin中,当我们定义一个变量时,其默认就是非空类型。如果你直接尝试给他赋值为null,编译器会直接报错。Kotlin中将符号“?”定义为安全调用操作符。变量类型后面跟?号定义,表明这是一个可空类型。同样的,在调用子属性和方法时,也可以用字符?进行安全调用。Kotlin的编译器会在写代码时就检查非空情况,因此下文例子中,当s2有前置条件判断为非空后,即便其本身是可空类型,也可以安全调用子属性或方法。对于ifelse结构的逻辑,Kotlin还提供了“?:”操作符,极大了简化了代码量又不失可读性。Kotlin还提供“!!”双感叹号操作符来强制调用对象的属性和方法,无视其是否非空。这是一个挺危险的操作符,除非有特殊需求,否则为了远离NPE,还是少用为妙。

    var s1: String = "abc"
    s1 = null // 这里编译器会报错
    var s2: String? = "abc"
    s2 = null // 编译器不会报错
    
    var l1 = s1.length // 可正常编译
    var l2 = s2.length // 没有做非空判断,编译器检查报错
    
    if (s2 != null) s2.length // Java式的判空方案
    s2?.length // Kotlin的安全调用操作符?。当s2为null时,s2?.length也为null
    if (s2 != null) s2.length else -1 // Java式判空的ifelse逻辑
    s2?.length ?: -1 // Kotlin的elvis操作符
    s2!!.length // 可能会导致NPE
    

    扩展方法

    扩展函数定义形式

    在方法体内是可以调用receiverType的对象中原本的方法

    fun receiverType.functionName(params){
        body // 注: 在方法体中使用 this ,那么 this 代表receiverType的对象(调用者)
    }
    
    • receiverType:表示函数的接收者,也就是函数扩展的对象
    • functionName:扩展函数的名称
    • params:扩展函数的参数,可以为NULL

    常见例子,其中 this 代表 context ,

    fun Context.showLongToast(msg: String) {
        Toast.makeText(this, msg, Toast.LENGTH_LONG).show()
    }
    

    扩展方法位置

    简单说可以分为两种: class 里面 ,class 外面

    1. 在一个类里面写扩展方法,这时,在其他类直接调用是不生效的.
    class B {
    
        fun Student.doF(){
            doFly() // Student 中原本就存在的方法
        }
    
        fun dow2(s:Student){
            s.doF()
        }
    
    }
    
    
    // 调用
    B().dow2(Student())
    

    在其他类调用 student.doF() 是不生效的 ,只有创建B对象调用dow2(s),扩展方法才能被调用

    1. 单独新建一文件,或者写在类的外面,可全部任意调用,如上面toast的例子,将扩展方法写在单独的文件中,在任意activity或者拥有Context的类中都能调用
    /**
    * 单独一个类
    */
    package ****
    
    import android.content.Context
    import android.widget.Toast
    import java.util.*
    
    // 扩展方法
    fun Context.showLongToast(msg: String) {
        Toast.makeText(this, msg, Toast.LENGTH_LONG).show()
    }
    
    
    /**
    * 一个类内 ,class 外
    */
    fun Context.showLongToast(msg: String) {
        Toast.makeText(this, msg, Toast.LENGTH_LONG).show()
    }
    class Student(name: String) {
        ...
    }
    
    
    // 调用
    class MainActivity : AppCompatActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            showLongToast("111")
        }
    }
    
    

    伴随对象的扩展

    伴随对象通过“类名.”形式调用伴随对象。假如对伴随对象声明了扩展函数该怎么调用呢?其调用方式与伴随对象一样,都是通过用类名限定符来调用。当然,扩展属性也是这样的。(伴随对象companion 的使用,请看上文)

    fun Student.Shop.doW(){
        println("伴随对象的扩展函数")
    }
    
    
    //调用
    Student.doW()
    

    扩展函数名与原类中的函数名相同

    相同时,调用时根据扩展函数写的位置不同,会存在两种情况

    1. 类外.setText()与 TextView 中的方法同名,但是在调用时,会全部提示出来
    fun TextView.setText(msg: String) {
        this.setText("扩展函数"+msg)
    }
    

    但是就傲娇的不按提示调用,生写一个一样的方法时,这种情况下,总是会优先使用成员函数,而不是扩展

    open class Person {
        fun doFly() {
            println("亲生的")
        }
    }
    
    fun Person.doFly() {
        println("后生的")
    }
    
    fun main(args: Array<String>) {
    
        val person: Person = Person()
        person.doFly()
    }
    
    // Log打印
    亲生的
    
    1. 类内,通过下面的例子可以得出结论,当出现重名 doFly() 时,会默认调用this(也就是 Person)中的方法
    open class Person {
    
        fun doFly() {
            println("Person do fly")
        }
    }
    
    class MyInfo {
    
        fun doRun() {
            println("MyInfo do run")
        }
    
        fun doFly() {
            println("MyInfo do fly")
        }
    
        fun Person.doSwim() {
            doFly() // 默认调用this(也就是 Person)中的方法
            doRun()
        }
    
        fun doSomething(person: Person) {
            person.doSwim()
        }
    }
    
    // 测试实例
    fun main(args: Array<String>) {
    
        val myInfo: MyInfo = MyInfo()
        val person: Person = Person()
        myInfo.doSomething(person)
    }
    // Log  
    Person do fly
    MyInfo do run
    

    但是如果也需要调用 MyInfo 中的doFly(),那么在
    doFly()前面加上 "this@MyInfo. " 即可.

      fun Person.doSwim() {
            doFly()
            this@MyInfo.doFly()
            doRun()
    }
    
    // Log
    Person do fly
    MyInfo do fly
    MyInfo do run
    

    接收者可为NULL

    调用者对象为空时,也可以调用

    fun Person.doFly() {
    
        if (null == this) {
            println("null")
        }
        println("doFly")
    }
    

    扩展属性

    扩展属性, 即 Extension Property , 即把某些函数添加为数据, 使用”=”, 直接设置或使用。

    
    val List.lastIndex: Int 
    get() = size - 1
    

    注:
    由于扩展属性实际上不会向类添加新的成员, 因此无法让一个扩展属性拥有一个后端域变量. 所以,对于扩展属性不允许存在初始化器. 扩展属性的行为只能通过明确给定的取值方法与设值方法来定义,也就意味着扩展属性只能被声明为val而不能被声明为var.如果强制声明为var,即使进行了初始化,在运行也会报异常错误,提示该属性没有后端域变量。

    数据类

    Java中数据model,通常都是由多个属性和对应的getter、setter组成。当有大量多属性的数据类时,不仅这些类会因为大量的getter和setter方法而行数爆炸,也使整个工程方法数骤增。Kotlin中也做了这层特性优化,提供了数据类的简单实现。不用再写get和set方法,这些都由编译器背后去做,你得到的是一个清爽干净的数据类。具体使用参考下面的例子。

    data class Student (
        var name: String,
        var age: Int,
        var hobby: String?,
        var university: String = "NJU"
    )
    fun printInfo() {
        var s: Student = Student("Ricky", 25, "playing Gwent")
        println("${s.name} is from ${s.university}, ${s.age} years old, and likes ${s.hobby}")
    }
    

    为了保证生成代码的一致性和有意义的方法,数据类对象必须满足一下要求

    • 构造函数必须至少有一个参数
    • 所有的构造函数参数必须标注val或者var;
    • 数据类对象不能是 abstract, open, sealed or inner;
    • 数据类对象不能继承其他类,但是可以实现接口
    • 在Java虚拟机里,如果生成的类需要有一个无参数的构造函数,所有属性的默认值必须有一个具体的值,

    componentN()

    类对象里,有几个属性,会有几个这个方法componentN(),比如上面的示例,调用方法 s.name 和 s.component1() 效果是一样的

    复制

    我们经常有这么一个使用场景:我么需要复制一个类对象,但是改变它的某些属性,保持剩余其他的属性值不变。这就需要用到copy()函数实现。对于上面的User类,可以如下实现复制:

    fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
    
    // 调用
    val jack = User(name = "Jack", age = 1)
    val olderJack = jack.copy(age = 2)
    
    

    相关文档

    Kotlin-扩展;
    Kotlin 基础学习+快速实践

    相关文章

      网友评论

          本文标题:Kotlin 笔记(一) 基础知识点--java对比

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