10

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,

aalku
  • 2,860
  • 2
  • 23
  • 44
  • I am not asking this: http://stackoverflow.com/questions/16544583/can-i-override-tostring-method-of-functional-interface-in-jdk8-using-lambdas I want to implement only toString. – aalku May 13 '14 at 10:24

5 Answers5

7

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.

fge
  • 119,121
  • 33
  • 254
  • 329
  • I don't think you should use this.toString in an interface thought to be implemented by a lambda as it would print useless information. – aalku May 13 '14 at 10:57
  • 1
    Well, it's your choice, I can't say more ;) Or maybe lambdas are just not adapted for what you're trying to do. Anyway, you have my view of a solution. – fge May 13 '14 at 11:46
  • 1
    It seems like you confused yourself. In the last line it should be `this.asString()` rather than `this.toString()` as that’s the whole point of your answer that the lambda will implement `asString` rather than `toString`. – Holger May 13 '14 at 13:17
  • 1
    @Holger yes indeed. But the OP's needs are not yet very clear... I do suspect in this case that lambdas are not the solution – fge May 13 '14 at 13:19
  • 1
    It seems the OP wants a way of conditionally skipping the computation/ construction of the `String`. So a `static` print/log method accepting a `Supplier` as an input could do the job as well. – Holger May 13 '14 at 13:24
  • @Holger I know an option is to mix Supplier with other Objects and when printing the trace constructing a new array of String processing each element with `(o instanceof Supplier) ? o.get() : String.value(o)` but I don't think I can check the generic type of Supplier. – aalku May 13 '14 at 15:02
  • 1
    @user270349: you don’t need to. Just let the print method accept *only* `Supplier`s. Then you have to convert every non-`Supplier` into a `Supplier` but that’s rather easy: `foo` → `foo::toString`. – Holger May 13 '14 at 15:06
  • @Holger I wish the syntax as clear as possible. I would want `print(format, whatever, "", ()->something(), ...)` but I don't think that is possible as lambda type detection would fail without a typecast. – aalku May 13 '14 at 15:13
4
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

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
Stephan
  • 41,764
  • 65
  • 238
  • 329
Dane White
  • 3,443
  • 18
  • 16
  • The caster method 'supply'is a good idea but I might want to print toString from an object that implements Supplier so I better use a custom interface. – aalku May 14 '14 at 05:39
1

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);
Fuwjax
  • 2,327
  • 20
  • 18
1

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().

stiemannkj1
  • 4,418
  • 3
  • 25
  • 45
  • This is an approach I like. Talking about the performance, we can think of a change to your `addToStringBehavior`, where it returns the original `t` without doing anything if it is executed in production. Such a check should be possible somehow if you are working on a performance-oriented project. (My preference is to check if assertions are disabled.) – Hiroshi_U Jun 23 '22 at 08:50