Java 编译 - 有没有办法告诉编译器忽略我的部分代码?
-
09-06-2019 - |
题
我维护一个 Java Swing 应用程序。
为了向后兼容 java 5(针对 Apple 机器),我们维护两个代码库,一个使用 Java 6 的功能,另一个不使用这些功能。
除了 3-4 个使用 Java 6 功能的类之外,代码基本相同。
我希望只维护 1 个代码库。有没有办法在编译过程中让 Java 5 编译器“忽略”我的代码的某些部分?
我不想简单地注释/取消注释我的代码部分,具体取决于我的 java 编译器的版本。
解决方案
假设这些类与 1.5 和 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 文件更大,但它会合并您的代码并消除您拥有的任何代码重复。您的编辑器可能会抱怨无法访问的代码或其他什么,但编译器应该幸福地忽略它。
其他提示
关于使用自定义类加载器和动态注释代码的建议在维护和保持理智方面有点令人难以置信,无论哪个可怜的灵魂在你搬到新的牧场后接手了这个项目。
解决办法很简单。将受影响的类拉出到两个单独的独立项目中 - 确保包名称相同,然后编译成 jar,然后可以在主项目中使用。如果您保持包名称相同,并且方法签名相同,那么没有问题 - 只需将您需要的 jar 版本放入部署脚本中即可。我假设您运行单独的构建脚本或在同一脚本中具有单独的目标 - ant 和 maven 都可以轻松处理有条件地抓取文件并复制它们。
我认为最好的方法可能是使用构建脚本。您可以将所有代码放在一个位置,通过选择要包含哪些文件和不包含哪些文件,您可以选择要编译的代码版本。请注意,如果您需要比每个文件更细粒度的控制,这可能没有帮助。
您可能可以重构您的代码,以便实际上不需要条件编译,而只需要条件类加载。像这样的东西:
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();
}
}
}
}
这可能需要花费大量精力,具体取决于您拥有多少特定于版本的代码片段。
保留一个在 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 等。ETC。
可以使用相同的技术来创建对可选库而不是较新的 JDK 的支持。
不是真的,但有解决方法。看http://forums.sun.com/thread.jspa?threadID=154106&messageID=447625
也就是说,您应该坚持至少拥有一个用于 Java 5 的文件版本和一个用于 Java 6 的文件版本,并根据需要通过构建或 make 包含它们。将所有内容都放在一个大文件中并尝试让 5 的编译器忽略它不理解的内容并不是一个好的解决方案。
华泰
——妮基——
这会让所有 Java 纯粹主义者感到畏缩(这很有趣,呵呵),但我会使用 C 预处理器,将 #ifdefs 放入我的源代码中。makefile、rakefile 或任何控制构建的文件都必须运行 cpp 来生成临时文件以供编译器使用。我不知道蚂蚁是否可以做到这一点。
虽然 stackoverflow 看起来会是 这 所有答案的地方,你可能没有人在看 http://www.javaranch.com 为了Java智慧。我想这个问题可能很久以前就已经被处理过。
这取决于您想要使用哪些 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 之后的任何版本(根据 这一页).
您可以专门在 Java6 上进行所有编译,然后使用 System.getProperty("java.version") 有条件地运行 Java5 或 Java6 代码路径。
您可以在类中包含仅 Java6 的代码,只要不执行仅 Java6 的代码路径,该类就可以在 Java5 上正常运行。
这是一个用于编写小程序的技巧,这些小程序将在古老的 MSJVM 一直到全新的 Java 插件 JVM 上运行。
Java中没有预编译器。因此,无法像 C 中那样执行#ifdef。构建脚本将是最好的方法。
您可以获得条件编译,但效果不是很好 - javac 会忽略无法访问的代码。因此,如果您正确构建代码,则可以让编译器忽略部分代码。要正确使用它,您还需要将正确的参数传递给 javac,这样它就不会将无法访问的代码报告为错误,并拒绝编译:-)
上面提到的公共静态最终解决方案还有一个作者没有提到的额外好处 - 据我了解,编译器将在编译时识别它并编译出引用该最终变量的 if 语句中的任何代码。
所以我认为这正是您正在寻找的解决方案。
一个简单的解决方案可能是:
- 将不同的类放置在正常类路径之外。
- 编写一个简单的自定义类加载器并将其作为默认安装在 main 中。
- 对于除 5/6 之外的所有类,casloader 可以遵循其父级(普通系统类加载器)
- 对于 5/6 的(这应该是唯一无法被父级找到的),它可以通过“os.name”属性或您自己的属性来决定使用哪个。
您可以使用反射 API。将所有 1.5 代码放在一个类中,将 1.6 api 放在另一个类中。在您的 ant 脚本中创建两个目标,一个用于 1.5,不会编译 1.6 类,另一个用于 1.6,不会编译 1.5 类。在你的代码中检查你的java版本并使用反射加载适当的类,这样javac就不会抱怨缺少函数。这就是我在 Windows 上编译 MRJ(Mac Runtime for Java)应用程序的方法。