kotlin 之于 java 的差异

作者: rexyren | 来源:发表于2017-09-04 07:55 被阅读220次

    一.变量声名,赋值和访问

    kotlin 变量是空安全的,一般要求声明时就赋值。声名变量有两个关键字varval,其中val相当于 java 的中的final。变量类型可显式给出,也可隐式推断。变量也可分可空类型和非空类型。以下给出几种声名和初始化示例说明

    var a:String="hello"   //显式声明String 类型变量
    private var b="hello"  //隐式声明,可推断为String类型变量
    var c:String?=null     //可空的 String 类型变量,可空类型必需显式给出类型
    lateinit var d:String  //String 类型变量,可延迟但必须在使用前要赋值,仅变量支持。
    
    internal val e="hello"           //常量
    val f:String by lazy { "hello" } //懒加载属性,首次使用才会初始化。
    

    可空变量访问需要强制判断空,见下面示例:

    var name:String?=null;
    val len1=name!!.length   //如果name 为空就会报NPE
    val len2=name?.length?:0 //为空时返回0,"?."只有不空时才执行后面语句否则返回null.
    

    在kotlin中变量都会自动有隐式的有get和set属性,访问可见性同声明变量的访问属性,这在 java 中是要对变量额外写一堆 get 和 set 方法。 kotlin 的属性的 get 和 set 的访问可见性和属性访问也都是重写的见下面示例:

     var name:String="won't null" //name 的访问级别为public
        get() {return field}      //属性的访问器需要有内置field 去引用
        private set(value) {      //set操作访问重写为private级别   
            if(value.isNotEmpty()){
                field=value       //改写设置器只接受非空字符
            }
        }
    

    在 java 中有个 volatile 修饰变量来解决多线程访问变量一致性问题,这种作用的关键字在 kotlin 中是没有的,不过它提供了注解的方式达到相同的功能即:@Volatile 来注解修饰某个变量。kotlin 的每个变量在编译生成 class 字节码中都会有相应的get和set方法,仅val的变量是没有 set 方法的(常量 set 无意义).如果我们想强制让变量像 java一样,而不会编译生成访问器可用 @JvmField 来注解变量。

    二.控制语句和运算符

    kotlin有的表达式和java差不多,但它没有 java 的逗号表达式和switch 表达式,不过有很好的代替方案:java 的逗号表达式在kotlin中可用Elvis操作符可 if-else代之,而 switch 表达式可用新加的 when 表达式代替。见下例使用

    var score=8
    val grade=when(score){
        9,10 ->"Excellent"
        7,8  -> "Good"
        6    -> "ok"
        in 0..5 -> "bad"
        else -> null
    }
    
    //Elvis操作符,?:左边的值不空则返回它,否则返回?:右边的值
    val len1= grade?.length ?: 0
    
    //条件表达式实现java 的逗号表达。
    val len2=if(grade==null) 0 else grade.length
    

    kotlin 的 if-else 和 when 表达式都是有返回值的,返回的都是执行分支的最后一条语句的值。在循环语句中 kotlin 的 for 循环相当丰富,如下:

    for (i in 1..10)
    for (i in 1 until 10)
    for (i in 1..10 step 2)
    for (i in 10 downTo 1)
    for (i in 10 downTo 1 step 2)
    
    val arr= arrayOf(1,2,3,4)
    for(i in arr)
    
    val map= mapOf(("a" to "va"),("b" to "vb"))
    for((key,value) in map)
    

    运算符号方面java 的instanceof类型判断在 kotlin 中被is操作关键字代替,其它最大区别在于按位操作符号在 kotlin 中都变为表达式如下表示例:

    kotlin 位操作 操作说明 kotlin 用例=java 用例
    shl(bits) 有符号左移 (相当于Java <<) 13 shl 3 = 13<<3
    shr(bits) 有符号右移 (相当于Java >>) 13 shr 3 = 13>>3
    ushr(bits) 无符号右移(相当于Java >>>) 13 ushr 3 = 13>>>3
    and(bits) 按位与 13 and 3 = 13&3
    or(bits) 按位或 13 or 3 =13|3
    xor(bits) 按位异或 13 xor 3 = 13^3
    inv() 按位取反 13.inv() =~13

    三.函数,lambda表达式,高阶函数。

    kotlin函数参数是用 Pascal 符号定义的 name:type。参数之间用逗号隔开,每个参数必须指明类型,如果有可变参数需要放到最后一个并用vararg修饰。函数参数可以有默认参数,函数都是有返回值的。

    fun read(b: Array<Byte>, offset: Int = 0, len: Int = b.size() ):Unit{
    //第一个参数是Byte数组,第二个参数是 offset,有默认值 0,第三个参数 len 默认值 b.size()
    //返回值为 Unit 相当于 java 的 Void.
    }
    

    Kotlin 中可以在文件根级声明函数而无需创建一个类来持有函数就象标准的扩展函数。

    kotlin 中可用inline 来标识一个函数是内联的。一般比较常用短小的方法指定为内联的,因为实际编译器会把内联的方法体直接嵌到调用处。

    Kotlin 可以声明局部函数,局部函数可作为成员函数或扩展函数,比如在另一个函数使用另一函数。局部函数可以访问外部函数的局部变量(比如闭包),它甚至可以返回到外部函数

    就象 java8,c#一样 kotlin 支持lambda表达式,即:参数在 -> 前面声明(参数类型可以省略)函数体在 -> 之后。

    kotlin 支持高阶函数即将函数作为参数或返回一个函数,称为高阶函数:如下示例:

    fun <T> lock(lock: Lock, body: () -> T): T {  
        lock.lock()  
      try {  
        return body()  
      }finally {  
        lock.unlock()  
      }  
    } 
    //函数表达式
    fun toBeSynchronized() = sharedResource.operation()  
    //以下是 lock 高阶函数的三种调用方式。
    
    val r=lock(lock,:: toBeSynchronized)
    //或直接lambda表达式
    val r=lock (lock,{sharedResource.operation()})
    //或,如果函数参数是最后一个参数可提至调用的()之外。
    val r=lock (lock) {   sharedResource.operation() }
    

    在 kotlin 中有需要有用的高阶函数如:apply,also,with,run,let,repeat等,以下给出定义就不具体一个个讲解了。

    /**
     * Calls the specified function [block] and returns its result.
     */
    public inline fun <R> run(block: () -> R): R = block()
    
    /**
     * Calls the specified function [block] with `this` value as its receiver and returns its result.
     */
    public inline fun <T, R> T.run(block: T.() -> R): R = block()
    
    /**
     * Calls the specified function [block] with the given [receiver] as its receiver and returns its result.
     */
    public inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()
    
    /**
     * Calls the specified function [block] with `this` value as its receiver and returns `this` value.
     */
    public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
    
    /**
     * Calls the specified function [block] with `this` value as its argument and returns `this` value.
     */
    public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }
    
    /**
     * Calls the specified function [block] with `this` value as its argument and returns its result.
     */
    public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
    
    /**
     * Executes the given function [action] specified number of [times].
     * A zero-based index of current iteration is passed as a parameter to [action].
     */
    public inline fun repeat(times: Int, action: (Int) -> Unit) {
        for (index in 0..times - 1) {
            action(index)
        }
    }
    

    四.类,对象

    在Kotlin中,一个类有一个primary constructor,一个或多个secondary constructors。primary constructor 属于类头部分,它在类名之后。下面我给出一个定义可能复杂的类来说明 kotlin 类与 java 类的区别。

    class Person(var firstName: String, var lastName: String):Any() {
        var fullName: String
        var sex: Int=0
    
        @field:JvmField  //此注解不会在字节码中生成get set 方法。
        var address:String?=null
    
        //这是一个二级构造器,类似的可以写多个
        constructor(firstName: String, lastName: String, sex: Int) : this(firstName, lastName) {
            this.sex = sex
        }
    
        init { //初始块,因为一级构造器不能用语句,只能放在初始化块中来做些额外的事
            fullName = firstName + " " + lastName
        }
    
        fun introduce(context: Context) {
            var sexism=if (sex== SEX_BOY)"boy"else "girl"
            print("I am "+fullName+",my sex is "+sexism+".and my first name is "+firstName)
        }
    
        //以下是外部类Person 的伴生对象,外部类可直接当java中的静态方法和变量来使用其内容。
        companion object {
            val SEX_BOY = 1
            val SEX_GIRL = 2
    
            fun boy(firstName: String,lastName: String):Person {
                return Person(firstName,lastName, SEX_BOY)
            }
    
            @JvmStatic  //有此注解,将会在外部类的字节码中生在相应的静态方法
            fun girl(firstName: String,lastName: String):Person {
                return Person(firstName,lastName, SEX_GIRL)
            }
        }
    }
    

    上面的 Person 类继承于Any基类,具有一个一级构造器(它省略了constructor关键字,有注解修饰时才必须要),它的两个参数直接会生成对应类的成员变量,相当于隐式声明并赋值了。一级构造器不能有语句,但是可在类的初始化块init中访问并做些处理。此类有一个二级构造器多了个参数 sex.

    在kotlin是没有java 中的静态方法和变量这些概念的,因为被认为不必要。当然在kotlin中有相对应的解决方案。其中一中就是伴生对象,见上面的companion object,如果要创建一个 person 对象可用Person.boy("firsN","lastN"),伴生对象的存在就象其外部类在java中的静态方法和变量一样。

    kotlin 类中也可声明一个类部类和java一样,只不过默认是不引用外部类的,如果要引用外部类需要加个inner来修饰内部类。

    kotlin 中对象的创建不需要 new 关键字。直接 var person=Person("first","last",Person. SEX_BOY)

    另外可用object :来创建匿名对象,一般用在想要创建一个接口的内联实现,或者扩展另一个类时如下:

    recycler.adapter = object : RecyclerView.Adapter() {
        override fun onBindViewHolder(holder: RecyclerView.ViewHolder?, position: Int) {
        }
        override fun getItemCount(): Int {
        }
        override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder {
        }
    }
    //也可创建一个不存在的类,并使用它如下
    val newObj = object {
        var x = "a"
        var y = "b"
    }
    Log.d(tag, "x:${newObj.x}, y:${newObj.y}")
    

    上面说到伴生对象可以为其外部类提供类似 java 静态的调用,除此之外我们可以用object来直接声明一个单例的类当作 java 中的静态类来使用,如下我们创建一个简单的 ViewUtils,那么使用时就可直接用ViewUtils.view(...)等和java静态Utils类一样:

    //KViewUtils.kt
    object ViewUtils {
        inline fun <T : View> view(root: View, id: Int) = root.findViewById(id) as T
        
        fun <T : View> view(root: View, cls: Class<T>): T? {
            if (cls.isAssignableFrom(root.javaClass)) {
                return root as T
            } else {
                if (root is ViewGroup) {
                    for (i in 0 until root.childCount) {
                        var result = view(root.getChildAt(i), cls)
                        if (result != null) {
                            return result
                        }
                    }
                }
            }
            return null
        }
    
        inline fun background(v: View, d: Drawable?) {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
                v.setBackgroundDrawable(d)
            } else {
                v.background = d
            }
        }
    }
    
    在kotlin 文件中可直接调用 ViewUtils.background(textView,colorDrawable)
    而在 java 中需要用ViewUtils.INSTANCE.background
    如果希望在 java 中也可调用 ViewUtils.background 需要将此方法添加@jvmField 注解
    

    kotlin扩展函数特性可对任何类扩展方法和属性,它是静态解析的,并未对原类添加函数或属性,对类本身没有任何影响,如下列我们给任意对象扩展一个toString(kotlin 本身没有)方法,那么任何对象都可调用 toString 了。

    fun Any?.toString():String{
      if(this==null) return "null"
      return ""+this::class
    }
    

    为了练手本人特意完全用 kotlin 编写了一个 UI容器组件的library和example工程 见 WidgetLayout-kotlin

    相关文章

      网友评论

        本文标题:kotlin 之于 java 的差异

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