• Java中赋值语句的返回值(通过反编译来分析)


    引言

    今天在看书的时候,看到了一句:

    return (count = ++ temp;)
    

    看到这行代码的时候,有点莫名其妙,因为以前并没有见过类似的用法。

    仔细想想,这里的返回的应该是count = ++ temp这一语句的返回值,而这是一个赋值语句,那么在Java里的赋值语句的是否有返回值呢?接下来,博主就准备自己动手试一下。

    Java中赋值语句的返回值

    首先,准备了一个简单的例子来实验:

    // AssignReturnTest.java
    public class AssignReturnTest {
        public static void main(String[] args) {
            int count = 8;
            int res = (count = 21);
        }
    }
    

    简单解释一下,这个例子中,在int res = (count = 21);中有两层赋值语句,内层的是count = 21,然后将这个语句的返回值赋给了外层的res

    首先编译一遍,没有报错,点击运行也可以正常跑通,说明对于变量res的赋值语句int res = (count = 21);是没有问题的。

    接下来,在这一句设定一个断点:
    设定断点
    然后进行调试,运行到这一赋值语句的下一行(即下图的第7行花括号处):
    在这里插入图片描述
    这时,可以看到各个变量的值:
    在这里插入图片描述
    这里要注意有两个点:

    • count已经被赋值为21,说明语句后半部分的赋值语句被正常执行
    • res也被赋值为21,说明等号右端的21不止赋给了变量count,也作为返回值返回到前一个等号,并赋给了res

    由这个例子,我们发现,一个赋值语句的返回值就是赋值语句中等号右端的值,并且,将赋值语句作为返回值的时候,这个赋值语句也是被正常执行的。

    如果不想继续深究的话,基本上,记住这个规则就可以了。但是如果想要细细研究一下的话,我们可以再详细地看一看这个语句是怎样被执行的。

    通过反编译得到的字节码分析

    这里,我们可以通过对于.class文件进行反汇编,得到这个代码的字节码,来仔细看看这条语句是怎样被执行的。

    字节码文件是可以被直接打开阅读的,但多少还是有些不便,但是,Oracle已经为我们准备好了一个专门用于分析Class文件的字节码的工具:javap,接下来我们可以看一下具体怎么使用这个工具。

    首先,我们打开cmd,并进入存放代码编译得到的.class文件的路径(小技巧:如何快速地打开指定文件夹的cmd),比如我用的IDE是IDEA,就在程序设置的out文件夹(默认就在项目文件夹下,与src平级),里面的各个文件的路径与项目中是一样的。或者,没有在IDE中编写或者想要自己编译的话,可以找到所写的文件AssignReturnTest.java的路径,并在cmd中用javac命令得到.class文件。

    在cmd中进入.class文件路径之后,我们键入’javap -verbose AssignReturnTest.class`命令,即可在cmd中打印出文本化的可阅读的字节码内容:

     Last modified 2019-5-13; size 497 bytes
      MD5 checksum cabfd26a2477fdf6540a362b22d5e98c
      Compiled from "AssignReturnTest.java"
    public class learning_java.GrammarTest.AssignReturnTest
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #3.#20         // java/lang/Object."<init>":()V
       #2 = Class              #21            // learning_java/GrammarTest/AssignReturnTest
       #3 = Class              #22            // java/lang/Object
       #4 = Utf8               <init>
       #5 = Utf8               ()V
       #6 = Utf8               Code
       #7 = Utf8               LineNumberTable
       #8 = Utf8               LocalVariableTable
       #9 = Utf8               this
      #10 = Utf8               Llearning_java/GrammarTest/AssignReturnTest;
      #11 = Utf8               main
      #12 = Utf8               ([Ljava/lang/String;)V
      #13 = Utf8               args
      #14 = Utf8               [Ljava/lang/String;
      #15 = Utf8               count
      #16 = Utf8               I
      #17 = Utf8               res
      #18 = Utf8               SourceFile
      #19 = Utf8               AssignReturnTest.java
      #20 = NameAndType        #4:#5          // "<init>":()V
      #21 = Utf8               learning_java/GrammarTest/AssignReturnTest
      #22 = Utf8               java/lang/Object
    {
      public learning_java.GrammarTest.AssignReturnTest();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return
          LineNumberTable:
            line 3: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   Llearning_java/GrammarTest/AssignReturnTest;
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=2, locals=3, args_size=1
             0: bipush        8
             2: istore_1
             3: bipush        21
             5: dup
             6: istore_1
             7: istore_2
             8: return
          LineNumberTable:
            line 5: 0
            line 6: 3
            line 7: 8
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       9     0  args   [Ljava/lang/String;
                3       6     1 count   I
                8       1     2   res   I
    }
    SourceFile: "AssignReturnTest.java"
    

    其中,我们关注的是这一部分:

    public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=2, locals=3, args_size=1
             0: bipush        8
             2: istore_1
             3: bipush        21
             5: dup
             6: istore_1
             7: istore_2
             8: return
          LineNumberTable:
            line 5: 0
            line 6: 3
            line 7: 8
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       9     0  args   [Ljava/lang/String;
                3       6     1 count   I
                8       1     2   res   I
    

    这一部分,即为程序中main()方法部分的内容。

    我们现在详细看一看其中的Code部分中的字节码指令,下面是对于指令我的理解,如有不足还请读者指出:

             0: bipush        8     // 将常量8推送至栈顶
             2: istore_1            // 将栈顶元素存入标号为1的局部变量(即count),并出栈
             3: bipush        21    // 将常量21推送至栈顶
             5: dup                 // 将栈顶元素21复制一份,并压入栈顶
             6: istore_1            // 将栈顶元素21存入标号为1的局部变量(即count),并出栈
             7: istore_2            // 将栈顶元素21存入标号为2的局部变量(即res),并出栈
             8: return              // 方法结束
    

    代码行int res = (count = 21);所对应的即为上面标号3 - 7的指令,即将21入栈,并再复制一份,也压入栈顶,然后按顺序将栈顶的两个21陆续出栈并赋给两个局部变量:count、res,而这与之前我们所分析的赋值规则是相吻合的。

    写在最后

    因为在平时用python用的颇多,所以也在python中试了一下:

    return i = 2
    

    结果,报错。。。。
    说明在python中并没有对赋值语句的返回值的支持。

  • 相关阅读:
    软工实践
    福大软工 · 最终作业
    福大软工 · 第十二次作业
    Beta冲刺(7/7)
    Beta冲刺(5/7)
    Beta 冲刺(6/7)
    Beta冲刺 (4/7)
    Beta冲刺 (3/7)
    Beta冲刺 (2/7)
    Beta 冲刺(1/7)
  • 原文地址:https://www.cnblogs.com/liulaolaiu/p/11744403.html
Copyright © 2020-2023  润新知