简体   繁体   中英

varargs as input parameter to a function in java 8

In Java 8, how is a Function is defined to fit varargs.

we have a function like this:

private String doSomethingWithArray(String... a){
   //// do something
   return "";
}

And for some reason I need to call it using Java 8 function (because 'andThen' can be used along with other functions.)

And thus I wanted to define it something as given below.

Function<String... , String> doWork = a-> doSomethingWithArray(a)  ;

That gives me compilation error.Following works, but input is now has to be an array and can not be a single string.

Function<String[] , String> doWork = a-> doSomethingWithArray(a)  ;

Here I mentioned String, but it can be an array of any Object.

Is there a way to use varargs(...)instead of array([]) as input parameter?

Or if I create a new interface similar to Function, is it possible to create something like below?

@FunctionalInterface
interface MyFunction<T... , R> {
//..
} 

You cannot use the varargs syntax in this case as it's not a method parameter.

Depending on what you're using the Function type for, you may not even need it at all and you can just work with your methods as they are without having to reference them through functional interfaces.

As an alternative you can define your own functional interface like this:

@FunctionalInterface
public interface MyFunctionalInterface<T, R> {
    R apply(T... args);
}

then your declaration becomes:

MyFunctionalInterface<String, String> doWork = a -> doSomethingWithArray(a);

and calling doWork can now be:

String one = doWork.apply("one");
String two = doWork.apply("one","two");
String three = doWork.apply("one","two","three");
...
...

note - the functional interface name is just a placeholder and can be improved to be consistent with the Java naming convention for functional interfaces eg VarArgFunction or something of that ilk.

Because arrays and varargs are override-equivalent, the following is possible :

@FunctionalInterface
interface VarArgsFunction<T, U> extends Function<T[], U> {
    @Override
    U apply(T... args);
}

// elsewhere
VarArgsFunction<String, String> toString =
    args -> Arrays.toString(args);
String str = toString.apply("a", "b", "c");
// and we could pass it to somewhere expecting
// a Function<String[], String>

That said, this has a pitfall having to do with invoking the method generically. The following throws a ClassCastException :

static void invokeApply() {
    VarArgsFunction<Double, List<Double>> fn =
        Arrays::asList;
    List<Double> list = invokeApply(fn, 1.0, 2.0, 3.0);
}
static <T, U> U invokeApply(VarArgsFunction<T, U> fn,
                            T arg0, T arg1, T arg2) {
    return fn.apply(arg0, arg1, arg2); // throws an exception
}

( Example in action. )

This happens because of type erasure: invoking the apply method generically creates an array whose component type is the erasure of the type variable T . In the above example, since the erasure of the type variable T is Object , it creates and passes an Object[] array to the apply method which is expecting a Double[] .

Overriding the apply method with generic varargs (and more generally writing any generic varargs method) will generate a warning and that's why. (The warning is mandated in 8.4.1 of the JLS.)

Because of that, I don't actually recommend using this. I've posted it because, well, it's interesting, it does work in simpler cases and I wanted to explain why it probably shouldn't be used.

Short answer

This doesn't seem possible. Function interface has only four methods, and none of those methods takes vararg arguments.

Extend Function interface?

Doesn't work either. Since arrays are somewhat strange low-level constructs in Java, they do not work well with generic types because of type erasure. In particular, it is not possible to create an array of generic type without contaminating your entire codebase with Class<X> -reflection-thingies. Therefore, it's not even feasible to extend the Function<X, Y> interface with a default method which takes varargs and redirects to apply .

Syntax for array creation, helper methods

If you statically know the type of the arguments, then the best thing you can do is to use the inline syntax for array creation:

myFunction.apply(new KnownType[]{x, y, z});

instead of the varargs, which you want:

myFunction.apply(x, y, z); // doesn't work this way

If this is too long, you could define a helper function for creation of arrays of KnownType from varargs:

// "known type array"
static KnownType[] kta(KnownType... xs) {
    return xs;
}

and then use it as follows:

myFunction.apply(kta(x, y, z, w))

which would at least be somewhat easier to type and to read.

Nested methods, real varargs

If you really (I mean, really ) want to pass arguments of known type to a black-box generic Function using the vararg -syntax, then you need something like nested methods. So, for example, if you want to have this:

myHigherOrderFunction(Function<X[], Y> blah) {
  X x1 = ... // whatever
  X x2 = ... // more `X`s
  blah(x1, x2) // call to vararg, does not work like this!
}

you could use classes to emulate nested functions:

import java.util.function.*;

class FunctionToVararg {

    public static double foo(Function<int[], Double> f) {
      // suppose we REALLY want to use a vararg-version
      // of `f` here, for example because we have to 
      // use it thousand times, and inline array 
      // syntax would be extremely annoying.

      // We can use inner nested classes.
      // All we really need is one method of the
      // nested class, in this case.
      class Helper {
        // The inner usage takes array, 
        // but `fVararg` takes varargs!
        double fVararg(int... xs) {
          return f.apply(xs);
        }
        double solveTheActualProblem() {
          // hundreds and hundreds of lines 
          // of code with dozens of invokations
          // of `fVararg`, otherwise it won't pay off
          // ...
          double blah = fVararg(40, 41, 43, 44);
          return blah;
        }
      }

      return (new Helper()).solveTheActualProblem();
    }

    public static void main(String[] args) {
      Function<int[], Double> example = ints -> {
        double d = 0.0;
        for (int i: ints) d += i;
        return d / ints.length;
      };

      System.out.println(foo(example)); // should give `42`
    }
}

As you see, that's a lot of pain. Is it really worth it?

Conclusion

Overall, this seems to be an idea which would be extremely painful to implement in Java, no matter what you do. At least I don't see any simple solutions. To be honest, I also don't see where it would be really necessary (maybe it's just me vs. the BLUB-paradox).

One safe way to target a varargs method to a strongly typed Function is by using a technique called currying .

For example, if you need to target your varargs method with 3 arguments, you could do it as follows:

Function<String, Function<String, Function<String, String>>> doWork =
    a1 -> a2 -> a3 -> doSomethingWithArray(a1, a2, a3);

Then, wherever you need to call the function:

String result = doWork.apply("a").apply("b").apply("c");

This technique works to target not only varargs methods, but also any method with any number of arguments of different types.

If you already have an array with the arguments, just use a Function<String[], String> :

Function<String[], String> doWork = a -> doSomethingWithArray(a);

And then:

String[] args = {"a", "b", "c"};

String result = doWork.apply(args);

So, whenever you have a fixed number of arguments, use currying. And whenever you have dynamic arguments (represented by an array), use this last approach.

Unfortunately, adding a method to intercede and do the translation for you was all I could come up with.

public class FunctionalTest {
   public static void main( String[] args ) {
      kludge( "a","b","c" );
   }

   private static Function<String[],PrintStream> ref = a -> System.out.printf( "", a );
   public static void kludge( String... y ) {
      ref.apply( y );
   }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM