Reputation: 4433
I am parsing an XML file where one of the fields I want to be immutable, ID, has to be set after the object is created. Should I set it to null, and throw an exception in the setID() method if ID!=null ?
Edit: I am parsing an XML file, and at the beginning, I create an object whose fields and objects are populated using information in XML file. I want to be able to set ID, which should be immutable, after creating the root object.
Edit: changed "final" to "immutable" because that's really what I meant semantically. (Sorry :( )
Upvotes: 3
Views: 4717
Reputation: 81054
The most common way around this is to use the builder pattern. You build up an object using setters and then once it's ready you create the immutable object using the builder as a template.
Upvotes: 14
Reputation: 27464
Technically if you can change the value after executing the constructor, it is not "immutable". It's more "semimutable".
But terminology aside: The first idea that occurs to me is to have a "validate" function of some sort. Go through all your setters, and when you think you're done, call validate. If it fails, the object is missing required fields or whatever.
The idea of a builder object is good too. I've never used that pattern. 'Have to think through the pros and cons.
Upvotes: 0
Reputation: 143094
A better approach might be to use a Builder, as described in Effective Java 2nd Edition in Item 2.
The basic idea is to have a Builder class that has setters (but usually not getters) for the different constructor parameters. There's also a build()
method. The Builder class is often a (static) nested class of the class that it's used to build. The outer class's constructor is often private.
The end result looks something like:
public class Foo {
public static class Builder {
public Foo build() {
return new Foo(this);
}
public Builder setId(int id) {
this.id = id;
return this;
}
// you can set defaults for these here
private int id;
}
public static Builder builder() {
return new Builder();
}
private Foo(Builder builder) {
id = builder.id;
}
private final int id;
// The rest of Foo goes here...
}
To create an instance of Foo you then write something like:
Foo foo = Foo.builder()
.setId(id)
.build();
You can also split this up, of course:
// I don't know the ID yet, but I want a place to store it.
Foo.Builder fooBuilder = Foo.builder();
...
// Now I know the ID:.
fooBuilder.setId(id);
...
// Now I'm done and want an immutable Foo.
Foo foo = fooBuilder.build();
You can have multiple setters on the builder, and even add additional parameters to build() or the Builder's constructor.
This lets you have a mutable object while parsing, but switch to an immutable object when you're finished. Your API only ever need expose the immutable object in many cases.
Upvotes: 7
Reputation: 3666
Maybe this nice lib will help you whith parsing xml XStream, so you don't have to bother about object creation. I don't know if you can configure it the way you want, but im sure its worth a look.
I would try to aviod a public setId method, since you get rid of this problem, if you don't have one which some client could call. Try to use the language to tell the user what he can do and what he should not.
If you don't store the id in the xml file you should use a factory which creates the object and uses a parameterized constructor.
If you don't provide a setter for the id, users can only "set" a id when creating the object, and I think thats what you want.
Upvotes: 0
Reputation: 269637
The field cannot be declared final
unless it is initialized during construction. I think something like this might meet your requirements.
class Foo {
private Bar x;
void setX(Bar x) {
if (x == null)
throw new IllegalArgumentException();
if (this.x != null)
throw new IllegalStateException();
this.x = x;
}
}
This Foo
implementation is intended for access by a single thread. For multi-thread access, you could use an AtomicReference
or externally synchronize
access.
Also note that if null
is a valid value for x
, you can create a private, dummy Bar
instance and use it in place of null
.
Upvotes: 0
Reputation: 89729
Final fields, by definition, do not change after construction of the object. If what you really mean is that you want the field to be set once, then you could simply initialize the field to null have the setter for that field throw an exception if the field is no longer null.
Upvotes: 6
Reputation: 61011
You definitely cannot make it final
. Throwing an exception from setID()
if ID != null
is a good idea. Maybe if you provide some more details, someone can come up with a more creative solution?
Upvotes: 1
Reputation: 837996
You cannot change a final member outside of a constructor. You will have to make it not final.
Upvotes: 9