在java中,return后面跟着语句,编译时一定报错么?

 
public class Demo02 {
 
    public static void main(String[] args) {
 
        test();
    }
 
    public static void test() {
        int a = 3;
        if (true) {
            return;
        }
        //else return;
 
        System.out.println(1);
    }
}

如上代码,if的条件是true,这就代表这调用test方法时,return一定会执行,后面的打印语句看起来是无法执行到的,按照平常来讲,这种代码直接就会编译报错,但是实际上是没有报错,除非加上else return;才会出现报错,请问这是为什么呢?JVM在进行编译的时候是怎么编译这段代码的呢?java在编译if else的时候会检查if()中的条件么?

感谢三位大佬,从流程控制和底层帮我解释了该问题,虽然有些可能因为自己水平不够看的不清楚,但是在三位的解答中,我明白了死代码和不可达代码是不一样的,再次感谢。

阅读 2.6k
4 个回答

为啥有死代码也没报错

编译时报错的原因一般是语法错误(比如少括号,拼错关键字),语义错误(比如在循环外使用break,把double赋值给int强制cast除外,应为这样会对操作对象重新解释,最后还是能生成底层语言),你这里有了dead code也不报错的原因是本质上他没有语法错误,也没有语义错误,也就是他们的确能够成功的转换为更底层的语言,你写的test方法,他的更底层伪类汇编代码可能会是这样的

    public static void test() {
        int a = 3;
        if (true) {
            return;
        }
        //else return;
 
        System.out.println(1);
    }
test:    mov $t0, $3                   # int a = 3
         jumpIfFlase true label1       # if (true == false) goto label1
         ret
label1:  
         mov $a0, $1                   # 放置第一个参数
         call System.out.println       # 调用println
         ret                           # 函数结束必须有一个显示的ret

是怎么编译这段代码的呢

编译器明显知道有死代码所以产生的底层语言可能是这样的

test:    mov $t0, $3                   # int a = 3
         ret

java在编译if else的时候会检查if()中的条件么

当然会,编译器作为计算机领域迭代了快一个世纪软件,检查常量表达是只是他其中做的一个优化而已,现在的编译器,会在常量折叠,公共表达式提取,方法内联,强度削减,优化寄存器分配(减少寄存器溢出的数量),代码局部性(减少缓存MISS的概率),指令集并行性(重排指令顺序减少指令之间的数据依赖),数据并行性(SIMD)都下足功夫

编译器是如何检测出死代码的

控制流分析:编译器会根据一个程序所有可能的跳转逻辑构建出一张控制流图,有了图之后就可以根据一些常量表达式提前将一些不可能达到的分支删除,比如test方法优化前的控制流图是这样的
image.png
由于我们可以知道程序总是会走true分支所以完全可以把!true则条分支上的所有节点删除而不会影响程序的正确性,删除后的控制流图是这样的
image.png

新手上路,请多包涵

return后面跟的语句应该跟返回值一致,不然编译肯定报错阿

Dead Code 不检查 return,只检查是否有在编译期即可确定的、恒成立的条件。

也就是说,即便你不写 return,而是:

if (true) {
    System.out.println(1);
} else {
    System.out.println(2);
}

也是会有编译警告的。

代码紧跟在 return; 后面,或者流程上紧跟在 return; 后面才会编译出错(除了 for 循环流程,见文末)
可以当成是一种语义错误,具体看一下以下 3 种情况

  1. return;
    System.out.println(1); // 不可达语句(Unreachable Statement)
    stateDiagram-v2
    A: return
    B: System.out.println(1)
    [*] --> A
    A --> B
    B --> [*]

    代码不应该紧跟在 return; 后面,编译出错

  2. if (true) {
      return;
    }
    System.out.println(1); // 死代码(Dead Code)
    stateDiagram-v2
    A: return
    B: System.out.println(1)
    C: if (true)
    state if <<choice>> 
    [*] --> C
    C --> if
    if --> A: true
    if --> B: false
    B --> A
    A --> [*]

    代码不是紧跟在 return; 后,甚至可以改成

    if (false) {
      System.out.println(1); // 死代码(Dead Code)
    }

    这样连 return; 都没了,编译通过,但 if 和死代码会被删除,test() 会被编译成:

    public static void test() {
      int a = 3;
    }
  3. if elsereturn;

    if (任意条件) {
      return;
    } else {
      return;
    }
    System.out.println(1); // 不可达语句(Unreachable Statement)
    stateDiagram-v2
    A: return
    B: System.out.println(1)
    C: if (任意条件)
    state if <<choice>> 
    [*] --> C
    C --> if
    if --> A: true
    if --> A: false
    A --> B
    B --> [*]

    相当于

    if (任意条件) {}
    else {}
    return;
    System.out.println(1); // 不可达语句(Unreachable Statement)

    代码在流程上紧跟在 return; 后面,编译出错

注意 for 循环流程是个反例:

for (int i = 0; i < 3; i++) {
  return;
}

流程上相当于

for (int i = 0; i < 3;) {
  return;
  i++;
}

但前者 i++ 是死代码,编译通过,后者 i++ 是不可达语句,编译出错

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