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中并没有对赋值语句的返回值的支持。

posted @ 2019-05-13 20:07  点点爱梦  阅读(1047)  评论(0编辑  收藏  举报