在这种情况下,如何处理Function 和省略号/ varargs?

我的一个项目是扔lambda ; 在其中我的目标是简化Stream中潜在的@FunctionalInterface的使用,它在Stream使用的唯一“缺陷”是它们抛出已检查的exception(就我而言,我宁愿称有缺陷的事实是你不能从流中抛出已检查的exception但这是另一个故事)。

所以,对于Function我定义了这个:

 @FunctionalInterface public interface ThrowingFunction extends Function { R doApply(T t) throws Throwable; default R apply(T t) { try { return doApply(t); } catch (Error | RuntimeException e) { throw e; } catch (Throwable throwable) { throw new ThrownByLambdaException(throwable); } } } 

这允许我定义,例如:

 final ThrowingFunction = Path::toRealPath; 

(为什么Path::toRealPath ……好吧, 正是因为它有一个省略号 )。

不想停在这里我希望能够写下这样的东西:

 Throwing.function(Path::toRealPath).fallbackTo(Path::toAbsolutePath) 

以上几乎是有效的…继续阅读。

我也定义了这个:

 public abstract class Chainer<N, T extends N, C extends Chainer> { protected final T throwing; protected Chainer(final T throwing) { this.throwing = throwing; } public abstract C orTryWith(T other); public abstract  T orThrow( final Class exclass); public abstract N fallbackTo(N fallback); public final  T as(final Class exclass) { return orThrow(exclass); } } 

这是Function的实现:

 public final class ThrowingFunctionChain extends Chainer<Function, ThrowingFunction, ThrowingFunctionChain> implements ThrowingFunction { public ThrowingFunctionChain(final ThrowingFunction function) { super(function); } @Override public R doApply(final T t) throws Throwable { return throwing.doApply(t); } @Override public ThrowingFunctionChain orTryWith( final ThrowingFunction other) { final ThrowingFunction function = t -> { try { return throwing.doApply(t); } catch (Error | RuntimeException e) { throw e; } catch (Throwable ignored) { return other.doApply(t); } }; return new ThrowingFunctionChain(function); } @Override public  ThrowingFunction orThrow( final Class exclass) { return t -> { try { return throwing.doApply(t); } catch (Error | RuntimeException e) { throw e; } catch (Throwable throwable) { throw ThrowablesFactory.INSTANCE.get(exclass, throwable); } }; } @Override public Function fallbackTo(final Function fallback) { return t -> { try { return doApply(t); } catch (Error | RuntimeException e) { throw e; } catch (Throwable ignored) { return fallback.apply(t); } }; } } 

到目前为止一直很好(尽管IDEA未能将orTryWith()的代码识别为有效 ,但这是另一个故事)。

