Java条件编译:如何防止代码块被编译?

Posted

技术标签:

【中文标题】Java条件编译:如何防止代码块被编译?【英文标题】:Java conditional compilation: how to prevent code chunks from being compiled? 【发布时间】: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 预处理器做的比条件代码编译更多。我感到有点沮丧的是,人们将预处理器问题标记为重复:关于“条件编译”主题的主题。 【参考方案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();
         
     
 

【讨论】:

条件编译可以半做:见***.com/a/1922636/632951【参考方案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 的东西。

【讨论】:

以上是关于Java条件编译:如何防止代码块被编译?的主要内容,如果未能解决你的问题,请参考以下文章

如何用Java实现条件编译

C语言条件编译(ifdefifndefendif)

如何防止代码被反编译

如何有条件地为 Catalyst 编译代码?

Java 中的条件编译:编译器会从类中省略“始终为假”的块吗?

在 Visual Studio 2010 中防止条件表达式中的赋值