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
方法,这样每个构造函数只构造它知道的对象的一部分。
我有 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
方法,这样每个构造函数只构造它知道的对象的一部分。