11

I tried to do:

public class HelloWorld {
     public static void main(String... args){
        final String string = "a";
        final Supplier<?> supplier = string::isEmpty;
        System.out.println(supplier);
     }
}

I get:

HelloWorld$$Lambda$1/471910020@548c4f57

I would like to get the string isEmpty. How can I do this?

EDIT: the code of the method I created is this one:

public class EnumHelper {
    private final static String values = "values";
    private final static String errorTpl = "Can't find element with value `{0}` for enum {1} using getter {2}()";

    public static <T extends Enum<T>, U> T getFromValue(T enumT, U value, String getter) {
        @SuppressWarnings("unchecked")
        final T[] elements = (T[]) ReflectionHelper.callMethod(enumT, values);

        for (final T enm: elements) {
            if (ReflectionHelper.callMethod(enm, getter).equals(value)) {
                return enm;
            }
        }

        throw new InvalidParameterException(MessageFormat.format(errorTpl, value, enumT, getter));
    }
}

The problem is I can't pass as parameter T::getValue, since getValue is not static. And I can't pass someEnumElem::getValue, since the get() will return the value of that element. I could use inside the for loop:

Supplier<U> getterSupllier = enm:getValue;
if (getterSupllier.get().equals(value)) {
    [...]
}

but in this way getValue is fixed, I can't pass it as parameter. I could use some third-party library to do an eval(), but I really don't want to open that Pandora vase :D

EDIT 2: Function does work with no parameters methods, but only in Java 11. Unluckily I'm stuck with Java 8.

Marco Sulla
  • 15,299
  • 14
  • 65
  • 100
  • @MCEmperor At the *very* least: For debugging, to not print `"Checking elements for HelloWorld$$Lambda$1/471910020@548c4f57"`, but `"Checking elements for isEmpty"`. That would be great... – Marco13 Sep 11 '19 at 12:15
  • @Marco13 that would mean a `toString` of some kind and an _identity_ for lambdas or methods references, the designers did not do that on purpose IIRC. – Eugene Sep 11 '19 at 13:14
  • @MCEmperor: It's for `enum`s. I created a generic method that from an enum, a value and a getter name, it returns the `enum` element with that value, returned by the getter specified. The only finesse is that I want to extract the name of the getter from the getter itself, not passing a string, like "getValue". This is because if the getter change in the `enum`, the compiler informs me I have to change it also when I invoke this method. – Marco Sulla Sep 11 '19 at 14:09
  • 1
    So why don’t you make your method accept a `Function` rather than a getter name? Then, instead of trying to find out the getter name, just to search for the right method to invoke, call `apply` on the function, which will already invoke the right method. – Holger Sep 11 '19 at 16:48
  • @Marco13 would an imaginary `"Checking elements for + string::isEmpty.getMethodName()` be better than `"Checking elements for isEmpty"`? – Andrew Tobilko Sep 11 '19 at 21:07
  • 1
    @MarcoSulla could you add it (ideally, with the code) to the question? maybe we could come up with something... – Andrew Tobilko Sep 11 '19 at 21:09
  • @AndrewTobilko I think the point is (also in view of the question) that at some point, you only have a `Supplier`, and there's nothing you can do with it in this regard ... except for calling `toString`. Some magic `if (s instanceof MethodRef) print(((MethodRef)s).getMethodName());` might be reasonable for some cases. But I personally like having `toString` methods that return something "readable" (or at least, something useful). Figuring out which lambda/methodRef hides behind a `$$1233@456` string is awkward... – Marco13 Sep 11 '19 at 23:44
  • @Eugene I don't see where *identity* comes into play here. But as it has already been sorted out: There is no automatic (or at least, no obvious) way of giving it a readable `toString`, or even *deriving* a helpful string from the object. But I agree with Andrew: Providing more code might help to find an appropriate solution. – Marco13 Sep 11 '19 at 23:47
  • I added the code. – Marco Sulla Sep 12 '19 at 07:31

4 Answers4

10

string::isEmpty will be constructed by a method LambdaMetafactory.metafactory which has implMethod among its parameters.

final String methodName = implMethod.internalMemberName().getName();

would return a method name (here, "isEmpty") if we had access to the arguments passed to this factory method, and to implMethod in particular. The arguments generated by up-calls from the JVM that provides very specific information for the java.lang.invoke API.

For example, to initialise a DirectMethodHandle which string::isEmpty represents, the JVM will call the following method.

