java methods java-8 functional-programming method-reference

¿Podemos obtener un nombre de método usando java.util.function?



methods java-8 (4)

Traté de hacer:

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

Yo obtengo:

HelloWorld$$Lambda$1/471910020@548c4f57

Me gustaría obtener la cadena isEmpty . ¿Cómo puedo hacer esto?

EDITAR: el código del método que creé es este:

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)); } }

El problema es que no puedo pasarlo como parámetro T :: getValue, ya que getValue no es estático. Y no puedo pasar someEnumElem :: getValue, ya que get() devolverá el valor de ese elemento. Podría usar dentro del bucle for:

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

pero de esta manera getValue es fijo, no puedo pasarlo como parámetro. Podría usar alguna biblioteca de terceros para hacer una eval() , pero realmente no quiero abrir ese jarrón de Pandora: D

EDIT 2: La Function funciona sin métodos de parámetros, pero solo en Java 11. Desafortunadamente, estoy atascado con Java 8.


En resumen: no, no es posible.

Una solución alternativa que he estado usando es crear métodos que envuelvan instancias java.util.functional en versiones "con nombre".

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; } }; } }

Por supuesto, esto no tiene sentido para todos los casos de aplicación. Lo que es más importante, no le permite "derivar" cosas como el nombre del método de un Supplier en retrospectiva cuando lo recibe, por ejemplo, como un argumento de método. La razón de esto es más técnica, lo más importante: el proveedor no tiene que ser un método de referencia.

Pero cuando controla la creación del Supplier , cambiar string::isEmpty a Named.named("isEmpty", string::isEmpty) puede ser un camino razonable.

De hecho, hice esto de manera tan sistemática para todos los tipos funcionales que incluso consideré llevar esto a alguna biblioteca públicamente visible (GitHub / Maven) ...


En resumen: no.

Una vez que se utiliza una referencia de método, tendrá una implementación de la interfaz funcional que solicitó ( Supplier<?> En este caso), pero básicamente todos los detalles de ese objeto como indefinidos (o definidos de implementación para ser precisos).

La especificación no dice nada sobre si es un objeto separado, qué tiene que ser toString() o qué más puede hacer con él. Es un Supplier<?> Y básicamente nada más.

Lo mismo se aplica a las expresiones lambda.

Entonces si hubieras usado

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

el Supplier haría lo mismo y tampoco podría volver al "código" de la lambda.


Es extraño que preguntes sobre lo contrario de lo que realmente necesitas.

Tiene un método que recibe una cadena y desea ejecutar un método con ese nombre, pero por alguna razón desconocida, solicita lo contrario, para obtener el nombre del método de un proveedor existente.

Y ya escrito en un comentario antes de conocer el código real, puede resolver el problema real reemplazando el parámetro String getter Function<T,U> getter .

No necesita ninguna herramienta de reflexión aquí:

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)); } }

La Function getter se puede implementar a través de la referencia del método, por ejemplo

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

Hice que la firma del parámetro de función sea más generosa en comparación con la Function<T,U> , para aumentar la flexibilidad con respecto a las funciones existentes, por ejemplo

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

Si la impresión de nombres significativos en el caso erróneo es realmente importante, simplemente agregue un parámetro de nombre solo para informar:

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)); }

ser llamado como

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

Eso sigue siendo mejor que usar Reflection para las operaciones normales ...


string::isEmpty se construirá mediante un método LambdaMetafactory.metafactory que tiene implMethod entre sus parámetros.

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

devolvería un nombre de método (aquí, "isEmpty" ) si tuviéramos acceso a los argumentos pasados ​​a este método de fábrica, y a implMethod en particular. Los argumentos generados por las llamadas ascendentes de la JVM que proporciona información muy específica para la API java.lang.invoke .

Por ejemplo, para inicializar un DirectMethodHandle que string::isEmpty representa, la JVM llamará al siguiente método.

/** * 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)

JVM pondrá ese name (exactamente lo que solicitó) y no hay forma de que podamos acceder a él. Por ahora.

Leer: