如何获取Java 14方法引用的MethodInfo?

问题描述 投票:0回答:2

我本质上是在问与这个老问题相同的问题,但针对的是 Java 14 而不是 Java 8。为了避免回答者导航到旧问题的麻烦,我将在这里重新表述。 我想从引用的方法中获取函数的名称。下面的 Java 代码应该能让您有所了解:

public class Main
{
    public static void main(String[] args)
    {
       printMethodName(Main::main);
    }

    private static void printMethodName(Consumer<String[]> theFunc)
    {
        String funcName = // somehow get name from theFunc
        System.out.println(funcName)
    }
 }

C# 中的等价物是:

public class Main
{
    public static void Main()
    {
        var method = Main.Main;
        PrintMethodName(method)
    }

    private static void PrintMethodName(Action action)
    {
        Console.WriteLine(action.GetMethodInfo().Name);
    }
}

根据旧问题的公认答案,如果没有大量的工作,这在 Java 8 中是不可能的,例如 这个解决方案。 Java 14中有更优雅的解决方案吗?

java lambda reflection java-14
2个回答
6
投票

从方法引用中获取方法信息从来都不是 JDK 开发人员的目标,因此没有做出任何努力来改变这种情况。

但是,您的链接中显示的方法可以简化。您可以在序列化时简单地拦截原始

SerializedLambda
对象,而不是序列化信息、修补序列化数据并使用替换对象恢复信息。

例如

public class GetSerializedLambda extends ObjectOutputStream {
    public static void main(String[] args) { // example case
        var lambda = (Consumer<String[]>&Serializable)GetSerializedLambda::main;
        SerializedLambda sl = GetSerializedLambda.get(lambda);
        System.out.println(sl.getImplClass() + " " + sl.getImplMethodName());
    }

    private SerializedLambda info;

    GetSerializedLambda() throws IOException {
      super(OutputStream.nullOutputStream());
      super.enableReplaceObject(true);
    }

    @Override protected Object replaceObject(Object obj) throws IOException {
      if(obj instanceof SerializedLambda) {
        info = (SerializedLambda)obj;
        obj = null;
      }
      return obj;
    }

    public static SerializedLambda get(Object obj) {
        try {
            GetSerializedLambda getter = new GetSerializedLambda();
            getter.writeObject(obj);
            return getter.info;
        } catch(IOException ex) {
            throw new IllegalArgumentException("not a serializable lambda", ex);
        }
    }
}

将打印

GetSerializedLambda main
。这里使用的唯一新功能是立即删除书面信息的
OutputStream.nullOutputStream()
。在 JDK 11 之前,您可以写入
ByteArrayOutputStream
并在操作后删除信息,但这只是效率稍低。示例中也使用了
var
,但这与获取方法信息的实际操作无关。

限制与 JDK 8 中的相同。它需要可序列化的方法引用。此外,不保证实现将直接映射到方法。例如,如果将示例的声明更改为

public static void main(String... args)
,则在使用 Eclipse 编译时,它将打印类似
lambda$1
的内容。当还将下一行更改为
var lambda = (Consumer<String>&Serializable)GetSerializedLambda::main;
时,代码将始终打印合成方法名称,因为使用辅助方法是不可避免的。但对于
javac
,其名称更像是
lambda$main$f23f6912$1
,而不是 Eclipse 的
lambda$1

换句话说,您可能会遇到令人惊讶的实现细节。不要依赖此类信息的可用性来编写应用程序。


0
投票

(伟大的)已接受答案的更新版本,使用 Java 7 try-with-resources、Java 16“instanceof 的模式匹配”并删除了一个多余的

IOException
。 含义:更新至 Java 17 LTS。

(我不允许发表评论)

/**
 * Compiler safe way to get a method name from a lambda / anonymous function.
 */
public class GetSerializedLambda extends ObjectOutputStream {

  // Test this class.
  public static void main(String[] args) {
    var lambda = (Consumer<String[]> & Serializable) GetSerializedLambda::main;
    SerializedLambda sl = GetSerializedLambda.get(lambda);
    System.out.println(
      sl.getImplClass() + ": \"" + sl.getImplMethodName() + "\""
    );
  }

  private SerializedLambda info;

  GetSerializedLambda() throws IOException, SecurityException {
    super(OutputStream.nullOutputStream());
    super.enableReplaceObject(true);
  }

  @Nullable
  @Override
  protected Object replaceObject(Object obj) {
    if (obj instanceof SerializedLambda serializedLambda) {
      this.info = serializedLambda;
      obj = null;
    }
    return obj;
  }

  public static SerializedLambda get(Object obj) {
    try (GetSerializedLambda getter = new GetSerializedLambda()) {
      getter.writeObject(obj);
      return getter.info;
    } catch (IOException ex) {
      throw new IllegalArgumentException("not a serializable lambda", ex);
    }
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.