【问题标题】:Java conditional compilation: how to prevent code chunks from being compiled?Java条件编译:如何防止代码块被编译?
【发布时间】:2011-05-30 09:26:24
【问题描述】:

我的项目需要 Java 1.6 才能编译和运行。现在我需要让它与 Java 1.5 一起工作(从营销方面)。我想替换方法体(返回类型和参数保持不变)以使其与 Java 1.5 一起编译而不会出错。

详细信息: 我有一个名为OS 的实用程序类,它封装了所有特定于操作系统的东西。它有一个方法

public static void openFile(java.io.File file) throws java.io.IOException {
  // open the file using java.awt.Desktop
  ...
}

双击打开文件(start Windows 命令或open Mac OS X 命令等效)。由于它不能用 Java 1.5 编译,我想在编译期间将其排除并替换为另一种方法,该方法调用 Windows 的 run32dll 或使用 Runtime.exec 的 Mac OS X 的 open

问题:我该怎么做?注释可以在这里提供帮助吗?

注意:我使用 ant,我可以创建两个 java 文件 OS4J5.javaOS4J6.java,它们将包含 OS 类以及 Java 1.5 和 1.6 所需的代码,然后将其中一个复制到 OS.java编译(或一种丑陋的方式 - 有条件地根据 java 版本替换 OS.java 的内容)但我不想这样做,如果有另一种方式。

详细说明:在 C 中我可以使用 ifdef, ifndef,在 Python 中没有编译,我可以使用 hasattr 或其他东西检查功能,在 Common Lisp 中我可以使用 #+feature。 Java有类似的东西吗?

找到this post,但似乎没有帮助。

非常感谢任何帮助。 kh。

【问题讨论】:

  • OS4J5OS4J6 编译成.class 文件,然后编写一个自定义类加载器来根据运行时版本选择哪一个?
  • @JUST 我不能同时使用 Java 1.5 编译,因为它不支持 java.awt.Desktop,而且我会在 OS4J6.java 上收到错误。
  • 用 1.6 编译。只要您不使用仅限 1.6 的 API,.class 文件就是向后兼容的。
  • @JUST 我用 1.6 编译了一个简单的测试 java 文件(没有特定于 1.6 的代码),但我无法用 1.5 运行它:Exception in thread "main" java.lang.UnsupportedClassVersionError: Bad version number in .class file
  • 预处理器做的比条件代码编译更多。我感到有点沮丧的是,人们将预处理器问题标记为重复:关于“条件编译”主题的主题。

标签: java conditional-compilation


【解决方案1】:

您好,当我在 Java SDK abd Android 之间共享库并且在两种环境中都使用图形时,我遇到了类似的问题,所以基本上我的代码必须同时使用两者 java.awt.Graphics 和 android.graphics.Canvas, 但我不想复制几乎任何代码。 我的解决方案是使用包装器,所以我以间接方式访问 graphisc API,并且 我可以更改几个导入,以导入我想要编译项目的包装器。 这些项目有一些锥形阴影,有些是独立的,但除了几个包装器等之外,没有任何重复的东西。 我认为这是我能做的最好的。

