kotlin基础 可变变量var、不可变变量val、const val你真的搞懂了吗?

280 阅读4分钟

阅读本文后,您将学习到:

  1. 什么场景下使用var定义可变变量
  2. 什么场景下使用val定义不可变变量
  3. 什么场景下使用const val

可变变量var

使用场景

当我们定义的变量在后续的业务流程中,会随着一定的条件被重新赋值,这时需要把变变量定义为可变变量。

var name = "cola_wang"

if(满足条件) {
    name = "var_cola_wang"
}

定义局部变量

java中定义局部变量:

public void main() {
    string name = "cola_wang";
}

kotlin中定义局部变量

fun main() {
    var name = "cola_wang"
}

定义成员变量

java中定义成员变量:

class Test {
    public String name = "cola_wang";
}

kotlin中定义成员变量:

class Test {
    var name = "cola_wangkai"
}

定义全局变量

java中定义全局变量:

class Test {
    public static String name = "cola_wang"
}

kotlin中定义全局变量:

class Test {
    companion object {
        var name = "cola_wang"
    }
}

显示声明和隐式声明

显示声明可变变量:在定义var变量时可以显示的声明变量的类型。

var name: String = "cola_wang"

隐式声明可变变量:在定义可变变量时可以不声明变量的类型,编译可以根据它的值推导出它的类型。

var name = "cola_wang"  //编译器可以推导出name的类型为String类型

注意:如果定义的可变变量不直接赋值,那么这个可变变量需要显示的声明其类型,因为编译器无法推导出它的类型。

var name = "cola_wang" //定义的变量直接赋值了,编译器能够推导出其类型为String类型

var name  //编译器无法推导出它的类型,报错,提示需要指定类型或赋初始值
name = ""

不可变变量val

使用场景

当我们定义的变量在后续的业务流程中,不会再给他重新赋值,我们需要把它定义为不可变变量。

val name = "cola_wang"

name = "xxxx"  //编译器报错,不能修改

定义局部常量

java中定义局部常量:

public void main() {
    final string name = "cola_wang";
}

kotlin中定义局部常量

fun main() {
    val name = "cola_wang"
}

定义成员常量

java中定义成员常量:

class Test {
    public final String name = "cola_wang";
}

kotlin中定义成员常量:

class Test {
    val name = "cola_wangkai"
}

定义全局常量

java中定义全局常量:

class Test {
    public static final String name = "cola_wang"
}

kotlin中定义全局常量,需要用const修饰:

class Test {
    companion object {
        const val name = "cola_wang"
    }
}

显示声明和隐式声明

显示声明不可变变量:在定义变量时可以显示的声明变量的类型。

val name: String = "cola_wang"

隐式声明不可变变量:在定义不可变变量时可以不声明变量的类型,编译可以根据它的值推导出它的类型。

val name = "cola_wang"  //编译器可以推导出name的类型为String类型

注意:如果定义的不可变变量不直接赋值,那么这个不可变变量需要显示的声明其类型,因为编译器无法推导出它的类型。

val name = "cola_wang" //定义的变量直接赋值了,编译器能够推导出其类型为String类型

val name  //编译器无法推导出它的类型,报错,提示需要指定类型或赋初始值
name = "cola_wang"

思考

构造函数的参数用var/val修饰或不修饰有什么区别

这是一道我在面试迅雷时的面试题,现在记录一下。

先上代码:

class Test(val name: String, age: String) {
    
    init {
        println(name)
        println(age)
    }
    
    fun main() {
        println(name)
        println(age)  //编译报错,无法访问age
    }
}

反编译查看kotlin文件编译后的代码,省略了一些非关键代码。从下面的代码可以看出:

  1. 被val/var修饰后的构造函数参数name,为类Test的成员变量,所以它在类中的任何位置可以被访问到。
  2. 没有被val/var修饰的构造函数参数age,只是方法的一个入参,它只能在构造函数内部或init代码块中被访问。
public final class Test {
   @NotNull
   private final String name;

   public Test(@NotNull String name, @NotNull String age) {
      Intrinsics.checkNotNullParameter(name, "name");
      Intrinsics.checkNotNullParameter(age, "age");
      super();
      this.name = name;
      String var3 = this.name;
      System.out.println(var3);
      System.out.println(age);
   }
}

定义全局常量时,const val和val有什么区别

我们分别定义这两个类型的全局常量:

class Test {
    
    companion object {
        const val name = "cola_wang"
        val age = "20"
    }
}

查看反编译kotlin文件后的代码,总结一下:

  1. const val修饰的变量name被直接声明为Test的static final类型的常量,可以直接使用Test访问name,它在companion中并不存在。
  2. val修饰的变量age被声明为Test的static final类型的常量,它是私有的无法直接访问,在companion中创建了一个get方法来获取这个age变量,这个方法非静态的,所以访问age时,还实例化了Companion。
public final class Test {
   @NotNull
   public static final String name = "cola_wang";
   @NotNull
   private static final String age = "20";
   @NotNull
   public static final Companion Companion = new Companion((DefaultConstructorMarker)null);

   public static final class Companion {
      @NotNull
      public final String getAge() {
         return Test.age;
      }

      private Companion() {
      }
   }
}

除了以上区别,我们自定义一个User类,然后分别用val和const val修饰,看看下面的代码,所以const还有个特性是:它只能修饰基本数据类型和String类型。

class Test {

    companion object {
        const val user1 = User()  //编译报错,提示const只能修饰基本数据类型和String类型
        val user2 = User()  //编译通过
    }
}