我还定义了一个名为Throwing的实用程序类,问题在于我作为测试编写的这个类的main()

 public final class Throwing { private Throwing() { throw new Error("nice try!"); } public static  ThrowingFunctionChain function( final ThrowingFunction function) { return new ThrowingFunctionChain(function); } public static void main(final String... args) { // FAILS TO COMPILE final Function f = function(Path::toRealPath) .fallbackTo(Path::toAbsolutePath); } } 

现在,上面代码的错误消息是:

 Error:(29, 48) java: incompatible types: cannot infer type-variable(s) T,R (argument mismatch; invalid method reference method toRealPath in interface java.nio.file.Path cannot be applied to given types required: java.nio.file.LinkOption[] found: java.lang.Object reason: varargs mismatch; java.lang.Object cannot be converted to java.nio.file.LinkOption) Error:(29, 49) java: invalid method reference non-static method toRealPath(java.nio.file.LinkOption...) cannot be referenced from a static context Error:(30, 25) java: invalid method reference non-static method toAbsolutePath() cannot be referenced from a static context 

我无法在这里诊断出错误的确切原因,但对我而言,它看起来就像省略号一样; 事实上,如果我这样做:

  final ThrowingFunctionChain f = function(Path::toRealPath); try ( final Stream stream = Files.list(Paths.get("")); ) { stream.map(f.fallbackTo(Path::toAbsolutePath)) .forEach(System.out::println); } 

然后它编译:所以这意味着Stream.map()确实将结果确认为一个Function

那么为什么不将Throwing.function(Path::toRealPath).fallbackTo(Path::toAbsolutePath)编译?

你的代码片段

 Function f = function(Path::toRealPath).fallbackTo(Path::toAbsolutePath); 

正在达到规范中包含的Java 8类型推断的限制,因此它不是编译器错误。 链接方法调用时,目标类型不起作用。 由于链的第一个方法是varargs方法,因此需要其目标类型来查找预期的调用签名。 这种情况类似于编写p->p.toRealPath() ,其中调用的参数数量是明确的,但p的类型是未知的。 两者都不能在调用链中工作(除了在最后一次调用中)

这可以通过使第一次调用的类型显式化来解决,

 Function f = Throwing.function(Path::toRealPath) .fallbackTo(Path::toAbsolutePath); 

要么

 ThrowingFunctionChain f0 = function(Path::toRealPath); Function f = f0.fallbackTo(Path::toAbsolutePath); 

要么

 Function f = function((Path p)->p.toRealPath()) .fallbackTo(Path::toAbsolutePath); 

或者通过将方法调用链转换为未链接的方法调用,如下所述 :

 public static  ThrowingFunctionChain function( final ThrowingFunction function) { return new ThrowingFunctionChain<>(function); } public static  Function function( final ThrowingFunction function, Function fallBack) { return new ThrowingFunctionChain<>(function).fallbackTo(fallBack); } public static void main(final String... args) { Function f = function(Path::toRealPath, Path::toAbsolutePath); } 

当一个调用以另一个调用的结果为目标时,规范故意拒绝两个调用的类型推断,但如果相同的表达式只是另一个调用的参数,则它可以工作。

正如Holger在评论中所述, 编译器在链接方法时的类型推断受到限制 。 只提供一个显式类型参数

 final Function f = Throwing.function(Path::toRealPath).fallbackTo(Path::toAbsolutePath); 

其中一个问题是,作为一个varargs方法, Path::toRealPath具有以下不同的类型(充当隐式重载)用于目标类型推断目的:

  • Path toRealPath(Path a)new LinkOption[0]将被编译器隐式提供为第二个参数)。
  • Path toRealPath(Path a,LinkOption... b) (第二个参数是直接的)。
  • Path toRealPath(Path a,LinkOption[] b) (第二个参数是直接)。
  • Path toRealPath(Path a,LinkOption b) (第二个参数将由编译器提供为new LinkOption[1] { b } )。
  • Path toRealPath(Path a,LinkOption b,LinkOption c) (第二个参数将由编译器提供为new LinkOption[2] { b, c } )。
  • Path toRealPath(Path a,LinkOption b,LinkOption c,LinkOption d) (第二个参数将由编译器提供为new LinkOption[3] { b, c, d }
  • Path toRealPath(Path a,LinkOption b,LinkOption c,LinkOption d,LinkOption e) (第二个参数将由编译器提供为new LinkOption[3] { b, c, d, e }
  • etc (第二个参数将由编译器提供为new LinkOption[n] { b, c, d, e, ... }

另一种是解决语句Function f= function(Path::toRealPath).fallbackTo(Path::toAbsolutePath) ;所隐含的类型方程Function f= function(Path::toRealPath).fallbackTo(Path::toAbsolutePath) ; 需要推断fallbackTo的类型参数,所以它的返回类型符合Function ,然后是function的类型参数,所以它自己的返回类型符合第一个。 Java进行这种推理,但仅在涉及单个步骤时(参数参数,返回值返回类型,从右到左分配)。 在返回类型的情况下,推理链是无界的,通常有多个解决方案。

另一种方法是向编译器提供更多类型信息。 例如:

 Function f= Throwing.function(Path::toRealPath).fallbackTo(Path::toAbsolutePath) ; 

或者通过创建临时变量,如:

 ThrowingFunctionChain stage1= function(Path::toRealPath) ; Function f= stage1.fallbackTo(Path::toAbsolutePath) ; 

在这种情况下, stage1的声明提供了附加信息。

从更广泛的角度来看,我并不完全理解为什么人们希望在将期望作为常态时传播exception。 我使用Optional做了或多或少相同的操作,或者使用一个非常小的扩展来封装exception信息。 您甚至可以使用1.7中引入的“抑制exception”机制来处理在对close方法的隐式调用期间发生的try-with-resourcesexception。 问题似乎完全一样。 代码非常简单,与streams和其他标准Java SE框架完全兼容。