【讨论】:

    【解决方案2】:

    Manifold framework 中的 Java 有一个新的 Preprocessor。它是一个 javac 插件,这意味着它直接与 Java 编译器集成——无需管理构建步骤、代码生成目标等。

    【讨论】:

      【解决方案3】:

      Java Primitive Specializations Generator支持条件编译:

         /* if Windows compilingFor */
         start();
         /* elif Mac compilingFor */
         open();
         /* endif */
      

      这个工具有 Maven 和 Gradle 插件。

      【讨论】:

        【解决方案4】:

        如果您不想在应用程序中启用有条件的代码块,那么预处理器是唯一的方法,您可以查看java-comment-preprocessor,它可用于 maven 和 ant 项目
        附:
        我也做了some example how to use preprocessing with Maven to build JEP-238 multi-version JAR without duplication of sources

        【讨论】:

          【解决方案5】:

          java 9 中,可以创建多版本 jar 文件。本质上,这意味着您制作同一个 java 文件的多个版本。

          当你编译它们时,你会用所需的 jdk 版本编译每个版本的 java 文件。接下来,您需要将它们打包成如下所示的结构:

          + com
            + mypackage
              + Main.class
              + Utils.class
          + META-INF
            + versions
              + 9
                + com
                  + mypackage
                    + Utils.class
          

          在上面的示例中,代码的主要部分是在 java 8 中编译的,但对于 java 9,有一个额外的(但不同的)版本的 Utils 类。

          当您在 java 8 JVM 上运行此代码时,它甚至不会检查 META-INF 文件夹中的类。但在 java 9 中,它将找到并使用该类的更新版本。

          【讨论】:

            【解决方案6】:

            我不是那么出色的 Java 专家,但似乎 Java 中的条件编译是受支持且易于实现的。请阅读:

            http://www.javapractices.com/topic/TopicAction.do?Id=64

            引用要点:

            条件编译实践用于选择性地从类的编译版本中删除代码块。它使用编译器将忽略任何无法访问的代码分支的事实。 实现条件编译,

            • 将静态最终布尔值定义为某个类的非私有成员
            • 将有条件编译的代码放置在计算布尔值的 if 块中
            • 将布尔值设置为 false 以使编译器忽略 if 块;否则,保持其值为真

            当然,这让我们可以“编译”出任何方法中的代码块。要删除类成员、方法甚至整个类(可能只留下一个存根),您仍然需要一个预处理器。

            【讨论】:

            • 只有当您有可编译的代码时,您在此处指定的方法才有效。
            【解决方案7】:

            不,Java 中不支持条件编译。

            通常的计划是将应用的操作系统特定位隐藏在Interface 后面,然后在运行时检测操作系统类型并使用Class.forName(String) 加载实现。

            在您的情况下,您没有理由不能使用 Java 1.6 和 -source 1.5 -target 1.5 编译 OS*(实际上是您的整个应用程序),然后在工厂方法中获取 OS 类(其中现在将是一个接口)检测java.awt.Desktop 类可用并加载正确的版本。

            类似:

             public interface OS {
                 void openFile(java.io.File file) throws java.io.IOException;
             }
            
             public class OSFactory {
                 public static OS create(){
                     try{
                         Class.forName("java.awt.Desktop");
                         return new OSJ6();
                     }catch(Exception e){
                         //fall back
                         return new OSJ5();
                     }
                 }
             }
            

            【讨论】:

            【解决方案8】:

            下面介绍的 Ant 脚本提供了漂亮而干净的技巧。

            链接:https://weblogs.java.net/blog/schaefa/archive/2005/01/how_to_do_condi.html

            例如,

            //[ifdef]
            public byte[] getBytes(String parameterName)
                    throws SQLException {
                ...
            }
            //[enddef]
            

            使用 Ant 脚本

                    <filterset begintoken="//[" endtoken="]">
                        <filter token="ifdef" value="${ifdef.token}"/>
                        <filter token="enddef" value="${enddef.token}"/>
                    </filterset>
            

            更多详情请点击上方链接。

            【讨论】:

            • 链接已损坏。
            【解决方案9】:

            在 Gareth 提议的接口后面隐藏两个实现类可能是最好的方法。

            也就是说,您可以使用 ant 构建脚本中的替换任务引入一种条件编译。诀窍是在您的代码中使用 cmets,这些 cmets 在编译源代码之前通过文本替换打开/关闭,例如:

            /*{{ Block visible when compiling for Java 6: IFDEF6
            
            public static void openFile(java.io.File file) throws java.io.IOException {
              // open the file using java.awt.Desktop
              ...
            
            /*}} end of Java 6 code. */
            
            /*{{ Block visible when compiling for Java 5: IFDEF5
            
              // open the file using alternative methods
              ...
            
            /*}} end of Java 5 code. */
            

            现在在 ant 中,当您为 Java 6 编译时,将“IFDEF6”替换为“*/”,给出:

            /*{{ Block visible when compiling for Java 6: */
            
            public static void openFile(java.io.File file) throws java.io.IOException {
              // open the file using java.awt.Desktop
              ...
            
            /*}} end of Java 6 code. */
            
            /*{{ Block visible when compiling for Java 5, IFDEF5
            
            public static void openFile(java.io.File file) throws java.io.IOException {
              // open the file using alternative methods
              ...
            
            /*}} end of Java 5 code. */
            

            在为 Java 5 编译时,替换“IFDEF5”。请注意,您需要小心在 /*{{/*}} 块内使用 // comments

            【讨论】:

            • 是的,这是一个有用的技巧,出于安全原因,我用它在生产版本中“关闭”标准输出(即,根据运行时条件执行此操作是不够的)。
            【解决方案10】:

            您可以使用反射进行调用并使用 Java 5 编译代码。

            例如

            Class clazz = Class.forName("java.package.ClassNotFoundInJavav5");
            Method method = clazz.getMethod("methodNotFoundInJava5", Class1.class);
            method.invoke(args1);
            

            您可以捕获任何异常并回退到适用于 Java 5 的东西。

            【讨论】:

              猜你喜欢
              • 2010-10-18
              • 2016-03-10
              • 1970-01-01
              • 2015-11-13
              • 1970-01-01
              • 1970-01-01
              • 2011-09-17
              • 1970-01-01
              相关资源
              最近更新 更多