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.

9
  • @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... Commented Sep 11, 2019 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. Commented Sep 11, 2019 at 13:14
  • @MCEmperor: It's for enums. 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. Commented Sep 11, 2019 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. Commented Sep 11, 2019 at 16:48
  • 1
    @MarcoSulla could you add it (ideally, with the code) to the question? maybe we could come up with something... Commented Sep 11, 2019 at 21:09

4 Answers 4

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:

Sign up to request clarification or add additional context in comments.

2 Comments

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__.
@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?
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.

1 Comment

Is there no another class in java.util.function that can be used for this scope?
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...

Comments

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…

11 Comments

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.
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 is?
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.
@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.
So why Eclipse gives me error with Java 8? I tried with bound and unbound lambdas.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.