深入理解java方法的按值调用

在编程中,在方法调用之间复制数据的过程称为按值调用。在java中,我们不需要指定要传递的实际参数要使用按值调用,因为它是自动发生的实际上也是唯一的选择。不管传递给方法的实际参数是什么类型,相关的形式参数都会得到该数据的一份拷贝,这就是按值调用的工作原理。

有如下代码:

public class Test {

public void changeNum(int a){
    a=3;
    System.out.println("方法内a=3");
}
public void changeString(String b){
    b="s";
    System.out.println("方法内b="+b);
}
public void changeStringBuffer1(StringBuffer c){
    c.append("111");
    System.out.println("方法内c="+c);
}
public void changeStringBuffer2(StringBuffer d){
    d=new StringBuffer("1");
    System.out.println("方法内d="+d);
}

public static void main(String[] args) {
    Test t=new Test();
    int a=1;
    String b="b";
    StringBuffer c=new StringBuffer("c");
    StringBuffer d=new StringBuffer("d");
    t.changeNum(a);
    System.out.println("main中a="+a);
    t.changeString(b);
    System.out.println("main中b="+b);
    t.changeStringBuffer1(c);
    System.out.println("main中c="+c);
    t.changeStringBuffer2(d);
    System.out.println("main中d="+d);
}
}
运行程序后,结果为如下图:

为了弄清这种结果:

一:理解 基本数据类型和引用数据类型的不同之处
int num = 10;
String str = "hello";

如图所示,num是基本类型,值就直接保存在变量中(保存在栈中)。而str是引用类型,栈中的变量保存的只是实际对象的地址。一般称这种变量为"引用",引用指向实际对象,实际对象中(保存在堆中)保存着内容。

二:明白赋值运算符(=)的作用
int num = 20;
String str = "java";
对于基本类型 num,赋值运算符会直接改变变量的值,原来的值被覆盖掉
对于引用类型 str,赋值运算符会改变引用中所保存的地址,原来的地址被覆盖掉。但是原来的对象不会被改变(重要)
如上图所示,"hello" 字符串对象没有被改变。(没有被任何引用所指向的对象是垃圾,会被垃圾回收器回收)

三:调用方法时发生了什么?
1.it.changeNum(a);图解如下:
需要注意的是:即便形式参数和实际参数用了相同的变量名,这两个变量是完全不同的,不要被名称所迷惑!我们完全可以将形式参数名称用别的变量名来代替。
 
2. t.changeString(b);图解如下:
需要注意的是:即便形式参数和实际参数用了相同的变量名,这两个变量是完全不同的,不要被名称所迷惑!我们完全可以将形式参数名称用别的变量名来代替。
 
3.t.changeStringBuffer1(c);图解如下
需要注意的是:即便形式参数和实际参数用了相同的变量名,这两个变量是完全不同的,不要被名称所迷惑!我们完全可以将形式参数名称用别的变量名来代替。
 
4.t.changeStringBuffer2(d);图解如下
需要注意的是:即便形式参数和实际参数用了相同的变量名,这两个变量是完全不同的,不要被名称所迷惑!我们完全可以将形式参数名称用别的变量名来代替。
 
 
5.新的示例如下:
  1. public class TempTest {  
  2. private void test1(A a){  
  3. a.age = 20;  
  4. System.out.println("test1方法中的age="+a.age);  
  5. }  
  6. public static void main(String[] args) {  
  7. TempTest t = new TempTest();  
  8. A a = new A();  
  9. a.age = 10;  
  10. t.test1(a);  
  11. System.out.println(”main方法中的age=”+a.age);  
  12. }  
  13. }  
  14. class A{  
  15. public int age = 0;  
  16. }  

运行结果如下:

  1. test1方法中的age=20  
  2. main方法中的age=20  

用上面的例子来进行分析:

(1):运行开始,运行第8行,创建了一个A的实例,内存分配示意如下:


(2):运行第9行,是修改A实例里面的age的值,运行后内存分配示意如下:


(3):运行第10行,是把main方法中的变量a指向对象的内存空间地址,传递给test1方法中的a变量。请注意:这两个a变量是完全不同的,不要被名称相同所蒙蔽

内存分配示意如下:

形成的新的内存示意图如下:


也就是说:是两个变量都指向同一个空间。

(4):运行第3行,为test1方法中的变量a指向的A实例的age进行赋值,完成后形成的新的内存示意图如下:


此时A实例的age值的变化是由test1方法引起的

(5):运行第4行,根据此时的内存示意图,输出test1方法中的age=20

(6):运行第11行,根据此时的内存示意图,输出main方法中的age=20


6:对上述例子的改变

能不能让test1方法里面的修改不影响到main方法里面呢?

方法是在test1方法里面新new一个实例就可以了(相当于重新赋值,指向新的对象,而非对原对象的内容进行更改)。改变成下面的例子,其中第3行为新加的:

  1.  public class TempTest {  
  2.  private void test1(A a){  
  3.  a = new A();//新加的一行  
  4.  a.age = 20;  
  5.  System.out.println("test1方法中的age="+a.age);  
  6.  }  
  7.  public static void main(String[] args) {  
  8.  TempTest t = new TempTest();  
  9.  A a = new A();  
  10.  a.age = 10;  
  11.  t.test1(a);  
  12.  System.out.println(”main方法中的age=”+a.age);  
  13.  }  
  14. }  
  15. class A{  
  16.  public int age = 0;  
  17. }  

运行结果为:

  1. test1方法中的age=20  
  2. main方法中的age=10  

为什么这次的运行结果和前面的例子不一样呢,还是使用内存示意图来理解一下

(1):运行开始,运行第9行,创建了一个A的实例,内存分配示意如下:

(2):运行第10行,是修改A实例里面的age的值,运行后内存分配示意如下:

(3):运行第11行,是把main方法中的变量a所指向的对象内存空间地址,传递给test1方法中的a变量。请注意:这两个a变量是完全不同的,不要被名称相同所蒙蔽。

内存分配示意如下:

传递完成后形成的新的内存示意图如下:


也就是说:是两个变量都指向同一个空间。

(4):运行第3行,为test1方法中的变量a重新生成了新的A实例的,完成后形成的新的内存示意图如下:

(5):运行第4行,为test1方法中的变量a指向的新的A实例的age进行赋值,完成后形成的新的内存示意图如下:

注意:这个时候test1方法中的变量a的age被改变,而main方法中的是没有改变的。

(6):运行第5行,根据此时的内存示意图,输出test1方法中的age=20

(7):运行第12行,根据此时的内存示意图,输出main方法中的age=10

 

posted @ 2016-05-24 00:07  skip_2_my_lou  阅读(537)  评论(0编辑  收藏  举报