How to make a lambda expression define toString in Java 8?

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,


Solution 1:

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.

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

Solution 3:

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

Solution 4:

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