I'm trying to refactor the following code:
class Base {
private Object a, b, <...>; // there's like 10 of these attributes of different type
public Object a() {
return a;
}
public Object b() {
return b;
}
// more getters like the ones above
}
class RootNode extends Base { }
class BranchNode extends Base {
private RootNode root; // passed via constructor
public Object a() {
Object value = super.a();
return value != null ? value : root.a();
}
public Object b() {
Object value = super.b();
return value != null ? value : root.b();
}
// below are more methods like the above one, all with same logic
}
Naturally, I want to remove the repetition in this code to save myself from typing even more of the same lines when new properties are added, but I cannot quite figure out how to do that.
My first instinct was that it looks a lot like this code (which, unfortunately, does not compile):
private <T> T nvlGet(Function<Base, T> accessor) {
T value = accessor.apply(super); // this is the problem line, because there is no way to pass a "super-reference" to anything
return value != null ? value : accessor.apply(root);
}
// and then public accessors would look like this:
public Object a() {
return nvlGet(Base::a);
}
I can't obviously "fix" the above code by calling accessor.apply(this)
instead of accessor.apply(super)
, because that would cause a Stack Overflow
error.
Closest I managed to come up with so far is using bound suppliers, like this:
private <T> T nvlGet(Supplier<T> first, Supplier<T> second) {
T value = first.get();
return value != null ? value : second.get();
}
public Object a() {
return nvlGet(super::a, root::a);
}
However, that is two times as many references to same method than I'd like to have in ideal world. So, I would like to know if I'm missing something, and I can still somehow fix the version which uses Function<Base, T>
There is no such thing as a “super reference” that would alter the outcome of an ordinary invocation of an overridable method (aka invokevirtual
instruction).
Your solution using two method references is the best you can get, as long as you insist on using functions, whereas passing evaluated values is even simpler:
private <T> T nvlGet(T value, Supplier<T> second) {
return value != null? value: second.get();
}
public Object a() {
return nvlGet(super.a(), root::a);
}
As said by others, you can't do much better, because super
is not a reference you can pass around.
I agree with this answer in that passing the values returned by the super.a()
, super.b()
, etc invocations is simpler.
In addition, I would change the 2nd argument to be of type Function<? super Base, ? extends T>
, so that the usage of the root
instance in the nvlGet
method remains encapsulated:
private <T> T nvlGet(T nullable, Function<? super Base, ? extends T> second) {
return nullable != null ? nullable : second.apply(root);
}
Usage:
public Object a() {
return nvlGet(super.a(), Base::a);
}
public Object b() {
return nvlGet(super.b(), Base::b);
}
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