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. @FunctionalInterface
s 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);