/**
 * The JVM is resolving a CONSTANT_MethodHandle CP entry.  And it wants our help.
 * It will make an up-call to this method.  (Do not change the name or signature.)
 * The type argument is a Class for field requests and a MethodType for non-fields.
 * <p>
 * Recent versions of the JVM may also pass a resolved MemberName for the type.
 * In that case, the name is ignored and may be null.
 */
static MethodHandle linkMethodHandleConstant(Class<?> callerClass, int refKind,
                                             Class<?> defc, String name, Object type) 

That name (exactly what you requested) will be put there by the JVM, and there is no means for us to access it. For now.

To read:

Andrew Tobilko
  • 48,120
  • 14
  • 91
  • 142
  • Thank you for your explanation. And I know that in Java methods are not first class citizen. Anyway remains obscure to me why such a thing is impossible in Java. For example, in Python is just simple as `str.lower.__name__`. – Marco Sulla Sep 11 '19 at 15:21
  • @MarcoSulla I''d like to know why you've changed your mind. Is there something else you want to know? How can I improve the answer? – Andrew Tobilko Oct 02 '19 at 08:07
7

In short: no.

Once a method reference is used you'll have an implementation of the functional interface that you requested (Supplier<?> in this case), but basically all the specifics of that object as undefined (or implementation-defined to be precise).

The spec doesn't say anything about it being a separate object, what its toString() has to be or what else you can do with it. It's a Supplier<?> and basically nothing else.

The same thing applies to lambda expressions.

So if you had used

final Supplier<?> supplier = () -> string.isEmpty();

the Supplier would do the same thing and you also couldn't get back to the "code" of the lambda.

Joachim Sauer
  • 302,674
  • 57
  • 556
  • 614
3

In short: No, it's not possible.


A workaround that I've been using is to create methods that wrap java.util.functional instances into "named" versions.

import java.util.Objects;
import java.util.function.Supplier;

public class Named {

    public static void main(String[] args) {

        String string = "a";
        Supplier<?> supplier = string::isEmpty;
        Supplier<?> named = named("isEmpty", supplier);
        System.out.println(named);

    }

    static <T> Supplier<T> named(String name, Supplier<? extends T> delegate) {
        Objects.requireNonNull(delegate, "The delegate may not be null");
        return new Supplier<T>() {
            @Override
            public T get() {
                return delegate.get();
            }

            @Override
            public String toString() {
                return name;
            }
        };
    }
}

Of course this does not make sense for all application cases. Most importantly, it does not allow you to "derive" things like the method name of a Supplier in hindsight when you just receive it, for example, as a method argument. The reason for that is more technical, most importantly: The supplier does not have to be a method reference.

But when you control the creation of the Supplier, changing string::isEmpty to Named.named("isEmpty", string::isEmpty) can be a reasonable way to go.

In fact, I did this so systematically for all the functional types that I even considered pushing this into some publicly visible (GitHub/Maven) library...

Marco13
  • 53,703
  • 9
  • 80
  • 159
3

It’s weird that you are asking about the opposite of what you actually need.

You have a method that receives a string and wants to execute a method with that name, but for some unknown reason, you ask for the opposite, to get the method name from an existing supplier.

And already written in a comment before knowing the actual code, you can solve the actual problem by replacing the String getter parameter with Function<T,U> getter.

You don’t need any Reflection tool here:

public class EnumHelper {
    private final static String errorTpl
            = "Can't find element with value `{0}` for enum {1} using getter {2}()";

    public static <T extends Enum<T>, U> T getFromValue(
            T enumT, U value, Function<? super T, ?> getter) {

        final T[] elements = enumT.getDeclaringClass().getEnumConstants();

        for (final T enm: elements) {
            if(getter.apply(enm).equals(value)) {
                return enm;
            }
        }

        throw new IllegalArgumentException(
            MessageFormat.format(errorTpl, value, enumT, getter));
    }
}

The getter Function can be implemented via method reference, e.g.

ChronoUnit f = EnumHelper.getFromValue(
    ChronoUnit.FOREVER, Duration.ofMinutes(60), ChronoUnit::getDuration);
System.out.println(f);

I made the signature of the function parameter more generous compared to Function<T,U>, to raise the flexibility regarding existing functions, e.g.

Function<Object,Object> func = Object::toString;
ChronoUnit f1 = EnumHelper.getFromValue(ChronoUnit.FOREVER, "Years", func);
System.out.println(f1.name());

