Java 在构造函数调用的函数中定义成员时的不变性

Java immutability when defining members in a function called by constructor

我有 class 这样的:

abstract class Parent {
  protected Parent(Raw rawData) {
     deserialize(rawData);
  }
  protected abstract void deserialize(Raw rawData);
}

class Child extends Parent {
   final byte firstByte;
   public Child(Raw rawdData) { super(rawData); }
   protected void deserialize(Raw rawData) { 
     firstByte = rawData.getFirst();
   }
}

所以基本上任何扩展 Parent 的子 class 都会定义一个 deserialize() 来反序列化 rawData 以填充它们的成员变量,并且在它们的构造函数中它们只会做 super(rawData)。子class的deserialize函数会自动执行

错误信息:

Child.java:5: error: cannot assign a value to final variable firstByte
 firstByte = rawData.getFirst();

但是这样做 Java 不允许我将成员变量定义为 final 这很奇怪,因为我正在构造函数中进行初始化。执行相同功能但允许成员成为 final 的最佳方法是什么?

当你调用超级构造函数时,对象的"super"部分正在构造,而对象的"sub"部分完全是一片荒地;你不能读或写它;实际上 "sub" 部分在概念上什至不存在。

调用抽象方法的超级构造函数是一个非常糟糕的主意,因为它可能依赖于尚不存在的子类状态。

就编译错误而言,您不能将 final 的赋值委托给另一个方法。您必须在构造函数中进行赋值或与声明内联。

    public Child(Raw rawdData) { 
        super(rawData);
        firstByte = rawData.getFirst();
    }

正如 bayou.io 所述,从构造函数调用抽象方法是一个非常非常糟糕的主意。

即使您从构造函数调用反序列化。您不能真正强制仅从构造函数调用反序列化。

java 编译器知道这一点,这就是它抱怨的原因。 final 成员变量只能在初始化时或在构造函数中赋值。

希望对您有所帮助。

final 变量只能赋值一次。编译器不会检查方法是否只被调用一次;因此,final 变量不能在方法中赋值。 (在 deserialize 方法的情况下,编译器无法确定该方法是否只被调用一次,即使它想要调用。该方法是受保护的,没有什么可以阻止 subclass调用它两次。)因此,您只能在构造函数中分配给最终成员变量(或在定义它们时立即初始化它们)。

解决办法是让变量不是final。您仍然可以通过限制对变量的访问(将其设为私有)来确保 class 的实例是不可变的,并且只为其提供 getter (如果您需要 [=26= 之外的值) ]).

另请注意,最好不要从构造函数调用方法,尤其是可以被 subclasses 覆盖的方法。虽然如果不依赖于对象的状态它现在可能会起作用,但您必须意识到调用方法时对象还没有完全构造好。如果您(或其他人)将来想修改该方法,您可能已经忘记了这一点并引入了难以发现的错误。比如下面这个小程序:

class Bar {
    public Bar() {
        f();
    }    
    protected void f() { }
}

public class Foo extends Bar  {
    final String a;
    public Foo() {
        a = "Hello";
    }
    protected void f() {
        System.out.println(a);
    }
    public static void main(String... args) {
        Foo foo = new Foo();
        System.out.println(foo.a);
    }
}

输出

null
Hello

尽管相同的最终字符串引用被打印了两次。

所以是的,你应该在每个 subclasses 的构造函数中而不是在 superclass 构造函数中调用你的 deserialize 方法,这样每个构造函数只构造它知道的对象的一部分。