Home > Software design >  Java function field of wildcard generics (wildcard capture error?)
Java function field of wildcard generics (wildcard capture error?)

Time:03-06

I'm trying to use a wildcard function as a field within a class. The idea is that I can hot swap the function from outside the class with a public helper function (e.g., in a test class). Here is the sketch of the field wildcard function:

class Foo {
  private Function<?, ?> bar;

  public <T, R> void setBar(Function<T, R> baz) {
    this.bar = baz;
  }

 // ...
}

The compiler does not complain about any of this code; however, if I were to try and utilize the field function bar within Foo I get errors of the sort ... capture#1 of ? (a wildcard capture error). Here is an example of how I would use the field function bar:

class Foo {
  private Function<?, ?> bar;

  public <T, R> void setBar(Function<T, R> baz) {
    this.bar = baz;
  }
  
  public doSomeMethod() {
    // ...
    if (this.bar != null) {
      // OutputType is hardcoded, as in input's type.
      // When `setBar` is called, function type must be correct.
      OutputType baz = bar.apply(input);
    }
  }
}

The outside caller would then be something in line with

public static void main() {
  Foo foo = new Foo();
  Function<InputType, OutputType> myFunc = x -> x.inputToOutput();
  foo.setBar(myFunc);
  foo.someCalculation();
}

How would I fix this wildcard capture error, or is there a way to salvage the general idea? The key is to be able to set the function outside of the class (such that it can be tuned in a test class).

CodePudding user response:

A Function<?, ?> is a function that has some unknown type as input parameter, and some other unknown type which it returns.

For the 'returns' thing, well, it's java, so at least it's Object. But for the input type thing you're just hosed, you cannot call this function, period - it might be a function that maps Strings to Integers, in which case you must pass a string. Or it is a function that maps Path objects to Strings, in which case you must pass a Path instance. The compiler won't let you take a try-and-pray approach on this: It won't let you write code that is nonsensical, and trying to call a function that turns Paths into Strings, by passing it a String - that is nonsensical.

The code you've pasted does not show how you can make this code sensical, mostly because it misses details.

For example, Foo probably needs the T and R type:


class Foo<T, R> {
  private Function<T, R> bar;

  public void setBar(Function<T, R> baz) {
    this.bar = baz;
  }
  
  public R someMethod(T thingie) {
    return bar.apply(thingie);
  }
}

If you insist on storing that function in the field, you're pretty much forced into the above, there's no other way: You need to 'track' that the paramtype and returntype of the function matches when you actually go to use it, and there's no other way to do that.

You can also do it in one go, and pass the function as part of the method invoke:

public <T, R> R someMethod(Function<T, R> func, T in) {
  return func.apply(in);
}

Remember: Generics LINK THINGS - There is no point to them unless the typevar is used in at least 2 places. class Foo<T, R> declares them. So does public <T, R> foo() {}. The other places are 'usages', and you want at least 2. Generics are compiled-checked documentation, they do nothing at runtime.

Let's say you want to write a method, and you want to say that the return type of it, is the same as its first parameter type, but it can be whatever the user wants. Let's say a no-op operation that just prints the param and then returns it. Without generics, you cannot do it:

public Object printMe(Object in) {
  System.out.println(in);
  return in;
}

Because this method 'undoes' the typing. You can't write printMe("hello").toLowerCase(). The spec of this method does not promise that it returns the parameter - it merely promises that it returns some Object.

With generics you can do it, because they link things:

<T> T printMe(T in) {
  System.out.println(in);
  return in;
}

Now you can write printMe("hello").toLowerCase() just fine.

CodePudding user response:

Thanks to @rzwitserloot for his explanation, and apologies for misleading that someMethod needed to have the same return type as the function (although irrelevant). I have no such requirement, and the solution was to simply make a helper function, with an Object input to wrap the apply call.

private Function<?, ?> bar;

public <T, R> setBar(Function<T, R> baz){
  this.bar = baz;
}

private <T> T executeBar(Object input){
  return ((Function) this.bar).apply(input);
}

Now someMethod "works" as desired

public void doSomeMethod(){
  // ...
  ​if (this.bar != null) {
    OutputType y = executeBar(input);
  }
  // ...
}

This has unchecked warnings, but those can be suppressed (I'm a C programmer by tenure, so I prefer the compiler let me do stupid things anyways).


These function calls are really just for testing cases, and are only meant to signal extra test, or throw errors. Therefore the return type has zero effect on the method and in reality are consumed by something entirely different than the method indicated. I again apologize if the abstraction distracted from my core question about Function<?, ?>.

  • Related