Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create non-capturing method reference which will call superclass method

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>

like image 665
M. Prokhorov Avatar asked Jan 29 '18 12:01

M. Prokhorov


2 Answers

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);
}
like image 109
Holger Avatar answered Nov 15 '22 18:11

Holger


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);
}
like image 30
fps Avatar answered Nov 15 '22 18:11

fps