浅谈Kotlin语法篇之变量和常量(二)

3,742

这次所说的是Kotlin的变量和常量,主要会对以下内容做介绍:

  • 1、变量基本定义
  • 2、var和val的区别
  • 3、智能类型推断
  • 4、自定义属性访问器
  • 5、var是可变的而val一定是不可变的吗

一、Kotlin与Java中变量和常量 使用对比

  • 1、在Java中定义一个变量和常量
public String name = "Mikyou";//定义变量
public final int age = 18;//final定义常量
  • 2、在Kotlin中定义一个变量和常量
var name: String = "Mikyou"
val age: Int = 18

或者

var name = "Mikyou"
val age = 18

总结: 由以上的对比可得出:

  • 1、Kotlin定义一个变量和常量比Java更简洁和灵活
  • 2、Kotlin中拥有类型推断的机制,当一个变量或者常量初始化的时候可以省略类型的声明,它会根据初始化值的类型来作为变量的类型。
  • 3、Kotlin声明变量和常量都必须使用var(变量),val(常量)关键字开头,然后再是名称,最后才是类型(如果有初始化值类型可以直接省略)
  • 4、Kotlin相比Java默认的访问修饰符是public,而Java中是default
  • 5、Java中一般都是以类型开头然后再是名称,并且类型是不可以省略的;这样的操作在Kotlin中是不行的。因为在Kotlin中类型是可以省略的,也就是类型相对比较弱化的,所以Kotlin会把类型放在最后,一般含有初始化值就会把在后面的类型声明省略。

二、Kotlin的变量和常量用法

var name: String = "Mikyou"
var address: String?//如果这个变量没有初始化,那么需要显示声明类型并且指明类型是否可null
address = "NanJing"
val age: Int = 18

或者

var name = "Mikyou"
val age = 18
  • 1、变量和常量声明必须以“var”和“val”关键字开头。
  • 2、 变量和常量的结构: (var 或者 val) 名称 :(分割逗号) 变量类型 = 初始化值。
  • 3、 智能类型转换(编译器提示为smart cast),如果变量或常量含有初始值可以省略类型,编译器会默认分析出将初始化值的类型作为变量和常量类型。
  • 4、如果变量没有初始化,那么需要显示声明类型并且需要指明类型是否可null。

三、Kotlin的自定义属性访问器

在Kotlin中属性是头等特性,它习惯于用一个属性去替代Java中的字段和setter,getter方法。而Kotlin中的set、get访问器相当于Java中的setter,getter方法。Kotlin有个新的特性就是可以去定义一个类中属性的访问器包括setter,getter访问器。该特性十分适用于需要经过多个属性逻辑计算得出的一个新的属性。那么逻辑计算的过程操作不需要像Java一样开一个方法来实现。可以直接在属性访问器中进行逻辑运算。

  • 1、自定义get属性访问器

在Java中实现:

public class Rectangle {

    private float width;
    private float height;

    public Rectangle(float width, float height) {
        this.width = width;
        this.height = height;
    }

    public boolean isSquare() {//在java中实现一个方法进行相关的逻辑计算
        return width == height;
    }

}

在Kotlin中实现:

class Rectangle(width: Float, height: Float) {
    val isSquare: Boolean//在Kotlin中只需要一个属性就能解决,重新定义了isSquare的getter属性访问器,访问器中可以写入具体逻辑代码。
        get() {
            return width == height
        }
}
  • 2、自定义set属性访问器

在Java中实现Android中的一个自定义View中的setter方法。

public class RoundImageView extends ImageView {
        ...
    
    public void setBorderColor(int color) {
        mColor = color;
        invalidate();
    }
        ...
}

在Kotlin中实现Android中的一个自定义View中的set属性访问器。

class RoundImageView @JvmOverloads constructor(context: Context, attributeSet: AttributeSet? = null, defAttrStyle: Int = 0)
    : ImageView(context, attributeSet, defAttrStyle) {

    var mBorderColor: Int
        set(value) {//自定义set属性访问器
            field = value
            invalidate()
        }
}

