在Java 8中,如何定义函数以适应varargs。
我们有这样的功能:
private String doSomethingWithArray(String... a){
//// do something
return "";
}
由于某种原因,我需要使用Java 8函数调用它(因为'然后'可以与其他函数一起使用。)
因此我想在下面给出一些定义。
Function<String... , String> doWork = a-> doSomethingWithArray(a) ;
这给了我编译错误。以下工作,但输入现在必须是一个数组,不能是一个字符串。
Function<String[] , String> doWork = a-> doSomethingWithArray(a) ;
这里我提到了String,但它可以是任何Object的数组。
有没有办法使用varargs(...)而不是array([])作为输入参数?
或者,如果我创建一个类似于Function的新界面,是否可以创建类似下面的内容?
@FunctionalInterface
interface MyFunction<T... , R> {
//..
}
答案 0 :(得分:7)
在这种情况下,您不能使用varargs语法,因为它不是方法参数。
根据您使用Function
类型的内容,您可能根本不需要它,您可以直接使用您的方法,而无需通过功能接口引用它们。
作为替代方案,您可以定义自己的功能界面:
@FunctionalInterface
public interface MyFunctionalInterface<T, R> {
R apply(T... args);
}
然后你的声明变成:
MyFunctionalInterface<String, String> doWork = a -> doSomethingWithArray(a);
现在可以调用doWork
:
String one = doWork.apply("one");
String two = doWork.apply("one","two");
String three = doWork.apply("one","two","three");
...
...
note - 功能接口名称只是一个占位符,可以改进以与功能接口的Java命名约定保持一致,例如VarArgFunction或类似的东西。
答案 1 :(得分:4)
因为数组和变量是覆盖等价的,所以可能:
@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>
也就是说,这与一般调用该方法有关。以下内容引发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
}
这是因为类型擦除:调用apply
方法通常会创建一个数组,其组件类型是类型变量T
的擦除。在上面的示例中,由于类型变量T
的擦除是Object
,因此它创建并将Object[]
数组传递给期望{{1}的apply
方法}}
使用泛型varargs覆盖Double[]
方法(更常见的是编写任何通用的varargs方法)将生成警告,这就是原因。 (该警告是在JLS的8.4.1中强制执行的。)
因此,我实际上并不建议使用此功能。我发布了它,因为它很有趣,它确实在更简单的情况下工作,我想解释它为什么不应该被使用。
答案 2 :(得分:1)
简短回答
这似乎不可能。 Function
接口只有四个方法,这些方法都不需要vararg
个参数。
扩展功能界面?
也不起作用。由于数组在Java中是一些奇怪的低级构造,因为类型擦除它们不能很好地处理泛型类型。特别是,不可能创建一个泛型类型的数组,而不会使用Class<X>
- reflection-thingies污染整个代码库。因此,使用Function<X, Y>
方法扩展default
接口甚至不可行,该方法需要varargs
并重定向到apply
。
数组创建语法,辅助方法
如果您静态地知道参数的类型,那么您可以做的最好的事情是使用内联语法进行数组创建:
myFunction.apply(new KnownType[]{x, y, z});
而不是你想要的varargs:
myFunction.apply(x, y, z); // doesn't work this way
如果这个太长,你可以定义一个辅助函数来创建
来自varargs的KnownType
数组:
// "known type array"
static KnownType[] kta(KnownType... xs) {
return xs;
}
然后按如下方式使用它:
myFunction.apply(kta(x, y, z, w))
这至少会更容易打字和阅读。
嵌套方法,真正的变种
如果你真的(我的意思是,真的)想要使用Function
语法将已知类型的参数传递给黑盒子通用vararg
,那么你需要类似于嵌套方法的东西。所以,例如,如果你想要这个:
myHigherOrderFunction(Function<X[], Y> blah) {
X x1 = ... // whatever
X x2 = ... // more `X`s
blah(x1, x2) // call to vararg, does not work like this!
}
您可以使用类来模拟嵌套函数:
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`
}
}
如你所见,这是一个很大的痛苦。这真的值得吗?
<强>结论强>
总的来说,这似乎是一个想法,无论你做什么,用Java实现都会非常痛苦。至少我没有看到任何简单的解决方案。说实话,我也没有看到它真正需要的地方(也许它只是我和BLUB-paradox)。
答案 3 :(得分:1)
将varargs方法定位到强类型Function
的一种安全方法是使用名为currying的技术。
例如,如果您需要使用3个参数定位varargs方法,则可以按如下方式执行:
Function<String, Function<String, Function<String, String>>> doWork =
a1 -> a2 -> a3 -> doSomethingWithArray(a1, a2, a3);
然后,无论你需要在哪里调用函数:
String result = doWork.apply("a").apply("b").apply("c");
这种技术不仅适用于varargs方法,也适用于任何具有不同类型参数的方法。
如果您已经有一个包含参数的数组,只需使用Function<String[], String>
:
Function<String[], String> doWork = a -> doSomethingWithArray(a);
然后:
String[] args = {"a", "b", "c"};
String result = doWork.apply(args);
因此,每当你有一个固定数量的参数时,使用currying。每当你有动态参数(由数组表示)时,请使用最后一种方法。
答案 4 :(得分:0)
不幸的是,添加一种方法来代替并为你做翻译是我能想到的。
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 );
}
}