¿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: