在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"
}
我不喜欢这两种方法。正确的做法是什么?
答案 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: String
与get()
一起使用将不会生成该字段,只是产生一个方法。此getter函数由Kotlin生成,我相信字段的丢失是由于val
声明中的初始赋值丢失而引起的,但是我找不到诸如Getters and Setters in Kotlin这样的真实文档。只是直接使用这个结论。
因此,这似乎是对final static
进行修改的旁路。
当不可变字段引用可变字段时,会出现问题。 val
不可重新分配,但它引用了一个可重新分配的字段var
,这导致对val
的修改。