【问题标题】:Can we get a method name using java.util.function?我们可以使用 java.util.function 获取方法名吗?
【发布时间】:2019-09-11 10:46:41
【问题描述】:

我尝试过:

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

我明白了:

HelloWorld$$Lambda$1/471910020@548c4f57

我想得到字符串isEmpty。我该怎么做?

编辑:我创建的方法的代码是这个:

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

问题是我不能作为参数 T::getValue 传递,因为 getValue 不是静态的。而且我不能传递 someEnumElem::getValue,因为get() 将返回该元素的值。我可以在 for 循环中使用:

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

但是这样getValue 是固定的,我不能将它作为参数传递。我可以使用一些第三方库来做一个eval(),但我真的不想打开那个潘多拉花瓶:D

编辑 2:Function 确实适用于无参数方法,但仅适用于 Java 11。不幸的是,我坚持使用 Java 8。

【问题讨论】:

  • @MCEmperor 至少非常:为了调试,不打印"Checking elements for HelloWorld$$Lambda$1/471910020@548c4f57",而是打印"Checking elements for isEmpty"。那太好了……
  • @Marco13 这意味着某种 toString 和 lambdas 或方法引用的 identity,设计者并没有故意这样做 IIRC。
  • @MCEmperor:这是给enums的。我创建了一个通用方法,它从一个枚举、一个值和一个 getter 名称返回带有该值的 enum 元素,由指定的 getter 返回。唯一的技巧是我想从 getter 本身中提取 getter 的名称,而不是传递字符串,例如“getValue”。这是因为如果 enum 中的 getter 发生更改,编译器会通知我在调用此方法时也必须更改它。
  • 那么为什么不让你的方法接受Function 而不是getter 名称呢?然后,与其尝试找出 getter 名称,而只是搜索要调用的正确方法,而是在函数上调用 apply,它已经调用了正确的方法。
  • @MarcoSulla 你能把它(最好是用代码)添加到问题中吗?也许我们可以想出点什么……

标签: java methods java-8 functional-programming method-reference


【解决方案1】:

string::isEmpty 将由 LambdaMetafactory.metafactory 方法构造,其参数中有 implMethod

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

如果我们可以访问传递给此工厂方法的参数,特别是implMethod,则将返回一个方法名称(此处为"isEmpty")。 JVM 向上调用生成的参数,为java.lang.invoke API 提供非常具体的信息。

比如初始化一个string::isEmpty代表的DirectMethodHandle,JVM会调用如下方法。

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

那个name(正是你要求的)将被JVM放在那里,我们无法访问它。目前。

阅读:

【讨论】:

  • 感谢您的解释。而且我知道在 Java 中方法不是一等公民。无论如何,我仍然不清楚为什么这样的事情在 Java 中是不可能的。例如,在 Python 中就像 str.lower.__name__ 一样简单。
  • @MarcoSulla 我想知道你为什么改变主意。你还有什么想知道的吗?如何改进答案?
【解决方案2】:

简而言之:没有。

一旦使用方法引用,您将获得您请求的功能接口的实现(在本例中为Supplier&lt;?&gt;),但基本上该对象的所有细节都是未定义的(或者准确地说是实现定义的) .

规范没有说明它是一个单独的对象,它的toString() 必须是什么,或者你可以用它做什么。这是Supplier&lt;?&gt;,基本上没有别的。

同样的事情也适用于 lambda 表达式。

如果你用过

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

Supplier 会做同样的事情,你也无法回到 lambda 的“代码”。

【讨论】:

  • java.util.function 中是否没有其他类可用于此范围?
【解决方案3】:

简而言之:不,这是不可能的。


我一直在使用的一种解决方法是创建将java.util.functional 实例包装成“命名”版本的方法。

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

当然,这对所有应用案例都没有意义。最重要的是,它不允许您“派生”诸如 Supplier 的方法名称之类的东西事后看来,例如,作为方法参数。其原因更具技术性,最重要的是:供应商不必是方法参考。

但是当您控制Supplier 的创建时,将string::isEmpty 更改为Named.named("isEmpty", string::isEmpty) 可能是一种合理的方法。

事实上,我对所有函数类型都进行了如此系统的处理,我什至考虑将其推送到一些公开可见的(GitHub/Maven)库中......

【讨论】:

    【解决方案4】:

    奇怪的是,你问的却与你实际需要的相反。

    您有一个接收字符串的方法并想要执行具有该名称的方法,但由于某些未知原因,您要求相反,从现有供应商处获取方法名称。

    already written in a comment在知道实际代码之前,可以通过将String getter参数替换为Function&lt;T,U&gt; getter来解决实际问题。

    这里不需要任何反射工具:

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

    getter Function 可以通过方法引用来实现,例如

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

    Function&lt;T,U&gt;相比,我使函数参数的签名更加慷慨,以提高现有函数的灵活性,例如

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

    如果在错误的情况下打印有意义的名称真的很重要,只需添加一个名称参数即可:

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

    被称为

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

    这仍然比使用反射进行正常操作要好......

    【讨论】:

    • 抱歉,我已经尝试过Function,它需要一个带有一个参数的方法。我有一个吸气剂,所以没有参数。所以你必须使用Supplier
    • 尝试这个例子了吗?你知道getDuration()toString() 没有参数的getter 吗?你知道unbound instance method reference 是什么吗?
    • 我试过Java 8 和Eclipse 说:The type String does not define isEmpty(Object) that is applicable here。 Java 11 没问题。但由于我坚持使用Java 8,我必须使用Supplier。所以是的,我尝试了这个例子,我建议你更好地阅读问题标签和洋甘菊。
    • @MarcoSulla 作为一般建议,如果您想成为一名成功的程序员,您应该尝试学习这些概念,而不是发布从轶事证据中得出的强烈意见。我提供了一个解释未绑定实例方法参考的链接,没有理由认为这是 JDK 11 的特性。我给出的示例与 JDK 8 一起使用,Eclipse 和标准 javac。但它不包含对String::isEmpty 的方法引用,并且不清楚这种需要 enum 类型的方法应该如何与String 上的方法作为参数一起使用。
    • 那么为什么 Eclipse 给我 Java 8 的错误呢?我尝试了绑定和未绑定的 lambdas。
    猜你喜欢
    • 2011-12-05
    • 2018-05-16
    • 2020-11-04
    • 2023-03-16
    • 1970-01-01
    • 2021-03-01
    • 1970-01-01
    • 2021-04-05
    • 1970-01-01
    相关资源
    最近更新 更多