【问题标题】:Any way to programmatically determine which Java language features are available on current platform?有什么方法可以以编程方式确定当前平台上可用的 Java 语言功能吗?
【发布时间】:2019-06-05 04:26:12
【问题描述】:

我想知道是否有 Java API 可以告诉您当前平台上是否有特定的语言功能(例如“钻石”运算符)。

(换句话说,我正在尝试做的类似于 JavaScript 中的“浏览器嗅探”。)

这在元编程中非常方便(编写一个生成 Java 源代码的 Java 程序。

到目前为止我发现的最佳解决方案是解析 System.getProperty("java.specification.version") 并检查它是否≥引入此功能的版本,但我不能 100% 确定此属性在所有 JVM 中都可用(甚至是否它在所有 JVM 中都符合相同的语法)。这种方法的另一个小烦恼是,您必须采取额外的步骤来查找哪个 Java 版本引入了您感兴趣的语言功能。没什么大不了的,因为该信息很容易通过谷歌搜索,但理想情况下它会如果有一个 API 可以很容易地提供信息,那就太好了,例如:

code.append("Map<Integer, String> map = ");
if (javax.meta.JavaVersion.getCurrentVersion().supportsDiamond()) {
    code.append("new Map<>();");
} else {
    code.append("new Map<Integer, String>();");
}

显然没有名为 javax.meta 的包,但我想知道是否已经有针对此问题的现有解决方案,它比解析 "java.specification.version" 属性更简洁。


更新:我刚刚意识到Package#getSpecificationVersion() 也提供与System.getProperty("java.specification.version") 相同的值,但可能更可靠,因为系统属性是可变的。换句话说,获得 Java 规范版本的最佳方法可能是在任何“内置”包上调用 Package#getSpecificationVersion()。例如:String.class.getPackage().getSpecificationVersion()

【问题讨论】:

  • 我只会让用户选择生成的 Java 程序的 Java 版本,并避免所有这些烦人的东西...... :)
  • 我看到的唯一另一种方法是以编程方式编译一个具有预期功能的小程序,看看编译是否失败(import java.util.*;class M{public static void main(String[]a){Map&lt;String, String&gt; m = new HashMap&lt;&gt;();}}),但它也是一个 hack,它也会使你的代码难以维护并且速度变慢,所以我会坚持根据安装的编译器版本决定写什么。

标签: java


【解决方案1】:

这将为您提供系统正在运行的 java 版本。

 System.getProperty("java.version")

如果您运行的是 Java 9 及更高版本,您可以使用:

Runtime.Version version = Runtime.version();

Java Docs

请注意,Java 版本控制命名标准在 Java 9 中也发生了变化。

Java 版本:1.7, 1.8, 9, 10, 11

我没有让您检查特定功能的解决方案。

【讨论】:

  • 感谢您的回答,但我已经在使用 java.specification.version 属性(它返回类似 "1.8" 的内容),这在这种情况下比 java.version 更有意义(返回类似的内容"1.8.0_151"),因为我只需要语言规范,"1.8""1.8.0_151" 更容易解析。但是,感谢您提供有关新 Runtime.Version 类的信息。也许有一天我会升级到 Java 9 :)
【解决方案2】:

一个功能可能是:JDK 10

  1. 方法:Optional.orElseThrow()
  2. API:用于创建不可修改集合的 API
  3. 系统属性:例如,禁用 JRE 上次使用跟踪
  4. GC 增强(完全并行)
  5. Javadoc 支持:(适用于多个样式表)

这也可能是功能的删除:也在 Java 10 中

  1. 取消对使用旧 LookAndFeel 的支持
  2. 删除 Runtime.getLocalizedInputStreamgetLocalizedOutputStream 方法
  3. 等等..

因此,如果新功能存在或已被删除,则很难以编程方式 checkdiscover 除非您知道自己在寻找什么,它需要由Oracle 本身作为文档、特性名称和描述。

如果我们要为此创建 API,我们必须首先从 Oracle 文档中获取列表,然后对每个功能进行必要的检查以发现当前版本或是否受支持。

