代码之家  ›  专栏  ›  技术社区  ›  Tom Tresansky

Java中的最终字段初始化问题:避免使用多个构造函数复制代码

  •  2
  • Tom Tresansky  · 技术社区  · 15 年前

    我有一个班级,有一些私人的最后领域:

    public class ClassA {
      private final Object field1;
      private final Object field2;
      ...
    }
    

    该类有几个不同的构造函数,具有各种参数:

    public ClassA(Object arg1, Object arg2);
    public ClassA(int arg1, String arg2, boolean arg3);
    

    这些构造函数计算要放入最终字段中的值。

    理想情况下,我想这样做:

    public ClassA(Object arg1, Object arg2) {
      ... // error check params
      Object value1 = ... // calculate value 1 based on args
      Object value2 = ... // calculate value 2 based on args
    
      init(value1, value2);
    }
    
    public ClassA(int arg1, String arg2, boolean arg3) {
      ... // error check params
      Object value1 = ... // calculate value 1 based on args
      Object value2 = ... // calculate value 2 based on args
    
      init(value1, value2);
    }
    
    private void init(Object arg1, Object arg2) {
      ... // error checks on combination of calculated arg1 and arg2 values
      ... // in reality there are more than 2 args, and this logic is fairly complex
      field1 = arg1;
      field2 = arg2;
      ... // other common initialization code, depends on field1 and field2
    }
    

    以便重用分配和通用初始化代码。但是,由于这些字段是最终字段,因此只能在构造函数调用中分配它们。

    在这种情况下,不能脱离使用公共构造函数使对象成为对象,因此任何类型的工厂方法都是不可能的。最好将arg1和arg2值的组合错误检查保留在同一块中,作为它们对classa字段的赋值。否则我会分开 init() 分为两个功能,一个是最终字段的预分配,一个是最终字段的后分配,使我的构造函数看起来像:

    public ClassA(int arg1, String arg2, boolean arg3) {
      ... // error check params
      Object value1 = ... // calculate value 1 based on args
      Object value2 = ... // calculate value 2 based on args
    
      preinit(value1, value2);  // error checks combination of values
      field1 = value1;
      field2 = value2;
      postinit();  // other common initialization code
    }
    

    建议?有什么办法可以避免这个,还是我一直在分裂 () 功能?

    6 回复  |  直到 15 年前
        1
  •  3
  •   nanda    15 年前

    首先,我将尝试查看builder模式,但是如果您坚持将其作为构造函数,并且字段应该是最终的,请使用私有构造函数:

    public class ClassA {
        private final Object field1;
        private final Object field2;
    
        public ClassA(Object arg1, Object arg2) {
            this(calc1(arg1, arg2), calc2(arg1, arg2), true);
        }
    
        public ClassA(int arg1, String arg2, boolean arg3) {
            this(calc1(arg1, arg2, arg3), calc2(arg1, arg2, arg3), true);
        }
    
        private ClassA(Object arg1, Object arg2, boolean a) {
            field1 = arg1;
            field2 = arg2;
        }
    
        private static Object calc1(int arg1, String arg2, boolean arg3) {
            return ... // calculate value 1 based on args
        }
    
        private static Object calc2(int arg1, String arg2, boolean arg3) {
            return ... // calculate value 2 based on args
        }
    
        private static Object calc1(Object arg1, Object arg2) {
            return ... // calculate value 1 based on args
        }
    
        private static Object calc2(Object arg1, Object arg2) {
            return ... // calculate value 2 based on args
        }
    
    }
    
        2
  •  3
  •   Péter Török    15 年前

    为什么不创建对象呢 value1 value2 在静态工厂方法中?然后,您可以用不同的工厂方法替换当前的构造函数,用一个(私有)构造函数来完成现在所做的工作。 init :

    public static createWithObjectParams(Object arg1, Object arg2) {
      ... // error check params
      Object value1 = ... // calculate value 1 based on args
      Object value2 = ... // calculate value 2 based on args
    
      return new ClassA(value1, value2);
    }
    
    public static createWithPrimitiveParams(int arg1, String arg2, boolean arg3) {
      ... // error check params
      Object value1 = ... // calculate value 1 based on args
      Object value2 = ... // calculate value 2 based on args
    
      return new ClassA(value1, value2);
    }
    
    private ClassA(Object arg1, Object arg2) {
      ... // error checks on combination of calculated arg1 and arg2 values
      ... // in reality there are more than 2 args, and this logic is fairly complex
      field1 = arg1;
      field2 = arg2;
      ... // other common initialization code, depends on field1 and field2
    }
    
        3
  •  1
  •   Justin Ardini    15 年前

    而不是单独的 init 方法,您可以将构造函数链接在一起。所以,计算 field field2 ,然后调用初始化它们的构造函数。基本上,更换 初始化 用:

    private classA(Object arg1, Object arg2) {
      ... // error checks on combination of calculated arg1 and arg2 values
      ... // in reality there are more than 2 args, and this logic is fairly complex
      field1 = arg1;
      field2 = arg2;
      ... // other common initialization code, depends on field1 and field2
    }
    

    当然,只有1个构造函数和2个构造函数 Objects 作为参数。

        4
  •  1
  •   mdma    15 年前

    您可以将所有内容下推到2个参数的构造函数,这样它是唯一真正初始化最终字段并进行验证的构造函数。

    例如。

    public ClassA(int arg1, String arg2, boolean arg3) {
       this(deriveO1(arg1, arg2, arg3));
       this(deriveO2(arg1, arg2, arg3));
    }
    
    public ClassA(Object arg1, Object arg2) {
       field1 = arg1;
       field2 = arg2;
       // do initialization checks, you can have this in a separate method, or check directly here.   
    }
    

    如果不希望直接调用两个对象版本(例如,假设要在分配前进行预验证),则声明一个“dummy”参数。这感觉有点错误,但是如果您想让公共和私有的构造函数在语义上具有相同的参数(它们必须在语法上是不同的),那么这实际上是唯一的选择。

    public ClassA(int arg1, String arg2, boolean arg3) {
       Object o1 = ...; 
       Object o2 = ...;
       this(o1, o2, false);
    }
    
    public ClassA(Object arg1, Object arg2) {
       this(arg1, arg2, false);
    }
    
    private ClassA(Object arg1, Object arg2, boolean dummy) {
       field1 = arg1;
       field2 = arg2;
       // do initialization checks, you can have this in a separate method, or check directly here.   
    }
    
        5
  •  0
  •   starblue    15 年前

    使用静态工厂方法和一个简单的私有构造函数,它只将值赋给字段,如果需要,还可以进行其他初始化。

        6
  •  0
  •   helpermethod    15 年前

    不知道这是否是一个选项,但是您可以使用依赖项注入,即创建您的字段引用到其他地方的对象,并在构造函数中传递它们。这使得你的课程更容易测试,更灵活。

    public class ClassA {
      private final Object field1;
      private final Object field2;
    
      public Class A(Object field1, Object field2) {
            this.field1 = field1;
            this.field2 = field2;
      }
    }
    
    
    public class AppFactory {
        private AppFactory() {}
    
        public static ClassA newInstance(Object arg1, Object arg2) {
            // create the fields there
            ...
    
            // pass them in
            return new ClassA(field1, field2);         
        }
    
        public static ClassA newInstance(int arg1, String arg2, boolean arg3) {
            // create fields there, in a different way
    
            // pass them in
            return new ClassA(field1, field2);
        }
    }
    

    这样,您可以:

    • 通过其父类型引用构造函数参数。
    • 在工厂中,返回ClassA的子类型
    • 为字段传入模拟对象(虚拟对象),以便隔离测试类
    • 更多的好处