首页 文章

Kotlin定制的Getter使`val`和`var`混乱?

提问于
浏览
3

在Kotlin中, var 是可变的, val 应该只分配一次 .

但是,请考虑以下示例中的 val foo

var counter = 0

val foo: String
  get(){
    counter++
    return "val$counter"
  }

fun main(): String {
    val a = foo
    val b = foo
    val c = foo
    return "we got: $a $b $c"
    // output: we got: val1 val2 val3
}

每次我们尝试访问 foo 时都会执行 get() 方法,从而产生 different values for val .

由于 foo 的值正在变化,我尝试使用 var . 然后编译器抱怨"Property must be initialized" . 所以我必须给它一个默认值:

var foo: String = "default value that will never be used"
  get(){
    counter++
    return "val$counter"
  }

我不喜欢这里的任何一种方法 . 什么是正确的做法?

2 回答

  • 2

    在Kotlin中,var是可变的,val应该只分配一次 .

    对于局部变量,是的 . 对于属性,不是真的: val 表示"only has a getter", var 表示"has both a getter and a setter" . 这个getter(和setter)可以做任何事情 . 例如,您每次都可以返回一个随机值 .

    一个例外是重新分配 val 的支持字段:

    val foo: Int = 0
      get(){
        field++
        return field
      }
    

    不会编译 .

  • 1

    这已在YouTrack中报告为KT-16681,"kotlin allows mutating the field of read-only property" .

    正如您在KT-16681的回复中所看到的,自定义getter被编译成另一个函数,这使得字段 foo 和方法 getFoo() 成为两个不相关的东西 .

    同样来自KT-16681的回复,这种违规(通过支持字段重新分配只读属性)将产生自Kotlin 1.3以来的错误 .

    Update: 在评论中,原始海报提到 KT-16681 与此问题不同 . 但是,受到这个问题的启发,我们可以通过 Tools -> Kotlin -> Show Kotlin Bytecode (删除元数据等)看到Kotlin字节码:

    public final class Test53699029Kt {
    
    
      // access flags 0xA
      private static I counter
    
      // access flags 0x19
      public final static getCounter()I
       L0
        LINENUMBER 3 L0
        GETSTATIC Test53699029Kt.counter : I
        IRETURN
       L1
        MAXSTACK = 1
        MAXLOCALS = 0
    
      // access flags 0x19
      public final static setCounter(I)V
       L0
        LINENUMBER 3 L0
        ILOAD 0
        PUTSTATIC Test53699029Kt.counter : I
        RETURN
       L1
        LOCALVARIABLE <set-?> I L0 L1 0
        MAXSTACK = 1
        MAXLOCALS = 1
    
      // access flags 0x19
      public final static getFoo()Ljava/lang/String;
      @Lorg/jetbrains/annotations/NotNull;() // invisible
       L0
        LINENUMBER 7 L0
        GETSTATIC Test53699029Kt.counter : I
        DUP
        ISTORE 0
        ICONST_1
        IADD
        PUTSTATIC Test53699029Kt.counter : I
       L1
        LINENUMBER 8 L1
        NEW java/lang/StringBuilder
        DUP
        INVOKESPECIAL java/lang/StringBuilder.<init> ()V
        LDC "val"
        INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
        GETSTATIC Test53699029Kt.counter : I
        INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;
        INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
        ARETURN
       L2
        MAXSTACK = 2
        MAXLOCALS = 1
    
      // access flags 0x19
      public final static main()V
       L0
        LINENUMBER 12 L0
        INVOKESTATIC Test53699029Kt.getFoo ()Ljava/lang/String;
        ASTORE 0
       L1
        LINENUMBER 13 L1
        INVOKESTATIC Test53699029Kt.getFoo ()Ljava/lang/String;
        ASTORE 1
       L2
        LINENUMBER 14 L2
        INVOKESTATIC Test53699029Kt.getFoo ()Ljava/lang/String;
        ASTORE 2
       L3
        LINENUMBER 15 L3
        NEW java/lang/StringBuilder
        DUP
        INVOKESPECIAL java/lang/StringBuilder.<init> ()V
        LDC "we got: "
        INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
        ALOAD 0
        INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
        BIPUSH 32
        INVOKEVIRTUAL java/lang/StringBuilder.append (C)Ljava/lang/StringBuilder;
        ALOAD 1
        INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
        BIPUSH 32
        INVOKEVIRTUAL java/lang/StringBuilder.append (C)Ljava/lang/StringBuilder;
        ALOAD 2
        INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
        INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
        ASTORE 3
       L4
        GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
        ALOAD 3
        INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
       L5
       L6
        LINENUMBER 17 L6
        RETURN
       L7
        LOCALVARIABLE c Ljava/lang/String; L3 L7 2
        LOCALVARIABLE b Ljava/lang/String; L2 L7 1
        LOCALVARIABLE a Ljava/lang/String; L1 L7 0
        MAXSTACK = 2
        MAXLOCALS = 4
    
      // access flags 0x1009
      public static synthetic main([Ljava/lang/String;)V
        INVOKESTATIC Test53699029Kt.main ()V
        RETURN
        MAXSTACK = 0
        MAXLOCALS = 1
    

    正如我们所看到的, foo 没有字段,只有 getFoo() ,比较正常的 val 声明:

    public final class Test53699029Kt {
    
    
      // access flags 0xA
      private static I counter
    
      // access flags 0x19
      public final static getCounter()I
       L0
        LINENUMBER 1 L0
        GETSTATIC Test53699029Kt.counter : I
        IRETURN
       L1
        MAXSTACK = 1
        MAXLOCALS = 0
    
      // access flags 0x19
      public final static setCounter(I)V
       L0
        LINENUMBER 1 L0
        ILOAD 0
        PUTSTATIC Test53699029Kt.counter : I
        RETURN
       L1
        LOCALVARIABLE <set-?> I L0 L1 0
        MAXSTACK = 1
        MAXLOCALS = 1
    
      // access flags 0x1A
      private final static Ljava/lang/String; foo = "aaa"
      @Lorg/jetbrains/annotations/NotNull;() // invisible
    
      // access flags 0x19
      public final static getFoo()Ljava/lang/String;
      @Lorg/jetbrains/annotations/NotNull;() // invisible
       L0
        LINENUMBER 3 L0
        GETSTATIC Test53699029Kt.foo : Ljava/lang/String;
        ARETURN
       L1
        MAXSTACK = 1
        MAXLOCALS = 0
    
      // access flags 0x19
      public final static main()V
       L0
        LINENUMBER 6 L0
        GETSTATIC Test53699029Kt.foo : Ljava/lang/String;
        ASTORE 0
       L1
        LINENUMBER 7 L1
        GETSTATIC Test53699029Kt.foo : Ljava/lang/String;
        ASTORE 1
       L2
        LINENUMBER 8 L2
        GETSTATIC Test53699029Kt.foo : Ljava/lang/String;
        ASTORE 2
       L3
        LINENUMBER 9 L3
        NEW java/lang/StringBuilder
        DUP
        INVOKESPECIAL java/lang/StringBuilder.<init> ()V
        LDC "we got: "
        INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
        ALOAD 0
        INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
        BIPUSH 32
        INVOKEVIRTUAL java/lang/StringBuilder.append (C)Ljava/lang/StringBuilder;
        ALOAD 1
        INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
        BIPUSH 32
        INVOKEVIRTUAL java/lang/StringBuilder.append (C)Ljava/lang/StringBuilder;
        ALOAD 2
        INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
        INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
        ASTORE 3
       L4
        GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
        ALOAD 3
        INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
       L5
       L6
        LINENUMBER 11 L6
        RETURN
       L7
        LOCALVARIABLE c Ljava/lang/String; L3 L7 2
        LOCALVARIABLE b Ljava/lang/String; L2 L7 1
        LOCALVARIABLE a Ljava/lang/String; L1 L7 0
        MAXSTACK = 2
        MAXLOCALS = 4
    
      // access flags 0x1009
      public static synthetic main([Ljava/lang/String;)V
        INVOKESTATIC Test53699029Kt.main ()V
        RETURN
        MAXSTACK = 0
        MAXLOCALS = 1
    
      // access flags 0x8
      static <clinit>()V
       L0
        LINENUMBER 3 L0
        LDC "aaa"
        PUTSTATIC Test53699029Kt.foo : Ljava/lang/String;
        RETURN
        MAXSTACK = 1
        MAXLOCALS = 0
    

    使用 val foo = "aaa" 将生成一个普通的 final static String foo 字段和 final static String getFoo() 方法,但使用带有 get()val foo: String 将不会生成该字段,只需生成一个方法 . 这个getter函数是由Kotlin生成的,我相信丢失的字段来自于 val 的声明中丢失的初始赋值,但是我找不到真正的文档,就像Getters and Setters in Kotlin这样直接使用这个结论 .

    所以,这似乎是修改 final static 的旁路 .

    当不可变字段引用可变字段时会出现问题 . val 不可重新分配,但它引用了 var ,一个可重新分配的字段,这导致 val 的修改 .

相关问题