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

Posted

技术标签:

【中文标题】Java 中的条件编译:编译器会从类中省略“始终为假”的块吗?【英文标题】:Conditional compilation in Java: will compiler omit "always false" blocks from class? 【发布时间】:2012-06-11 19:02:31 【问题描述】:

下面是我的代码的 sn-p:

class A  

   private boolean debug = false;

   // Called when server boots up.
   public void init (property)  
      debug = property.getBoolean ("debug_var"); // read debug from a config file.
   

   // some other function  
   public void foo ()  
       if (debug)  
                 System.out.println ("From inside the debug block");
       
   
 

当我运行代码时,如果(调试)实际上在配置文件中打印出“从调试块内部”如果 debug == true。

两个问题:

    那么,在这种情况下,编译器是否将 if 块包含在 .class 文件中只是因为变量 debug 的值可能会在运行时发生变化?

    如果这是真的,那么在某些环境中,如何避免将某些代码添加到 .class 文件中?

【问题讨论】:

与***.com/questions/1922521/…重复 【参考方案1】:

如果你必须这样做,大多数日志框架都有自己设置日志详细级别的方法,它们只是不会在运行时输出任何级别太低的日志语句。使用日志框架来正确执行此操作。

例如,使用内置的 java.util.logging 框架,您可以执行类似的操作

Logger.getLogger("ThisClass").log(Level.FINE, "Log message");

仅在日志级别设置为 FINE 或更低时打印,但在日志级别为 CONFIG 或更低时被忽略。

“条件编译”通常在 Java 中没有意义,但需要注意的是,JIT 会优化掉它可以确定永远不会执行的分支。

【讨论】:

知道了。谢谢!你能看看我上面对戴夫牛顿的回答的评论吗? 是的。我不认为有办法做到这一点。【参考方案2】:

最接近的方法是使用静态变量,它在运行时(基本上)是无用的。

但是,在您的示例中,JVM 可能会在运行足够多的时间后将其优化掉,因此如果您关心的是运行时效率,则可能不值得担心。

最后我会问你为什么要根据环境从 class 文件中删除代码 - 如果它不是可以在运行时更改的东西,那么你最好的选择是创建一些可以在运行时确定/注入的可插拔实现形式。

【讨论】:

我想要删除一些代码的原因是因为在开发环境中。我有一些我不想在生产中使用的失态。您能否详细说明一下您提到的开发可插拔实现,或者您能否指出一些好的资源。谢谢。 @FSP 好吧...我会修复代码或使其可配置。我看不出有任何真正的理由将其从 class 文件中删除,但您的情况尚不清楚。 “可插入”是指“在这个环境中,使用这个实现”——有很多方法可以做到这一点,大多数都围绕 some 形式的依赖注入/控制反转,所以我d首先瞄准那个。【参考方案3】:

正如其他人所指出的,Java 编译器通常非常幼稚,只会将您拥有的 Java 源代码转换为 Java 字节码。例如,if 语句最常被转换为条件分支,无论您是否发现分支条件始终为假是显而易见的。

然而,从理论的角度来看,没有什么可以阻止编译器找出某些分支永远不会被采用,并在字节码中简单地省略它们。 Java 语言规范中没有规定如何在以字节码为目标时编译某些内容。

【讨论】:

【参考方案4】:
    是的,编译器会将其包含在 .class 文件中。 没有条件编译机制。您可以使用编译器插件(在 JDK 1.7 及更高版本中)来模拟其中的一部分,或者您可以尝试使用 AspectJ 等字节码操作技术来实现类似的效果。

【讨论】:

规范不保证 1 和 2。 (所以从技术上讲,答案是错误的。) 什么?不允许编译器删除可达代码。它可以在某些条件下优化掉条件,但打印语句必须在那里。那是在规范中!对于 2,语言规范中没有任何内容允许条件编译。那我的回答怎么错了?【参考方案5】:

过去,如果您将布尔值声明为 staticfinal 并将其值设置为 false,编译器将忽略代码。不过,那是最初的 Java 编译器。我不知道当前的是否这样做(当前的已经存在很长时间了)。这背后的基本原理是让您可以使您的小程序下载更小。它充其量只是一个笨拙的机制,因此没有被广泛使用。

要对此进行测试,请将您的调试变量更改为 private static final boolean debug=false; 编译它,保存类文件,然后将其翻转为 true。然后再编译一遍,看看class文件有没有不同。顺便说一句,当 debug 声明为 final 时,您的 init 函数将不起作用。

【讨论】:

【参考方案6】:

没有什么能比得上条件编译(除非 java 找到一个保证不会被执行的条件)。您的类文件包含的字节码与您在 Java 文件中的任何内容相同。

编辑:正如 aiobee 所指出的,如果编译器发现分支语句不被执行,它可能会忽略它们。

【讨论】:

没有什么可以阻止编译器忽略分支,只要它们保证不被执行。 :-) 好吧,现在你的回答有点矛盾(或者你我对条件编译的定义不同。) 我认为这是定义“条件”的角度。

以上是关于Java 中的条件编译:编译器会从类中省略“始终为假”的块吗?的主要内容,如果未能解决你的问题,请参考以下文章

Java - 从类中调用私有数组列表[重复]

使用线程安全方法从类中的多个方法填充 Collection 或 Map

java 多态

Java中的即时编译器

从类中的串行端口访问 WinForms 控件

无法从类内部调用 ExtJS 类中的方法