如何在Java中通过编程实现功能检测?

5

我想编写一个Java框架,支持JRE7作为基线,但如果在JRE8(向上兼容?)的上下文中运行,则利用JRE8的功能。

(或者我可能搞反了...即JRE8是基线,但会优雅地降级以支持JRE7)。

Java提供了一种方法来实现这一点吗?

我的尝试/示例:

我认为我可以采用类似于JavaScript特性检测的方式来解决此问题,即通过编程测试我的Java8方法是否存在,如果存在则编程调用它,否则回退到Java7库。

在我看来,这是一种非常艰难的方法。如果这种“功能切换/检测”可以由Java Runtime Environment / Java Compiler处理,那就太好了。这是可能的,还是我在吃错药了?

免责声明:我没有解决编译问题,这将排除此解决方案的有用性(如果我使用Java7进行编译,则无法向编译后的类添加-parameters)。

public class Index {

    void tellme(String yourname) {
        /* ... */
    }

    public static void main(String[] args) throws Exception {
        Method tellme = Index.class.getDeclaredMethod("tellme", String.class);
        Method java8Params = null;
        try {
            java8Params = Method.class.getMethod("getParameters");
        } catch (NoSuchMethodException t) { /* ignore */ }
        if (java8Params != null) {
            // java 1.8 !!
            Object param = ((Object[]) java8Params.invoke(tellme))[0];
            System.out.printf("(java8) %s %s(%s %s){/*...*/}",
                    tellme.getReturnType(),
                    tellme.getName(),
                    param.getClass().getMethod("getType").invoke(param),
                    param.getClass().getMethod("getName").invoke(param));
        } else {
            // java 1.7 ...
            System.out.printf("(java7) %s %s(%s %s){/*...*/}",
                    tellme.getReturnType(),
                    tellme.getName(),
                    tellme.getParameterTypes()[0].getName(),
                    "arg0");
        }
    }
}

8
你可以利用Java类加载器系统,拥有两个完整的应用程序,然后根据Java版本从'main'引导正确的应用程序。 - Boris the Spider
1
你可以在Spring中寻找灵感,它似乎正在做类似的事情 - http://spring.io/blog/2015/04/03/how-spring-achieves-compatibility-with-java-6-7-and-8 - Deltharis
所以在运行时,您正在尝试确定代码编译到了哪个语言级别(即Java 8、7等)? - MadConan
1个回答

6
首先,使用反射确保通用代码和可选API之间没有代码依赖是正确的。由于关于何时解析类和成员引用有不同的策略,非反射式的懒加载在一个JVM上运行成功可能会在另一个JVM上失败。
但这引发了一个问题,即每个API使用都必须通过反射编码时,某些操作很难实现,特别是当您失去编译时检查并且运行时性能也可能受到影响时。
一般的解决方案是使用Java从一开始就提供的OO技术。创建定义该功能的接口或抽象类。实现Java 8解决方案和Java 7回退。在应用程序初始化或首次需要该功能时,进行一次反射检查,以确定所依赖的功能是否可用,如果可用,则实例化最佳实现,否则实例化回退。从那时起,您可以像使用普通对象一样通过接口使用实现。
在简单情况下,您可以将类的数量减少到两个,基类定义功能并提供回退行为,专用子类使用其专用实现覆盖实现。
将Java 8实现保留在单独的源文件夹中,并使用Java 8编译器进行编译。其他代码将使用Java 7编译器进行编译,以确保没有依赖于Java 8实现。
通用的Java 7兼容代码:
import java.lang.reflect.Method;

public class Index {
  static final MethodDescription DESC_ACCESSOR;
  static {
    MethodDescription md;
    try {
      Method.class.getMethod("getParameters");// test JRE feature
      // instantiate specialized solution
      md=(MethodDescription) Class.forName("MethodDescriptionJ8").newInstance();
    } catch(ReflectiveOperationException|LinkageError ex) {
      md=new MethodDescription(); // J7 fall-back
    }
    DESC_ACCESSOR=md;
  }

  void tellme(String yourname) {
      /* ... */
  }

  public static void main(String[] args) throws Exception {
      Method tellme = Index.class.getDeclaredMethod("tellme", String.class);
      String desc=DESC_ACCESSOR.get(tellme);
      System.out.println(desc);
  }
}
class MethodDescription {// base class defining application specific feature
    public String get(Method tellme) {// and providing fall-back implementation
      return String.format("(java7) %s %s(%s %s){/*...*/}",
          tellme.getReturnType(),
          tellme.getName(),
          tellme.getParameterTypes()[0].getName(),
          "arg0");
    }
}

使用Java 8单独编译:

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

public class MethodDescriptionJ8 extends MethodDescription {
    @Override
    public String get(Method tellme) {
      Parameter param = tellme.getParameters()[0];
      return String.format("(java8) %s %s(%s %s){/*...*/}",
                    tellme.getReturnType(),
                    tellme.getName(),
                    param.getType(),
                    param.getName());
    }
}

但是请注意,关于 这个特定的功能 ,结果可能会令人失望。如果使用 Java 8 编译时没有加上 -parameters 标志,则参数 名称 是不可用的。因此,即使在运行时使用 Java 8 方法检查与 Java 7 兼容的类,也无法获得参数名称。


Java 8 允许接口具有“默认”方法和“静态”方法。如果一个人依赖于接口作为交换 Java 7 和 Java 8 实现的方式,他们将无法在任何接口中使用“默认”或“静态”方法。请纠正我如果我错了,只是想知道这是否是使用接口而不是抽象类的限制。 - Chetan Kinger
1
你可以在Java 7中使用catch (ReflectiveOperationException ex)将所有捕获的异常压缩成一个,具体请参见Javadoc here - VGR
@Chetan Kinger:基础类型必须能够在最低公共平台上编译通过,因此如果您的目标是Java 7和Java 8,则使用interface作为基础类型时必须兼容Java 7。在Java 8引入之前,使用抽象类或接口的考虑方式与以往相同。但是,使用interface解决方案的一个额外优点是,如果它仅封装了一个操作并表示为与Java 8中的JRE方法匹配的单个方法,则可以使用LambdaMetaFactory实现Java 8版本... - Holger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接