I want a class that I can create instances of with one variable unset (the id
), then initialise this variable later, and have it immutable after initialisation. Effectively, I'd like a final
variable that I can initialise outside of the constructor.
Currently, I'm improvising this with a setter that throws an Exception
as follows:
public class Example { private long id = 0; // Constructors and other variables and methods deleted for clarity public long getId() { return id; } public void setId(long id) throws Exception { if ( this.id == 0 ) { this.id = id; } else { throw new Exception("Can't change id once set"); } } }
Is this a good way of going about what I'm trying to do? I feel like I should be able to set something as immutable after it's initialised, or that there is a pattern I can use to make this more elegant.
Make the variable private and set value to it using the setter method such that if you try to invoke it for the second time it should set the previous value or throw an exception.
Constants. In some languages, it is possible to define special variables which can be assigned a value only once – once their values have been set, they cannot be changed. We call these kinds of variables constants.
In Java, a final variable can a be assigned only once. It can be assigned during declaration or at a later stage. A final variable if not assigned any value is treated as a blank final variable.
Let me suggest you a little bit more elegant decision. First variant (without throwing an exception):
public class Example { private Long id; // Constructors and other variables and methods deleted for clarity public long getId() { return id; } public void setId(long id) { this.id = this.id == null ? id : this.id; } }
Second variant (with throwing an exception):
public void setId(long id) { this.id = this.id == null ? id : throw_(); } public int throw_() { throw new RuntimeException("id is already set"); }
The "set only once" requirement feels a bit arbitrary. I'm fairly certain what you're looking for is a class that transitions permanently from uninitialized to initialized state. After all, it may be convenient to set an object's id more than once (via code reuse or whatever), as long as the id is not allowed to change after the object is "built".
One fairly reasonable pattern is to keep track of this "built" state in a separate field:
public final class Example { private long id; private boolean isBuilt; public long getId() { return id; } public void setId(long id) { if (isBuilt) throw new IllegalArgumentException("already built"); this.id = id; } public void build() { isBuilt = true; } }
Usage:
Example e = new Example(); // do lots of stuff e.setId(12345L); e.build(); // at this point, e is immutable
With this pattern, you construct the object, set its values (as many times as is convenient), and then call build()
to "immutify" it.
There are several advantages to this pattern over your initial approach:
0
is just as valid an id as any other long
value.build()
is called, they work. After build()
is called, they throw, regardless of what values you pass. (Note the use of unchecked exceptions for convenience).final
, otherwise a developer could extend your class and override the setters.But this approach has a fairly big drawback: developers using this class can't know, at compile time, if a particular object has been initialized or not. Sure, you could add an isBuilt()
method so developers can check, at runtime, if the object is initialized, but it would be so much more convenient to know this information at compile time. For that, you could use the builder pattern:
public final class Example { private final long id; public Example(long id) { this.id = id; } public long getId() { return id; } public static class Builder { private long id; public long getId() { return id; } public void setId(long id) { this.id = id; } public Example build() { return new Example(id); } } }
Usage:
Example.Builder builder = new Example.Builder(); builder.setId(12345L); Example e = builder.build();
This is much better for several reasons:
final
fields, so both the compiler and developers know these values cannot be changed.Yes, it's a bit more complicated to maintain, but IMHO the benefits outweigh the cost.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With