我不想让普通的lambda实现一个方法,并将其重新定义为toString作为附加值。我希望lambda表达式只实现toString方法。 我知道我的表达不是很好,但我相信你会用这个例子来理解我。
public class LambdaToStringTest {
public interface ToStringInterface {
public abstract String toString();
}
public static void main(String[] args) {
print("TRACE: %s", (ToStringInterface)()->someComputation()); // <<-- ERROR: The target type of this expression must be a functional interface
}
private static void print(String format, Object... args) {
System.out.println(String.format(format, args));
}
}
如果我更改方法的名称但是它不会覆盖toString,那么它会编译,因此print方法不会打印预期的内容。
这是尝试定义一个日志子系统,该子系统仅在需要时(当它真的要打印时)评估lambda,但与非lambda参数兼容。我知道实现它的其他方法,但我想知道为什么我不能这样做,如果有解决方法或我做错了什么,
答案 0 :(得分:5)
简短的回答,你不能。 @FunctionalInterface
的{{1}}不能用于“覆盖”来自Object
的方法。
但是,您可以使用虚拟扩展方法实现Formattable
。注意:下面的代码是UNTESTED:
@FunctionalInterface
public interface ToStringInterface
extends Formattable
{
String asString();
@Override
default void formatTo(Formatter formatter, int flags, int width, int precision)
{
formatter.format("%s", this);
// Or maybe even better:
formatter.out().append(this.asString());
}
}
我建议使用此解决方案,因为您正在使用String.format()
来使用此接口。
或者您可以定义自己的界面。或者甚至为此接口编写一个包装器,在.toString()
中调用.asString()
。选择很多。
答案 1 :(得分:3)
正如fge所指出的,接口不能从Object类声明方法(toString,equals,hashCode)。
我认为Holger指向供应商是正确的,我认为根据您创建惰性日志评估程序的既定目的,您应该在您的打印方法中进行转换。为了帮助您打印调用的语法,您可以创建一个基本上为您执行强制转换的实用工具方法:
private static void print(String format, Object... args) {
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Supplier) {
args[i] = ((Supplier<?>)args[i]).get();
}
}
System.out.println(String.format(format, args));
}
private static <T> Supplier<T> supply(Supplier<T> supplier) {
return supplier;
}
private static class Example {
private static String someString() {
return "hello";
}
private static Boolean someBoolean() {
return true;
}
}
public static void main(String[] args) {
print("TRACE: %s; %s; %s",
supply(Example::someString),
supply(Example::someBoolean),
"extra");
}
<强>输出强>
TRACE: hello; true; extra
答案 2 :(得分:2)
static <X,Y> Function<X,Y> withName(Function<X,Y> func, String name) {
return new Function<X, Y>() {
@Override
public Y apply(X x) {
return func.apply(x);
}
@Override
public String toString() {
return name;
}
};
}
/* Predicate, BiFunction, ... */
{// using
myFunction(
withName(a->a+1, "add one"),
withName((a,b)->a+b, "sum")
);
}
答案 3 :(得分:1)
功能需要很快知道它们的类型,这意味着如果你试图与原始想法保持太接近,你将对ToStringInterface进行大量的强制转换。您可以调用静态方法而不是强制转换。
static Object asString(Supplier<String> string){
return new Object(){
public String toString(){
return string.get();
}
};
}
public static void main(String[] args) {
print("TRACE: %s", asString(()->someComputation()));
}
老实说,霍尔格的评论就是我要做的 -
void print(String pattern, Supplier<?>... args);
答案 4 :(得分:0)
这有点棘手,可能很慢,但是您可以使用toString()
轻松覆盖Proxy
来获取lambda。对于非toString()
呼叫,只需呼叫method.invoke(lambda, args)
。然后对于toString()
,返回所需的字符串表示形式。这是一个简单的静态实用程序方法,可以将toString()
添加到任何lambda或接口:
private static <T> T addToStringBehavior(
final T t, final Function<T, String> toString) {
final Class<?> aClass = t.getClass();
return (T) Proxy.newProxyInstance(
aClass.getClassLoader(),
aClass.getInterfaces(),
(proxy, method, args) -> {
if (method != null && "toString".equals(method.getName())
&& (args == null || args.length == 0)) {
return toString.apply(t);
}
return method.invoke(t, args);
});
}
您可以像这样使用它:
public static void main(final String[] args) {
final Supplier<String> lambda = () -> "test";
System.out.println(lambda);
final Supplier<String> lambdaWithToString =
addToStringBehavior(lambda, (l) -> l.get());
System.out.println(lambdaWithToString);
System.out.println(lambdaWithToString.get().equals(lambda.get()));
}
上述调用将输出以下内容:
Main$$Lambda$14/0x0000000800066840@65c7a252
test
true
警告::像这样代理lambda可能比直接使用lambda慢得多,因此这可能不适用于需要性能的生产环境。但是出于调试目的(或在我需要的测试中),此解决方案很好用,并且在需要使用lambda覆盖/实现toString()
时,基本上不需要重复/样板代码。