我有这样的课程:
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的子类都会定义一个deserialize()
来反序列化rawData以填充它们的成员变量,并且在它们的构造函数中它们只会super(rawData)
。子类的deserialize
函数将自动执行。
错误讯息:
Child.java:5: error: cannot assign a value to final variable firstByte
firstByte = rawData.getFirst();
但是,这个Java不允许我将成员变量定义为final
这很奇怪,因为我正在构造函数中进行初始化。执行相同功能但允许成员为final
的最佳方法是什么?
答案 0 :(得分:4)
当你打电话给一个超级构造函数时," super"正在构建对象的一部分,而" sub"对象的一部分是完整的废土地;你可能不会读或写它;实际上" sub"部分甚至可能在概念上不存在。
调用抽象方法的超级构造函数是一个非常糟糕的主意,因为它可能依赖于尚未存在的子类状态。
答案 1 :(得分:2)
最终变量只能分配一次值。编译器不会检查方法是否只被调用一次;因此,最终变量不能在方法中分配。 (在deserialize
方法的情况下,编译器无法确定该方法是否只被调用一次,即使它想要。该方法受到保护,并且没有任何东西阻止子类调用它两次。)因此,您只能在构造函数中分配最终成员变量(或在定义它们时立即初始化它们)。
解决方案是使变量不是最终的。您仍然可以通过限制对变量的访问(使其成为私有)来确保您的类的实例是不可变的,并且只为它提供一个getter(如果您需要它在类之外的值)。
另请注意,最好不要从构造函数中调用方法,尤其是可以由子类重写的方法。虽然如果你不依赖于对象的状态它现在可能会工作,你必须意识到当调用方法时,对象还没有完全构造。如果您(或其他人)希望将来修改该方法,您可能已经忘记了这一点并引入了难以发现的错误。例如,以下小程序:
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
尽管两次打印相同的最终字符串引用。
所以是的,你应该在每个子类的构造函数中而不是在超类构造函数中调用你的deserialize
方法,这样每个构造函数只构造它所知道的对象的一部分。
答案 2 :(得分:0)
就编译错误而言,您无法将final
的赋值委托给另一种方法。您必须在构造函数中执行赋值,或者使用声明内联。
public Child(Raw rawdData) {
super(rawData);
firstByte = rawData.getFirst();
}
正如bayou.io所说,从构造函数中调用抽象方法是一个非常非常糟糕的主意。
答案 3 :(得分:0)
即使您从构造函数中调用反序列化。您无法真正强制执行仅从构造函数调用反序列化。
java编译器知道这一点,这就是它抱怨的原因。最终成员变量只能在初始化时或在构造函数内分配。
希望这有帮助。