Reputation: 15930
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.
Upvotes: 11
Views: 4846
Reputation: 298113
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…
Upvotes: 3
Reputation: 49606
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:
Upvotes: 10
Reputation: 54611
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...
Upvotes: 3
Reputation: 308001
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.
Upvotes: 7