在这种情况下,如何处理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
所隐含的类型方程Function
需要推断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框架完全兼容。