Problem with final field initialization in java: avoiding duplication of code using multiple constructors
I have a class with some private final fields:
public class ClassA {
private final Object field1;
private final Object field2;
...
}
The class has several different constructors, with a variety of arguments:
public ClassA(Object arg1, Object arg2);
public ClassA(int arg1, String arg2, boolean arg3);
These constructors calculate the values to put in the final fields.
Ideally, I would like to do something like this:
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
}
In order to reuse the assignments and common initialization code. However, since the fields are final, they can only be assigned in the constructor calls.
Can't get away from using public constructors to make objects the in this case, so any type of factory methods aren't possible. It would be nice to keep the combination error checks on the arg1, arg2 values in the same block as the assignments of them to the ClassA fields. Otherwise I would split init()
into 2 functions, one pre-assignment of final fields and one post assignment of final fields, and make my constructors look like:
public ClassA(int arg1, String arg2, boolean arg3) {
... // error check params
Object value1 = ... // calculate value 1 based on args
Object va开发者_StackOverflow中文版lue2 = ... // calculate value 2 based on args
preinit(value1, value2); // error checks combination of values
field1 = value1;
field2 = value2;
postinit(); // other common initialization code
}
Suggestions? Is there any way to avoid this, or am I stuck splitting the init()
function?
First, I would try to look at builder patter, but if you insist to make it as constructor and the fields should be final, use private constructor:
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
}
}
Why don't you create the objects value1
and value2
in a static factory method? Then you could have distinct factory methods replacing the current constructors, and a single (private) constructor doing what is now done in 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
}
Instead of having a separate init
method, you can instead chain constructors together. So, calculate field
and field2
, then call a constructor that initializes them. Basically, replace init
with:
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
}
Of course, there can only be 1 constructor with 2 Objects
as parameters.
You can push everything down to a 2 arg constructor, so that it is the only constructor that actually initializes the final fields and does validation.
E.g.
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.
}
If You don't want the two Object version being called directly (e.g. say you want to do pre-validation before assignment) then declare a "dummy" argument. It feels a bit wrong, but it's really the only choice if you want to have public and private constructors with semantically the same arguments (they have to be syntacticly distinct.)
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.
}
Use static factory methods and a simple private constructor, which just assigns the values to the fields, and if necessary does other initializations.
Don't know if this is an option, but you could use Dependency Injection, i.e. you create the objects you're fields referring to somewhere else and just pass them in in the constructor. This makes your class much more easy to test and much more flexible.
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);
}
}
That way, you could:
- Refer to the constructor parameters by their supertypes.
- In the factory, return subtypes of ClassA
- Pass in mock objects (dummy objects) for the fields, in order to test the class in isolation
- many more benefits
精彩评论