JAVA中的传值和传址

我们先看一个例子:


很多人认为结果是cd,但是答案是:ab


例子二:

@Override
public void onCreate(Bundle savedInstanceState){

    StringBuffer sba = new StringBuffer("xxx");
    StringBuffer sbb = new StringBuffer("ooo");
    test(ssa,ssb);
    Log.e(TAG, "onCreate: " + ssa+ "#" + ssb);
}
private void test2(StringBuffer a, StringBuffer b) {
        StringBuffer c = a.append("hhhh");
        a = b;
        b = c;
        Log.e(TAG, "test2: " + a + "$" + b);
    }

答案是:test2:ooo$xxxhhhh

       onCreate:xxxhhhh#ooo


怎么样,晕了吧???为什么上面的改变入参就没有效果,而此次就可以改变入参的值呢?


好吧我们再来看几个例子:

        private Person person;
		private Float aFloat=10f;
        private int[] ints=new int[]{0,1};
		
		
		test3(person);
        Log.e(TAG, "onCreate: " + person.toString());
        test4(person);
        Log.e(TAG, "onCreate: " + person.toString());
	    test5(aFloat);
        Log.e(TAG, "onCreate: "+aFloat );
        test6(ints);
        Log.e(TAG, "onCreate: "+ints[0]+"#"+ints[1] );
	   
  
  private void test3(Person p) {
        p.age = 5;
        p.name = "hello";
        Log.e(TAG, "test3: " + p.toString());
    }
	
	
	
	private void test4(Person p) {
        Log.e(TAG, "test4: " + p.toString());
        Person pt = new Person(2, "2");
        p = pt;
        Log.e(TAG, "test4: " + p.toString());
    }
	
	
	private void test5(Float f){
        f+=10;
        Log.e(TAG, "test5: "+f );
    }

   private void test6(int[] int1){
        int1[0]=10;
        int1[1]=20;
        Log.e(TAG, "test6: "+int1[0]+"$"+int1[1] );
    }

结果:



看蓝色圈起来的部分(其他两个是上面的情况)。我们发现String也没有改变入参的值,包装类型的Float也是同样效果,但是数组和对象都成功的改变了入参的值,这是为什么呢?原来在JAVA中,方法的入参对于基本数据类型和字符串常量来说,传递的其实只是这个值本身的一个拷贝而已,对于外面的变量来说,并没有改变什么,入参的作用域也仅限函数内部,属于局部变量(对于包装类型和String,依然如此)。

但是对于StringBuffer和数组,还有对象来说,就不一样了,他们传递进去的都是对象的引用的拷贝,而不是值的拷贝(包装类型传入的虽然也是引用,但是传递进去的仍然是值的拷贝)。拷贝跟原对象指定的是同一块内存区域,所以在函数里面改变该内存区域的内容对外面会有同样的影响和效果。

这样也就解释了test4方法,入参跟它的拷贝虽然指向同一块内存区域,但是完全是两个不同的对象,所以当我们在方法中对形参(即拷贝)进行重新赋值时,改变的只是形参所指向的地址,而实参(即入参)所指向的地址没有被改变,所以其内容不变。讲到这里,就不得不提JAVA的堆与栈:

  • 栈:基本数据类型、数据的引用变量,这两种存放在栈内存
  • 堆:new创建的对象(包括数组、集合等),是存放在堆内存中,同时分配一个内存地址值,并将其赋值给引用它的变量。

栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:

int a = 3;

int b = 3;

编译器会先处理int a = 3;,首先它会在栈中创建一个变量为a 的引用,然后查找栈中是否有3 这个值,如果没找到,就将3存放进来,然后将a 指向3 。接着处理int b = 3; ,在创建完 b 的引用变量后,因为在栈中已经有3 这个值,便将b 直接指向3 。 这样,就出现了 a 与 b 同时均指向 3 的情况。这时,如果再令 a = 4;,那么编译器会重新搜索栈中是否有 4 这个值,如果没有,就将4 存放进来,并令a 指向 4;如果已经有了,则直接将 a 指向这个地址。 因此a 值的改变不会影响到 b 值。 要注意这种数据的共享与两个对象的引用同时指向一个对象的这种共享是不同的,因为这种情况 a 的修改并不会影响到 b,它是由编译器完成的,它有利于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。

但是你肯定会有疑问:为什么String入参的效果跟基本类型一样呢???

String是一个特殊的包装类数据,可以用:

String str = new String(“abc”);

String str = “abc”;

这里首先要说明一下,java中有一块专门为String开辟的缓冲池内存空间,不同于堆和栈。两种形式来创建String,第一种是用new()来创建对象的,它会首先去缓冲池中查找有没有该对象,如果没有则创建一个新的缓冲池对象入池,如果有直接拿来用,跟上面的基本数据类型是一样的原理。然后会new一个对象存放在堆中,每调用一次new就会创建一个新的对象;而第二种是先在栈中创建一个对String类的对象引用变量str ,然后查找缓冲池中有没有存放”abc”,如果没有,则将”abc”存放进缓冲池中,并令str 指向”abc”,如果已经有”abc”,则直接令str 指向”abc”。

另一方面,要注意:

我们在使用诸如String str = “abc”;的格式创建对象时,总是想当然地认为创建了String类的对象str,小心陷阱,对象可能并没有被创建!而可能只是指向一个先前已经创建好的对象。只有通过new()方法才能保证每次都创建一个新的对象。由于String类的不可变(immutable)性质,当String变量需要经常变换其值时,应该考虑使用StringBuffer类,以提高程序效率。

也正是由于String类的不可变性质,所以它跟普通的类不太一样,也就是你看到的StringBuffer可以改变入参,而String不行。(String所有的改变都是创建新对象或者新常量)









猜你喜欢

转载自blog.csdn.net/xxdw1992/article/details/80288467
今日推荐