12

我维护一个 Java Swing 应用程序。

为了向后兼容 java 5(对于 Apple 机器),我们维护了两个代码库,一个使用 Java 6 的功能,另一个没有这些功能。

代码基本相同,除了 3-4 个使用 Java 6 特性的类。

我希望只维护 1 个代码库。在编译期间有没有办法让 Java 5 编译器“忽略”我的代码的某些部分?

我不想简单地注释/取消注释我的代码部分,这取决于我的 java 编译器的版本。

4

14 回答 14

6

关于使用自定义类加载器和动态注释代码的建议有点令人难以置信,当涉及到维护和保存任何一个可怜的灵魂在你改组到新的牧场后拿起项目的理智时。

解决方案很简单。将受影响的类拉出到两个独立的独立项目中——确保包名相同,然后编译成 jar,然后可以在主项目中使用。如果您保持包名称相同,并且方法签名相同,则没有问题 - 只需将所需的 jar 版本放入部署脚本中即可。我假设您运行单独的构建脚本或在同一脚本中有单独的目标 - ant 和 maven 都可以轻松处理有条件地抓取文件并复制它们。

于 2008-09-16T19:58:13.073 回答
4

我认为这里最好的方法可能是使用构建脚本。您可以将所有代码放在一个位置,通过选择要包含哪些文件和不包含哪些文件,您可以选择要编译的代码版本。请注意,如果您需要比每个文件更细粒度的控制,这可能无济于事。

于 2008-09-16T16:23:32.310 回答
4

假设这些类在实现上有 1.5 和 6.0 的差异时具有相似的功能,您可以将它们合并到一个类中。然后,无需编辑源代码以进行注释/取消注释,您就可以依赖编译器始终执行的优化。如果 if 表达式始终为 false,则 if 语句中的代码将不会包含在编译中。

您可以在其中一个类中创建一个静态变量以确定要运行的版本:

public static final boolean COMPILED_IN_JAVA_6 = false;

然后让受影响的类检查该静态变量并将不同的代码部分放在一个简单的 if 语句中

if (VersionUtil.COMPILED_IN_JAVA_6) {
  // Java 6 stuff goes here
} else {
  // Java 1.5 stuff goes here
}

然后,当您想编译另一个版本时,您只需更改该变量并重新编译。它可能会使 java 文件变大,但它会整合您的代码并消除您拥有的任何代码重复。你的编辑器可能会抱怨无法访问的代码或其他什么,但编译器应该很高兴地忽略它。

于 2008-09-16T17:00:44.123 回答
3

您可能可以重构您的代码,以便真正不需要条件编译,只需要条件类加载。像这样的东西:

public interface Opener{

public void open(File f);

 public static class Util{
        public Opener getOpener(){
          if(System.getProperty("java.version").beginsWith("1.5")){
           return new Java5Opener();
          }
          try{ 
            return new Java6Opener();
           }catch(Throwable t){
            return new Java5Opener();
           }
        }
 }

}

这可能需要很多努力,具体取决于您拥有多少特定于版本的代码。

于 2008-09-16T16:42:31.313 回答
2

保留一个在 JDK 5 下构建的“主”源根。添加第二个必须在 JDK 6 或更高版本下构建的并行源根。(应该没有重叠,即两者中都没有类。)使用接口来定义两者之间的入口点,以及一点点反射。

例如:

---%<--- main/RandomClass.java
// ...
if (...is JDK 6+...) {
    try {
        JDK6Interface i = (JDK6Interface)
            Class.forName("JDK6Impl").newInstance();
        i.browseDesktop(...);
    } catch (Exception x) {
        // fall back...
    }
}
---%<--- main/JDK6Interface.java
public interface JDK6Interface {
    void browseDesktop(URI uri);
}
---%<--- jdk6/JDK6Impl.java
public class JDK6Impl implements JDK6Interface {
    public void browseDesktop(URI uri) {
        java.awt.Desktop.getDesktop().browse(uri);
    }
}
---%<---

您可以使用不同的 JDK 等在 IDE 中将它们配置为单独的项目。关键是主根可以独立编译,并且非常清楚您可以在哪个根中使用什么,而如果您尝试编译单独的单个 root 很容易意外地将 JDK 6 的使用“泄漏”到错误的文件中。