以下是example,用于以编程方式检查编译器的特定功能。

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.util.Arrays;

import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.ToolProvider;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject.Kind;

public class CompileSourceInMemory {
  public static void main(String args[]) throws IOException {
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();

    StringWriter writer = new StringWriter();
    PrintWriter out = new PrintWriter(writer);
    out.println("public class HelloWorld {");
    out.println("  public static void main(String args[]) {");
    out.println("    System.out.println(\"This is in another java file\");");    
    out.println("  }");
    out.println("}");
    out.close();
    JavaFileObject file = new JavaSourceFromString("HelloWorld", writer.toString());

    Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(file);
    CompilationTask task = compiler.getTask(null, null, diagnostics, null, null, compilationUnits);

    boolean success = task.call();
    for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
      System.out.println(diagnostic.getCode());
      System.out.println(diagnostic.getKind());
      System.out.println(diagnostic.getPosition());
      System.out.println(diagnostic.getStartPosition());
      System.out.println(diagnostic.getEndPosition());
      System.out.println(diagnostic.getSource());
      System.out.println(diagnostic.getMessage(null));

    }
    System.out.println("Success: " + success);

    if (success) {
      try {
        Class.forName("HelloWorld").getDeclaredMethod("main", new Class[] { String[].class })
            .invoke(null, new Object[] { null });
      } catch (ClassNotFoundException e) {
        System.err.println("Class not found: " + e);
      } catch (NoSuchMethodException e) {
        System.err.println("No such method: " + e);
      } catch (IllegalAccessException e) {
        System.err.println("Illegal access: " + e);
      } catch (InvocationTargetException e) {
        System.err.println("Invocation target: " + e);
      }
    }
  }
}

class JavaSourceFromString extends SimpleJavaFileObject {
  final String code;

  JavaSourceFromString(String name, String code) {
    super(URI.create("string:///" + name.replace('.','/') + Kind.SOURCE.extension),Kind.SOURCE);
    this.code = code;
  }

  @Override
  public CharSequence getCharContent(boolean ignoreEncodingErrors) {
    return code;
  }
}

参见 JDK 10 features

【讨论】:

  • 我喜欢你的建议:使用编译器工具 API。实际上,我刚刚开始在元编程工作中使用github.com/OpenHFT/Java-Runtime-Compiler,但我没有想到它也可以用于帮助发现功能,直到您指出这一点。谢谢! +1
【解决方案3】:

我刚刚发现了另一个部分解决方案:javax.lang.model.SourceVersion。它仍然没有完全解决问题,但似乎是朝着正确方向迈出的一步。

所以不是我的旧方法,

if (System.getProperty("java.specification.version").compareTo("1.7") >= 0)
   // do something that requires Java 7

你可以写:

if (SourceVersion.latestSupported().ordinal() >= SourceVersion.RELEASE_7.ordinal()) 
    // do something that requires Java 7

(因为SourceVersionenum 常量是按升序声明的)

这个类还提供了静态方法isKeywordisNameisIdentifier,它们可能很有用,但导出其中的一些信息已经相当容易(例如Character.isJavaIdentifierPart(cp))。

在底层,SourceVersion 还依赖于读取"java.specification.version" 系统属性:

private static SourceVersion getLatestSupported() {
    try {
        String specVersion = System.getProperty("java.specification.version");

        if ("1.8".equals(specVersion))
            return RELEASE_8;
        else if("1.7".equals(specVersion))
            return RELEASE_7;
        else if("1.6".equals(specVersion))
            return RELEASE_6;
    } catch (SecurityException se) {}

    return RELEASE_5;
}

那里有一些臭代码!例如如果有人打电话给System.setProperty("java.specification.version", "6.9"); 那么SourceVersion.latestSupported() 将返回RELEASE_5 ;)

令我惊讶的是,即使 Sun 也没有提供直接从 VM 获取 Java 版本的“神奇”方式(例如 sun.misc 包暴露的各种内部“神奇”操作)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-11-09
    • 2012-02-05
    • 2013-01-23
    • 2011-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多