【问题标题】:Java Compilation - Is there a way to tell the compiler to ignore parts of my code?Java 编译 - 有没有办法告诉编译器忽略我的部分代码?
【发布时间】:2010-09-09 14:31:20
【问题描述】:

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

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

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

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

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

【问题讨论】:

    标签: java swing compilation


    【解决方案1】:

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

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

    【讨论】:

    • 独立项目是多余的,单独的源文件夹和自定义构建脚本就可以了。使用 src/main 的源文件夹,以及通用代码库,例如,src/java5 和 src/java6 用于依赖于每个版本的功能。
    【解决方案2】:

    假设这些类具有相似的功能,但在实现上存在 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 文件变大,但它会整合您的代码并消除您拥有的任何代码重复。您的编辑器可能会抱怨无法访问的代码或其他任何问题,但编译器应该很高兴地忽略它。

    【讨论】:

    • 这是 Java 编译器的记录行为吗?
    • 这个答案似乎不正确。我做了一个使用 java.io.Console 和上述方法的快速 Java 应用程序。编译器在 1.5 中失败,出现如下错误:Test.java:8: cannot find symbol: method console() location: class java.lang.System
    • 我可以看到您如何在 1.6 中编译并在 1.5 JVM 中运行时排除某些运行时功能。当然,您还需要重新编译成 1.5 JVM 字节码格式以避免“无效的次要版本 49.0”错误。对于这种方法,这个解决方案可以工作。
    • 这个答案不正确的。 Java 5 编译器将无法编译“Java 6 stuff going here”块,因此您还没有解决问题。更好的解决方案是有两个接口实现,一个用于 Java 5,一个用于 Java 6。然后您的构建工具可以在编译时包含正确的实现。 Ant 可以通过使用构建属性轻松做到这一点。
    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

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

      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();
                 }
              }
       }
      
      }
      

      这可能需要大量工作,具体取决于您拥有多少特定于版本的代码。

      【讨论】:

        【解决方案5】:

        保留一个在 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 中将它们配置为单独的项目。关键是主根可以独立编译,并且很清楚您可以在哪个根中使用什么,而如果您尝试编译不同的单个根目录的各个部分很容易意外地将 JDK 6 的使用“泄漏”到错误的文件中。

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

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

        【讨论】:

          【解决方案6】:

          并非如此,但有一些解决方法。看 http://forums.sun.com/thread.jspa?threadID=154106&messageID=447625

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

          HTH

          -- 尼基--

          【讨论】:

            【解决方案7】:

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

            虽然 stackoverflow 看起来将是所有答案的 地方,但您可以通过 http://www.javaranch.com 来了解 Java 智慧。我想这个问题很久以前就在那里处理过了。

            【讨论】:

              【解决方案8】:

              这取决于您要使用哪些 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 之后的任何版本(根据 this page)。

              【讨论】:

                【解决方案9】:

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

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

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

                【讨论】:

                  【解决方案10】:

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

                  【讨论】:

                  • 他建议使用 s C 编译器来运行实际的 C 预处理器,它会输出修改后的源文件,然后在这些文件上运行 Java 编译器。大多数(如果不是全部)C 编译器都有一个仅运行预处理器的开关。
                  【解决方案11】:

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

                  【讨论】:

                    【解决方案12】:

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

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

                    【讨论】:

                      【解决方案13】:

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

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

                      【讨论】:

                        【解决方案14】:

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

                        【讨论】:

                          猜你喜欢
                          • 2020-12-29
                          • 2020-05-03
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 2017-04-23
                          • 1970-01-01
                          相关资源
                          最近更新 更多