Question

I don't want to have a normal lambda implementing a method and redefine it's toString as a added value. I want the lambda expression implement only the toString method. I know I am not expressing it very well but I am sure you will understand me with this example.

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));
    }

}

It compiles if I change the name of the method but then it does not override toString so print method will not print what is expected.

This is an attempt to define a log subsystem that evaluates lambdas only when needed (when it is really going to be printed) but being compatible with non-lambda arguments. I know other ways to achieve it but I wonder why I can't do it this way and if there is a workaround or I am doing something wrong,

Was it helpful?

Solution

Short answer, you can't. @FunctionalInterfaces cannot be used to "override" methods from Object.

You can implement Formattable however, with a virtual extension method. Note: code below is 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());
    }
}

I propose this solution since you are using String.format() which makes use of this interface.

Or you can just define your own interface. Or even write a wrapper for this interface which calls .toString() in .asString(). Choices are many.

OTHER TIPS

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")
    );
}

As fge points out, interfaces cannot declare methods from the Object class (toString, equals, hashCode).

I think Holger is correct in pointing you to Supplier, and I think given your stated purpose of creating a lazy log evaluator, you should just hand the casting inside your print method. To help with the syntax of your print calls, you can create a utility method that essentially performs the cast for you:

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");
}

OUTPUT

TRACE: hello; true; extra

Functionals need to know their type pretty quickly, which means you'll have a ton of casts to ToStringInterface if you try to stay too close to your original idea. Instead of the cast, you can call a static method.

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()));
}

Honestly though, Holger's comment is what I would do -

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

This is a bit hacky and probably slow, but you can easily override toString() for a lambda using a Proxy. For non-toString() calls, simply call method.invoke(lambda, args). Then for toString(), return your desired string representation. Here's a simple static utility method that can add toString() to any lambda or interface:

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);
            });
}

You can use it like so:

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()));
}

The above calls will output the following:

Main$$Lambda$14/0x0000000800066840@65c7a252
test
true

Warning: it's likely that proxying a lambda like this will be much slower than using the lambda directly, so this may not be appropriate to use in production environments where performance is a concern. But for debugging purposes (or in tests which was where I needed it), this solution works great and basically removes the need for duplicate/boilerplate code when you need a lambda to override/implement toString().

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top