zoukankan      html  css  js  c++  java
  • 1、探究java方法参数传递——引用传递?值传递!

    原创博文,转载请注明出处。谢谢~~


    java程序运行时,其对象是怎么进行放置和安排的呢?内存是怎么分配的呢?理解好这个很有好处!java有5个地方可以存储数据:

    1、寄存器。这是最快的存储区,位于处理器内部。java程序员无法感知到它的存在,所以不用深究。

    2、堆栈。位于内存中,通过堆栈指针可以获得直接的内存分配。对象引用和基本数据(int,boolean等)存放于堆栈中。注意:是对象的引用,对象数据本身却是存放在中的。于对象而言,在堆中存放的只是对象数据的一个地址(类似于C语言的指针),通过它可以访问到对象本身的属性和方法。

    3、堆。同样位于内存中,用于存放所有的java对象。需要一个对象的时候,只需要用new写一行简单的代码,当执行这行代码的时候,会直接在堆中进行空间分配。

    4、常量存储。常量值直接存放在代码内部,保证安全。

    5、非RAM存储。比如,流对象和持久化对象,可以存放在磁盘文件中。

    先来看一看下面这个对象是如何存放的

    String s = new String("hello");

    下图表示了其存储状态:

    new String("hello") 语句中用关键字 new 定义了一个String的对象,并为其在 “堆内存” 中分配了合适的存储空间。

    s 是一个String型对象的引用,实际上就是堆内存中一个String型对象的地址。引用 s 保存在 “堆栈” 中

    清楚了对象和其引用的存储方式以后,我们再来看看方法的参数传递是怎么一回事情。看看下面语句:

    List-1:

    1 public static void main(String[] args) {
    2         
    3     Func f = new Func();
    4         
    5     A a1 = new A();
    6     f.modify(a1);
    7 }

    语句很简单,在main方法中首先定义了一个Func对象f和A对象a1,然后将a1作为参数传递给f的modify方法中。程序在第6行的时候会从main方法跳转到f.modify(...)方法内部执行。下图展示了数据的保存状态:

    可以清楚的看到main方法向modify方法中传递的是对象引用 a1 的一份拷贝(为了方便区分,将拷贝命名为aa1),这两个引用都指向同一块堆内存区域(同一个A对象)。所以,我们可以想象,在modify方法中可以通过 aa1 来修改A对象的属性,而且其变化在由modify返回main以后依然会保留下来

    下面通过一个例子来分析一下:

    List-2:

     1 package com;
     2 
     3 public class A {
     4     int cunt = 5;
     5     String str = "hello";
     6     
     7     public String toString() {
     8         return "A [cunt=" + cunt + ", str=" + str + "]";
     9     }
    10 }
     1 package com;
     2 
     3 public class Func {
     4     
     5     public void modify(A a){
     6         //通过“对象引用”来修改对象的属性
     7         a.cunt = 111;
     8         a.str = "modify";
     9     }
    10     
    11     public void newObj(A a){
    12         //创建了一个新的对象,企图在方法中将新对象引用赋值给形参a
    13         //方式来达到修改外围方法中实参引用的目的。
    14         //注意:这个是做不到的。
    15         a = new A();
    16         a.cunt = 222;
    17         a.str = "newObj";
    18     }
    19     
    20     public A reNewObj(A a){
    21         //返回一个新对象,在外围方法中将返回的对象引用赋值给实参
    22         //这种方法是可以实现的。
    23         a = new A();
    24         a.cunt = 333;
    25         a.str = "reNewObj";
    26         return a;
    27     }
    28     
    29     public static void main(String[] args) {
    30         
    31         Func f = new Func();
    32         
    33         //在modify方法中通过形参来改变a1对象的属性,在modify
    34         //方法返回以后,修改的属性可以保留下来
    35         A a1 = new A();
    36         f.modify(a1);
    37         System.out.println(a1);
    38         
    39         //企图通过newObj方法让a2指向新的对象40         //但是,这样做不可能达到目的
    41         A a2 = new A();
    42         f.newObj(a2);
    43         System.out.println(a2);
    44         
    45         //reNewObj方法会返回一个新的对象引用,并将其赋值给a3
    46         //这种方式可以让a3指向一个新的对象。
    47         A a3 = new A();
    48         a3 = f.reNewObj(a3);
    49         System.out.println(a3);
    50         
    51     }
    52 }

    运行结果:

    我们知道,A对象初始化的时候 cunt = 5, str = "hello" 。现在来分析上面的三个结果的原理。

    1、第一个结果是 “cunt = 111, str = "modify" 说明 f.modify(a1) 方法中通过形参a1对A对象属性的修改被保留了下来。现在来分析,为什么会保留下来??

     

    解释:

    ①. 在main方法中,a1指向堆内存中的一个A对象,a1是作为A对象的一个引用,其值是“引用”(对空间的地址值)。

    调用f.modify(a1)的时候,a1作为实参传递给modify方法,由于java参数传递是属于“值传递”(值的拷贝),所以在modify堆栈中会有a1的一份拷贝,两个a1指向同一个A对象。

    ②. 通过modify堆栈中的引用a1可以操作其所指向的对内存中的对象。在modify方法中将堆内存中的A对象属性值修改为“cunt = 111, str = "modify"”。

    ③. modify方法返回以后,modify堆栈中的a1引用所占内存被回收,main堆栈中的a1依然指向最开始的那个对象空间。由于在modify方法中已经将对象的属性修改了,所以在main中的a1可以看见被修改后的属性值。

    2、第二个结果是 “cunt = 5, str = "hello",说明main方法中的a2所指向的对象没有变化。42行中 f.newObj(a2); 方法的原本意图是:在newObj方法中新创建一个A对象,并将其赋值给newObj的形参a2,并且期望在newObj方法返回以后main方法的局部变量a2也指向新的A对象。

    可是,从打印的结果来看,main方法中的局部变量a2依然指向原来的A对象,并没有指向newObj方法中新建的A对象。

    那么,这个是为什么呢??

    解释:

    ①. 前面两个图和1中的解释是一样的,无非就是一个参数的“值传递问题”。

    ②. 在newObj方法中有这样的三条语句:a = new A(); a.cunt = 222; a.str = "newObj";

       其执行过程是这样的:在堆内存中开辟一块新的A对象空间,让newObj堆栈中的a2引用指向新的对象空间 → 后两句语句会修改新对象的属性值。

         注意,此时main堆栈中的a2依然指向最开始的那个对象空间,而newObj堆栈中的a2却指向了新的对象。

    ③. newObj方法返回,newObj堆栈空间被回收,堆空间中的新对象没有任何变量引用它,在合适的时机会被JVM回收。此时,返回到main方法中,此时的a2依然指向

    依然指向最开始的那个A对象,其属性值没有发生变化。

    所以,外围方法中的实参无法看见在内部方法中新创建的对象。除非,新创建的对象作为内部方法的返回值,并且在外围方法中将其赋值给实参变量。就像47~49行所做的那样:在reNewObj方法中新创建一个对象并赋值给形参,同时在方法最后将新对象作为返回值返回给外围方法,进一步在外围方法中将返回的对象赋值为a3,从而达到目的。

  • 相关阅读:
    UNIX网络编程——处理服务器中大量的TIME_WAIT
    UNIX网络编程——套接字选项(心跳检测、绑定地址复用)
    UNIX网络编程——TCP 滑动窗口协议
    [Training Video
    [Training Video
    [Training Video
    [Training Video
    [Training Video
    Use formatter to format your JAVA code
    右键菜单没有新建文本文件了,怎么办?
  • 原文地址:https://www.cnblogs.com/lj95801/p/5241412.html
Copyright © 2011-2022 走看看