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

58
我的项目需要用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编译,我想在编译时排除它,并用另一种方法替换它,该方法调用run32dll(Windows)或open(Mac OS X),并使用Runtime.exec

问题:我该怎么做?注释能帮上忙吗?

注意:我使用ant,并且可以创建两个java文件OS4J5.javaOS4J6.java,其中包含OS类的所需代码,用于Java 1.5和1.6,并在编译之前将其中一个复制到OS.java中(或者一个丑陋的方式-有条件地替换OS.java的内容,具体取决于Java版本),但如果有其他方法,我不想这样做。

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

发现这篇帖子,但似乎没什么用。

非常感谢任何帮助。kh。


OS4J5OS4J6编译成.class文件,然后编写一个自定义类加载器来根据运行时版本选择哪个文件。 - JUST MY correct OPINION
1
@JUST 我无法使用Java 1.5同时编译,因为它不支持java.awt.Desktop,这会导致在OS4J6.java上出现错误。 - khachik
编译时请使用1.6版本。只要不使用1.6特有的API,.class文件就是向下兼容的。 - JUST MY correct OPINION
1
@JUST 我制作了一个简单的Java测试文件(没有1.6特定的代码),我使用1.6编译它,但我无法在1.5上运行它:Exception in thread "main" java.lang.UnsupportedClassVersionError: Bad version number in .class file. - khachik
预处理器做的远不止条件代码编译。我感到有点沮丧,因为人们将预处理器问题标记为“条件编译”主题的重复。 - will
9个回答

45

不,Java中没有支持条件编译的功能。

通常的做法是将应用程序中特定于操作系统的部分隐藏在一个接口后面,然后在运行时检测操作系统类型并使用Class.forName(String)加载实现。

在您的情况下,您可以使用Java 1.6编译OS*(以及整个应用程序)并使用-source 1.5 -target 1.5。然后,在获取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();
         }
     }
 }

1
条件编译可以半成品化:请参阅https://dev59.com/E3I-5IYBdhLWcg3wTWdu#1922636。 - Pacerier

19

像Gareth建议的那样,在接口背后隐藏两个实现类可能是最好的方法。

话虽如此,你可以通过在ant构建脚本中使用替换任务来引入一种条件编译。诀窍是在代码中使用可以在源代码编译之前通过文本替换打开/关闭的注释:

/*{{ 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" 替换为相应代码。注意,在 /*{{/*}} 块内使用// 注释时要小心。


是的,这是一个有用的技巧,我用它来在生产构建中“关闭”标准输出,出于安全原因(即基于运行时条件进行此操作是不够的)。 - Stephen Swensen

6
在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 5编译代码。
例如:
Class clazz = Class.forName("java.package.ClassNotFoundInJavav5");
Method method = clazz.getMethod("methodNotFoundInJava5", Class1.class);
method.invoke(args1);

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


6
下面介绍的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>

请点击上方链接获取更多详细信息。

链接已损坏。 - Don Hatch
条件编译检查一个条件。在孤独的 //[ifdef] 中,条件在哪里? - alife

5

4
我不是很擅长Java,但似乎Java支持条件编译并且易于操作。请参阅以下链接:http://www.javapractices.com/topic/TopicAction.do?Id=64 简要摘要如下:
条件编译的实践是用于从类的编译版本中可选地移除代码块的技术。它利用编译器会忽略任何无法到达的代码分支的特性。要实现条件编译,请按照以下步骤进行:
- 将一个静态的final boolean值定义为某个类的非私有成员 - 将需要被条件编译的代码放置在if块中,并对布尔值进行求值 - 如果将布尔值设置为false,则编译器将忽略if块;否则,保持其值为true 当然,这样我们可以在任何方法内“编译掉”代码块。要删除类成员、方法甚至是整个类(可能只剩下一个存根),您仍需要使用预处理器。

3
这里所指的方法只适用于可编译的代码。 - LXSoft
在某些情况下这是有用的。然而,在以下两种情况下它是无用的:1.在多个方法和声明之间编译时,2.(正如@LXSoft所指出的)当代码目前不工作时。 - alife

0

Java原始类型特化生成器支持条件编译:

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

这个工具有Maven和Gradle插件。


0

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


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