四、Kotlin中的var和val区别

  • 1、var(来自于variable)可变引用。并且被它修饰的变量的值是可以改变,具有可读和可写权限,相当于Java中非final的变量。
  • 2、val(来自于value)不可变引用。并且被它修饰的变量的值一般情况初始化一遍后期不能再次否则会抛出编译异常(这句话有待商榷,这个问题的讨论和求证请看Kotlin中val不可变与可读的讨论),相当于Java中的final修饰的常量。
  • 3、在Kotlin开发过程中尽可能多的使用val关键字声明所有的Kotlin的变量,仅仅在一些特殊情况使用var,我们都知道在Kotlin中函数是头等公民,并且加入很多函数式编程内容,而使用不可变的引用的变量使得更加接近函数式编程的风格。
  • 4、需要注意的是val引用的本身是不可变的,但是它指向的对象可能是可变的(这个问题的讨论和求证请查看Kotlin中val不可变与可读的讨论)。
  • 5、var关键字允许改变自己的值,但是它的类型却是无法改变的。

五、Kotlin中val不可变与可读的讨论

由于Kotlin是一门新的语言,我们在学习的过程中经常习惯性的去记住一些所谓定理,而没有去真正深究为什么是这样。比如拿今天的议题来说,相信很多的人都这样认为(刚开始包括我自己)var修饰的变量是可变的,而val修饰的变量是不可变的。然后学完Kotlin的自定义属性访问器就会觉得是有问题的。然后去看一些国外的博客,虽然有讲述但是看完后更让我懵逼的是val修饰的变量的值是可以变化的可读的,并且底层的引用也是变化的。前面那句确实可以理解,后面一句还是保留意见。于是乎就开始写demo认证。 引用国外博客的一句原话"But can we say that val guarantees that underlying reference to the object is immutable? No…" 国外博客源地址

  • 1、val不可变与可读的假设

假设一: 在Kotlin中的val修饰的变量不能说不可变的,只能说val修饰变量的权限是可读的。

假设二: 在Koltin中的val修饰的变量的引用是不可变的,但是指向的对象是可变的。

  • 2、 val不可变与可读的论证

论证假设一: 我们在Kotlin的开发过程中,一般是使用了val修饰的变量就不能再次被赋值了,否则就会抛出编译时的异常。但是不能再次被赋值不代表它是不可变的。因为Kotlin与Java不一样的是多了个自定义属性访问器的特性。这个特性貌似就和val修饰的变量是不可变的矛盾了。而Java中不存在这个问题,如果使用了final修饰的变量,没有所谓自定义访问器概念。

fun main(args: Array<String>) {
    val name = "Hello Kotlin"
    name = "Hello Java"
}

print error:

Error:(8, 5) Kotlin: Val cannot be reassigned

定义get属性访问器例子

class RandomNum {
    val num: Int
        get() = Random().nextInt()
}

fun main(args: Array<String>) {
    println("the num is ${RandomNum().num}")
}

print result:

the num is -1411951962
the num is -1719429461

总结: 由以上的例子可以说明假设一是成立的,在Kotlin中的val修饰的变量不能说是不可变的,而只能说仅仅具有可读权限。

论证假设二: 由论证一,我们知道Kotlin的val修饰的变量是可变的,那它的底层引用是否是可变的呢?国外一篇博客说引用是可变的,真是这样吗?通过一个例子来说明。

User类:

package com.mikyou.kotlin.valtest
open class User() {
    var name: String? = "test"
    var age: Int = 18
    var career: String? = "Student"
}

Student类:

class Student() : User()

Teacher类:

class Teacher() : User()

Customer接口:

interface Customer {
    val user: User//注意: 这里是个val修饰的User实例引用
}

VipCustomer实现类:

class VipCustomer : Customer {
    override val user: User
        get() {
//            return Student().apply {
//                name = "mikyou"
//                age = 18
//                career = "HighStudent"
//            }
            return Teacher().apply {
                //看到这里很多人肯定认为,底层引用也会发生改变,毕竟Student, Teacher是不同的对象了。但是事实是这样的吗?
                name = "youkmi"
                age = 28
                career = "HighTeacher"
            }
        }
}

测试:

fun main(args: Array<String>) = VipCustomer().user.run {
    println("my name is $name, I'm $age years old, my career is $career, my unique hash code is ${hashCode()} ")
}

print result:

my name is mikyou, I'm 18 years old, my career is HighStudent, my unique hash code is 666988784 
	
//切换到Teacher
my name is youkmi, I'm 28 years old, my career is HighTeacher, my unique hash code is 666988784

总结: 由以上的例子可以说明假设二是成立的,两个不同的对象hashCode一样说明,user的引用地址不变的,而变化的是引用指向的对象是可以变化的。

到这里Kotlin入门基础语法就结束了,下一篇将会继续深入Kotlin相关内容

欢迎关注Kotlin开发者联盟,这里有最新Kotlin技术文章,每周会不定期翻译一篇Kotlin国外技术文章。如果你也喜欢Kotlin,欢迎加入我们~~~