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

时间:2015-06-17 15:08:13

标签: java class design-patterns immutability

我有这样的课程:

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的最佳方法是什么?

4 个答案:

答案 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编译器知道这一点,这就是它抱怨的原因。最终成员变量只能在初始化时或在构造函数内分配。

希望这有帮助。