1:按值传递是什么

指的是在方法调用时,传递的参数是按值的拷贝传递。示例如下:

    1. public class TempTest {  
    2. private void test1(int a){  
    3. //做点事情  
    4. }  
    5. public static void main(String[] args) {  
    6. TempTest t = new TempTest();  
    7. int a = 3;  
    8. t.test1(a);//这里传递的参数a就是按值传递  
    9. }  
    10. }



    按值传递重要特点:传递的是值的拷贝,也就是说传递后就互不相关了。

    示例如下:



    1. public class TempTest {  
    2. private void test1(int a){  
    3. a = 5;  
    4. System.out.println("test1方法中的a="+a);  
    5. }  
    6. public static void main(String[] args) {  
    7. TempTest t = new TempTest();  
    8. int a = 3;  
    9. t.test1(a);//传递后,test1方法对变量值的改变不影响这里的a  
    10. System.out.println(”main方法中的a=”+a);  
    11. }  
    12. }


    运行结果是:

    1. test1方法中的a=5  
    2. main方法中的a=3


    2:按引用传递是什么

    指的是在方法调用时,传递的参数是按引用进行传递,其实传递的引用的地址,也就是变量所对应的内存空间的地址。

    示例如下:

      1. public class TempTest {  
      2. private void test1(A a){  
      3. }  
      4. public static void main(String[] args) {  
      5. TempTest t = new TempTest();  
      6. A a = new A();  
      7. t.test1(a); //这里传递的参数a就是按引用传递  
      8. }  
      9. }  
      10. class A{  
      11. public int age = 0;  
      12. }



      3:按引用传递的重要特点

      传递的是值的引用,也就是说传递前和传递后都指向同一个引用(也就是同一个内存空间)。

      示例如下:

      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



      4:理解按引用传递的过程——内存分配示意图

      要想正确理解按引用传递的过程,就必须学会理解内存分配的过程,内存分配示意图可以辅助我们去理解这个过程。

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

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







      java 方法根据传入类返回对象 java按值传送_java 方法根据传入类返回对象


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


      java 方法根据传入类返回对象 java按值传送_java 方法根据传入类返回对象_02


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

      内存分配示意如下:




      java 方法根据传入类返回对象 java按值传送_System_03


      由于是按引用传递,也就是传递的是内存空间的地址,所以传递完成后形成的新的内存示意图如下:


      java 方法根据传入类返回对象 java按值传送_java 方法根据传入类返回对象_04

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

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


      java 方法根据传入类返回对象 java按值传送_引用传递_05




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

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

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

      5:对上述例子的改变

      理解了上面的例子,可能有人会问,那么能不能让按照引用传递的值,相互不影响呢?就是test1方法里面的修改不影响到main方法里面呢?

      方法是在test1方法里面新new一个实例就可以了。改变成下面的例子,其中第3行为新加的:

      1.  public class TempTest {  
      2. private void test1(A a){  
      3. new A();//新加的一行  
      4. 20;  
      5. "test1方法中的age="+a.age);  
      6.  }  
      7. public static void main(String[] args) {  
      8. new TempTest();  
      9. new A();  
      10. 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


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

      6:再次理解按引用传递

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


      java 方法根据传入类返回对象 java按值传送_java_06


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


      java 方法根据传入类返回对象 java按值传送_java 方法根据传入类返回对象_07


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

      内存分配示意如下:


      java 方法根据传入类返回对象 java按值传送_System_08


      由于是按引用传递,也就是传递的是内存空间的地址,所以传递完成后形成的新的内存示意图如下:



      java 方法根据传入类返回对象 java按值传送_System_09


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

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


      java 方法根据传入类返回对象 java按值传送_java 方法根据传入类返回对象_10


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


      java 方法根据传入类返回对象 java按值传送_引用传递_11


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

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

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

      7:说明

      (1):“在Java里面参数传递都是按值传递”这句话的意思是:按值传递是传递的值的拷贝,按引用传递其实传递的是引用的地址值,所以统称按值传递。

      (2):在Java里面只有基本类型和按照下面这种定义方式的String是按值传递,其它的都是按引用传递。就是直接使用双引号定义字符串方式:String str = “Java私塾”;

      --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

       许多编程语言都有2种方法将参数传递给方法------按值传递和按引用传递。


      与其他语言不同,Java不允许程序员选择按值传递还是按引用传递各个参数,基本类型(byte--short--int--long--float--double--boolean--char)的变量总是按值传递。就对象而言,不是将对象本身传递给方法,而是将对象的的引用或者说对象的首地址传递给方法,引用本身是按值传递的-----------也就是说,讲引用的副本传递给方法(副本就是说明对象此时有两个引用了),通过对象的引用,方法可以直接操作该对象(当操作该对象时才能改变该对象,而操作引用时源对象是没有改变的)。


      现在说说数组:如果将单个基本类型数组的元素传递给方法,并在方法中对其进行修改,则在被调用方法结束执行时,该元素中存储的并不是修改后的值,因为这种元素是按值传递,如果传递的是数组的引用,则对数组元素的后续修改可以在原始数组中反映出来(因为数组本身就是个对象,int[] a = new int[2];,这里面的int是数组元素的类型,而数组元素的修改是操作对象)。


      对于单个非基本类型数组的元素在方法中修改,则在被调用方法结束执行时,该元素中存储的是修改后的值,因为这种元素是按引用传递的,对象的改动将在源数组的数组元素中反映出来。


      下面看个小程序:


      1. public class Test{
      2.     
      3.     String str = new String("good");
      4.     char[] ch = {'a','b','c'};
      5.     int i = 10;
      6.     public void change(String str,char[] ch,int i){
      7.     
      8.         str = "test ok";
      9.         ch[0] = 'g';
      10.         i++;    
      11.     }
      12.     
      13.     public static void main(String[] args){
      14.     
      15.         Test tt = new Test();
      16.         tt.change(tt.str,tt.ch,tt.i);
      17.         System.out.println(tt.i);
      18.         System.out.print(tt.str+" and ");
      19.         System.out.println(tt.ch);     
      20.     }
      21. }


      str是String类型的引用,i是基本类型变量,ch是数组名,也是数组对象的引用

      在chang()方法里,str="test ok",是一个新的对象把首地址放在引用变量str上;

      而ch[0]='g';因为传的是数组的引用,而此时ch[0]='g';是对数组元素的操作,能修改源数组的内容;

      i是整型值,只是把值copy了一份给方法,在方法的变化是不改变的源i的。

      所以结果是:

      10

      good and gbc

       

      现在咱们把代码变化一下:



      1. public class Test{
      2.     
      3.     String str = new String("good");
      4.     char[] ch = {'a','b','c'};
      5.     int i = 10;
      6.     public void change(String str,char ch,int i){
      7.     
      8.         str = "test ok";
      9.         ch = 'g';
      10.         this.i = i+1;    
      11.     }
      12.     
      13.     public static void main(String[] args){
      14.     
      15.         Test tt = new Test();
      16.         tt.change(tt.str,tt.ch[0],tt.i);
      17.         System.out.println(tt.i);
      18.         System.out.print(tt.str+" and ");
      19.         System.out.println(tt.ch);     
      20.     }
      21. }


       

      仔细观察下实参以及入参有何变化?

      change()方法里的入参char[] ch变成--------------char ch;

      这次传递的是个char值的单个数组元素,按照上面的解析,此时ch='9';是不影响源数组元素的。

      this.i = i+1;这里面等号左边的i是属性i,等号右边的i是局部变量(入参里的i);

      此时i+1后赋值给属性的i,自然会改变属性i的值,同时17行,tt.i又是调用属性的i,这次的结果是:

       

      11

      good and abc

       

      现在是不是有点明白了?

      那好再看下面一个小程序


      1. public class Test{
      2.     
      3.     public void change(StringBuffer x,StringBuffer y){
      4.         
      5.         x.append(y);
      6.         y=x;    
      7.     }
      8.     public static void main(String[] args){
      9.     
      10.         StringBuffer buffA = new StringBuffer("a");
      11.         StringBuffer buffB = new StringBuffer("b");
      12.         new Test().change(buffA,buffB);
      13.         System.out.println(buffA+","+buffB);   
      14.     }
      15. }


      这次传递的是两个对象的引用的值,

      new StringBuffer("a");的内容。

      new StringBuffer("b");的引用变量,所以输出结果是:

      ab,b


      下面是个稍难的小程序,先自己用笔画画过程,写出自己的结果,而后再上机操作下,如果自己的结果和在电脑上的结果一样,那么再碰到这类题就不难了,如果不一样,回头仔细体会下我前面的讲解,找找原因。



      1. public class Test{
      2.     
      3.     private String nn = new String("1");
      4.     private String[] mm = {"2","5"};
      5.     
      6.     void test(String nn,String[] mm){
      7.         
      8.         nn = new String("3");
      9.         this.nn = "9";
      10.         
      11.         mm[0] = "4";
      12.         System.out.println("in test(),mm[0]: "+mm[0]);
      13.         mm = new String[]{"8","7"};
      14.         System.out.println("in test(),nn: "+nn);
      15.         System.out.println("this.nn: "+this.nn);
      16.         System.out.println("mm[0]: "+mm[0]);
      17.     }
      18.     
      19.     public static void main(String[] args){
      20.         
      21.         Test s = new Test();
      22.         s.test(s.nn,s.mm);
      23.         System.out.println(s.nn+"  "+s.mm[0]);
      24.     }
      25. }


      public class Callby{
      	int x,y;
      	Callby(int i,int j){
      		x=i;
      		y=j;	}
      	void f(Callby obj){//传入对象
      		obj.x*=2;
      		obj.y/=2;	}	
      	public static void main(String[] args){
      		Callby obj=new Callby(5,30);
      		System.out.println("obj.x and obj.y before call:"+obj.x+" "+obj.y);
      		obj.f(obj);
      		System.out.println("obj.x and obj.y after call:"+obj.x+" "+obj.y);
      	}
      }



      ------------------------------------------------------------------------------------

      <p><pre name="code" class="java">class Callbyvalue{
      public static void main(String[] args)
      {
      	Passtest obj=new Passtest();
      	int x=5,y=30;
      	System.out.println("x and y before call:"+x+" "+y);
      	obj.f(x,y);
      	System.out.println("x and y after call:"+x+" "+y);
      	}
      }
      class Passtest{
      	void f(int i,int j)
      	{
      		i*=2;
      		j/=2;
      	}
      }

      结果为:


      x and y before call:5 30


      x and y after call:5 30