Kotlin定制的Getter使val和var令人困惑吗?

时间:2018-12-10 02:59:28

标签: kotlin

在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
}

每次尝试访问get()时都会执行foo方法,从而导致 val的不同值

由于foo的值正在更改,因此我尝试使用var。然后,编译器抱怨“必须初始化属性”。所以我必须给它一个默认值:

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

我不喜欢这两种方法。正确的做法是什么?

2 个答案:

答案 0 :(得分:3)

  

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

对于局部变量,是的。对于属性,不是真的:val表示“只有吸气剂”,var表示“既有吸气剂又有吸气剂”。这个getter(和setter)可以做任何事情。例如,您每次可以只返回一个随机值。

一个例外是重新分配val的后备字段:

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

不会编译。

答案 1 :(得分:2)

这已经在YouTrack中报告为KT-16681,“ kotlin允许更改只读属性的字段”。

正如您在KT-16681的答复中看到的那样,自定义getter被编译为另一个函数,这使字段foo和方法getFoo()成为两个无关的东西。

此外,从KT-16681的答复中来看,自Kotlin 1.3开始,这种违反(通过后备字段重新分配只读属性)将产生错误。

更新:在评论中,原始张贴者提到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()方法,但是将val foo: Stringget()一起使用将不会生成该字段,只是产生一个方法。此getter函数由Kotlin生成,我相信字段的丢失是由于val声明中的初始赋值丢失而引起的,但是我找不到诸如Getters and Setters in Kotlin这样的真实文档。只是直接使用这个结论。

因此,这似乎是对final static进行修改的旁路。

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