If printing meaningful names in the erroneous case is really important, just add a name parameter just for reporting:

public static <T extends Enum<T>, U> T getFromValue(
        T enumT, U value, Function<? super T, ?> getter, String getterName) {

    final T[] elements = enumT.getDeclaringClass().getEnumConstants();
    for (final T enm: elements) {
        if(getter.apply(enm).equals(value)) {
            return enm;
        }
    }

    throw new IllegalArgumentException(
            MessageFormat.format(errorTpl, value, enumT, getterName));
}

to be called like

ChronoUnit f = EnumHelper.getFromValue(
    ChronoUnit.FOREVER, Duration.ofMinutes(60), ChronoUnit::getDuration, "getDuration");

That’s still better than using Reflection for the normal operations…

Holger
  • 285,553
  • 42
  • 434
  • 765
  • Sorry, but I already tried `Function`, and it expects a method with one parameter. I have a getter, so no parameters. So you HAVE to use `Supplier`. – Marco Sulla Sep 19 '19 at 13:37
  • 1
    Did you *try* the example? Are you aware that `getDuration()` and `toString()` *are* getters with no parameter? Do you understand what an [unbound instance method reference](https://stackoverflow.com/q/35914775/2711488) is? – Holger Sep 19 '19 at 13:47
  • I tried with `Java` 8 and `Eclipse` says: `The type String does not define isEmpty(Object) that is applicable here`. No problem with Java `11`. But since I'm stuck with `Java` 8, I have to use `Supplier`. So yes, I tried the example, and I suggest you to read question tags better, and chamomile. – Marco Sulla Sep 19 '19 at 22:46
  • @MarcoSulla as a general advice, if you want to be successful as programmer, you should try to learn the concepts instead of posting strong opinions derived from anecdotal evidence. I gave a link that explains the *unbound instance method reference* and there’s no reason given to assume that this was a JDK 11 feature. The example, I gave, works with JDK 8 with both, Eclipse and standard `javac`. But it does not contain a method reference to `String::isEmpty` and it’s not clear, how this method requiring an **enum** type should work with a method on `String` as argument. – Holger Sep 20 '19 at 07:15
  • So why Eclipse gives me error with Java 8? I tried with bound and unbound lambdas. – Marco Sulla Sep 20 '19 at 16:53
  • I don’t know *what* you’ve tried. As said, my example works in Eclipse, but, as also said, it doesn’t contain a `String::isEmpty` reference. I also did already mention that `String::isEmpty` can’t work with your `getFromValue` method, as that method mandates an `enum` type and `String` is not an `enum` type. If you managed to make it work with JDK 11, the code must look entirely different than the setup of your question. So without knowing that setup, I can’t say why it doesn’t work with JDK 8. – Holger Sep 23 '19 at 10:28
  • I just make it working with `Java` 8 using `Function` for String:isEmpty. `Function, ?>` and Function` does work only in `Java` 10+. I want toaccept your answer, but I don't understand why you use `Function super T, ?> getter` as parameter instead of simply `Function getter` – Marco Sulla Oct 01 '19 at 22:19
  • Why do you think, `? extends Object` is simpler than just `?`? The reason to use `? super T` is explained in [What is PECS (Producer Extends Consumer Super)?](https://stackoverflow.com/q/2723397/2711488), it raises the reusability regarding preexisting functions. E.g. consider an existing function, `Function f = Object::toString;` which is valid for the `getFromValue` method, as it can process a supertype of `T`. It’s the same reason why we use `?` (shorthand for `? extends Object`) instead of `Object` for the function’s result type. – Holger Oct 02 '19 at 09:13
  • <<`The reason to use ? super T is [...] it raises the reusability regarding preexisting functions`>> Well, but in the code there's already `T extends Enum`... ### <<`Why do you think, ? extends Object is simpler than just ?`>> It's not, but it does not compile on `Java` 8. – Marco Sulla Oct 02 '19 at 10:44
  • <<`Object does not match T extends Enum`>> Excuse me, but the scope is to invoke the getter of the passed enum. Why should I allow the user to pass a `Object` that clearly have no getter that will give to me the requested enum value? – Marco Sulla Oct 02 '19 at 13:59
  • Anyway your code with `?` or `Object` does not work, as this link demonstrates: https://www.onlinegdb.com/HyJ0gNMur But `?` works for the second generic argument, so it's ok to write `Function getter` – Marco Sulla Oct 02 '19 at 14:20