除了像这样使用 Class.forName 之外,您还可以使用某种服务注册系统 - java.util.ServiceLoader(如果 main 可以使用 JDK 6 并且您想要对 JDK 7 的可选支持!)、NetBeans Lookup、Spring 等。等等

相同的技术可用于创建对可选库而不是更新的 JDK 的支持。

于 2008-09-16T16:37:57.830 回答
1

不是真的,但有解决方法。见 http://forums.sun.com/thread.jspa?threadID=154106&messageID=447625

也就是说,您应该坚持至少有一个用于 Java 5 的文件版本和一个用于 Java 6 的文件版本,并通过适当的构建或制作来包含它们。将它们全部放在一个大文件中并试图让 5 的编译器忽略它不理解的东西并不是一个好的解决方案。

高温高压

-- 尼基 --

于 2008-09-16T16:26:49.057 回答
1

这将使所有 Java 纯粹主义者畏缩(这很有趣,呵呵),但我会使用 C 预处理器,将#ifdefs 放入我的源代码中。makefile、rakefile 或任何控制您的构建的东西都必须运行 cpp 来制作临时文件以供编译器使用。我不知道蚂蚁是否可以做到这一点。

虽然 stackoverflow 看起来它将成为所有答案的地方,但您可能会发现没有人会在http://www.javaranch.com上寻找 Java 智慧。我想这个问题很久以前就在那里处理过了。

于 2008-09-16T16:30:42.823 回答
1

这取决于您要使用哪些 Java 6 功能。对于像向 JTables 添加行排序器这样简单的事情,您实际上可以在运行时进行测试:

private static final double javaVersion =
         Double.parseDouble(System.getProperty("java.version").substring(0, 3));
private static final boolean supportsRowSorter =
         (javaVersion >= 1.6);

//...

if (supportsRowSorter) {
    myTable.setAutoCreateRowSorter(true);
} else {
    // not supported
}

此代码必须使用 Java 6 编译,但可以使用任何版本运行(不引用新类)。

编辑:更正确地说,它适用于自 1.3 以来的任何版本(根据此页面)。

于 2008-09-16T19:39:52.650 回答
1

您可以专门在 Java6 上进行所有编译,然后使用 System.getProperty("java.version") 有条件地运行 Java5 或 Java6 代码路径。

您可以在类中包含纯 Java6 代码,只要不执行纯 Java6 代码路径,该类将在 Java5 上正常运行。

这是一种用于编写小程序的技巧,这些小程序将在古老的 MSJVM 上一直运行到全新的 Java 插件 JVM。

于 2008-09-16T20:54:20.677 回答
0

Java 中没有预编译器。因此,没有办法像在 C 中那样执行#ifdef。构建脚本将是最好的方法。

于 2008-09-16T16:32:20.527 回答
0

您可以获得条件编译,但不是很好 - javac 将忽略无法访问的代码。因此,如果您正确地构建代码,您可以让编译器忽略您的部分代码。要正确使用它,您还需要将正确的参数传递给 javac,这样它就不会将无法访问的代码报告为错误,并拒绝编译:-)

于 2008-09-16T16:35:22.517 回答
0

上面提到的公共静态最终解决方案还有一个作者没有提到的额外好处——据我了解,编译器将在编译时识别它并编译出 if 语句中引用该最终变量的任何代码。

所以我认为这就是您正在寻找的确切解决方案。

于 2008-09-16T17:06:30.973 回答
0

一个简单的解决方案可能是:

  • 将不同的类放在您的正常类路径之外。
  • 编写一个简单的自定义类加载器并将其安装在 main 中作为默认值。
  • 对于除了 5/6 类之外的所有类,cassloader 可以推迟到其父类(普通系统类加载器)
  • 对于 5/6 的(应该是唯一不能被父级找到的),它可以通过 'os.name' 属性或您自己的属性决定使用哪个。
于 2008-09-16T17:07:55.433 回答
0

您可以使用反射 API。将所有 1.5 代码放在一个类中,将 1.6 api 放在另一个类中。在您的 ant 脚本中创建两个目标,一个用于 1.5,它不会编译 1.6 类,另一个用于 1.6,不会编译 1.5 类。在您的代码中检查您的 java 版本并使用反射加载适当的类,这样 javac 就不会抱怨缺少函数。这就是我如何在 Windows 上编译我的 MRJ(Java 的 Mac 运行时)应用程序。

于 2008-09-28T15:00:03.140 回答