Given a class with a non-trivial getter and some other internal method accessing the same private
member field:
class TestType {
private String value;
public String getValue() {
return this.value == null || this.value.isEmpty() ? "default" : this.value;
}
public void setValue(String newValue) {
this.value = newValue;
}
public int getValueLength() {
return this.getValue().length();
}
}
I want to forbid the by-passing of the getter in the getValueLength()
method.
The following implementation of getValueLength()
should produce some kind of error (unit test failure, checkstyle error, or whatever else can be automated in a generic way):
public int getValueLength() {
// ERROR: possible NullPointerException
return this.value.length();
}
In my real-life code there is some lazy-loading going on inside the getter that only loads the actual value when it is being accessed for the first time. Calling getValueLength()
first may result in a NullPointerException
directly or perform wrong actions based on the missing value.
The motivation is to ensure that future developers don't forget to only use the getter method and never access the member directly. Since this is an isolated issue (i.e. only where this kind of lazy-loaded is being specifically added) it is acceptable if some extra annotations are required, e.g. on the member itself that it should only be accessed in selected methods and those methods to be marked as well -- or specific checkstyle comments enabling/disabling specific rules.
Just to put this answer to form:
class LoadableVar<T> {
private T val;
private Supplier<T> loader;
public LoadableVar(Supplier<? extends T> loader) {
this.loader = loader;
}
public T get() {
if (this.val == null) {
//see: volatile and double-locking if multithreading
this.val = this.loader.get();
}
return this.val;
}
public void set(T overwrite) { //WARN: ignores the loader!
this.val = overwrite;
}
}
Then, in applying it to your class:
class TestType {
private final LoadableVar<String> value;
public TestType() {
//can also be passed into the class, or done however you desire
this.value = new LoadableVar<>(() -> /* load string from i/o, etc */);
}
public String getValue() {
return this.value.get();
}
public void setValue(String newValue) {
this.value.set(newValue); //I don't think this should be settable, personally
}
public int getValueLength() {
return this.getValue().length();
}
}
Now when you are writing code within TestType's scope:
String s;
s = this.value; //compile error!
s = this.getValue(); //OK
s = this.value.get(); //OK
As you can see, in doing it this way you've also made TestType#getValue
redundant, you could simply allow value
to be a protected
member (and in which case, I'd remove those setters and make it immutable).