【问题标题】:How to make a lambda expression define toString in Java 8?如何使 lambda 表达式在 Java 8 中定义 toString?
【发布时间】:2014-06-30 23:20:26
【问题描述】:

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

【问题讨论】:

标签: java lambda java-8


【解决方案1】:

简短的回答,你不能。 @FunctionalInterfaces 不能用于“覆盖”来自 Object 的方法。

但是,您可以使用虚拟扩展方法来实现 Formattable。注意:以下代码未经测试:

@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(),它利用了这个接口。

或者您也可以定义自己的界面。或者甚至为此接口编写一个包装器,在.asString() 中调用.toString()。选择很多。

【讨论】:

  • 嗯,这是你的选择,我不能说更多;)或者也许 lambdas 只是不适合你想要做的事情。无论如何,你有我对解决方案的看法。
  • 看来你自己搞糊涂了。在最后一行中,它应该是 this.asString() 而不是 this.toString(),因为这就是 lambda 将实现 asString 而不是 toString 的答案的全部意义。
  • @Holger 确实如此。但是 OP 的需求还不是很清楚......我确实怀疑在这种情况下 lambdas 不是解决方案
  • 看来 OP 想要一种有条件地跳过 String 的计算/构造的方法。因此,接受Supplier&lt;String&gt; 作为输入的static 打印/日志方法也可以完成这项工作。
  • @user270349:你不需要。只需让 print 方法接受 only Suppliers。然后你必须将每个非Supplier 转换为Supplier&lt;String&gt;,但这很容易:foofoo::toString
【解决方案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】:

正如 fge 所指出的,接口不能从 Object 类中声明方法(toString、equals、hashCode)。

我认为 Holger 将您指向 Supplier 是正确的,并且我认为鉴于您声明的创建懒惰日志评估器的目的,您应该将铸件交给您的 print 方法。为了帮助您打印调用的语法,您可以创建一个实用程序方法,该方法基本上为您执行转换:

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

【讨论】:

  • caster 方法 'supply' 是个好主意,但我可能想从实现 Supplier 的对象中打印 toString,因此我最好使用自定义接口。
【解决方案4】:

函数需要很快地知道它们的类型,这意味着如果你试图太接近你的原始想法,你将有大量的 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()));
}

老实说,Holger 的评论是我会做的 -

void print(String pattern, Supplier<?>... args);

【讨论】:

    【解决方案5】:

    这有点 hacky 并且可能很慢,但是您可以使用 Proxy 轻松地为 lambda 覆盖 toString()。对于非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() 时,基本上不需要重复/样板代码。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-11-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-04-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多