如何在Java 8中创建一个lambda表达式来定义toString?

时间:2014-05-13 10:22:56

标签: java lambda java-8

我不想让普通的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参数兼容。我知道实现它的其他方法,但我想知道为什么我不能这样做,如果有解决方法或我做错了什么,

5 个答案:

答案 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()时,基本上不需要重复/样板代码。