转载请注明出处:http://blog.youkuaiyun.com/binbinqq86/article/details/79253619
本篇属于JAVA语法篇,属于JAVA中的基本知识,也是平时比较容易混淆的地方,同时也是面试的时候会经常问到的。首先来看一个案例:
@Override
public void onCreate(Bundle savedInstanceState){
int ai=5;
int bi=10;
test(ai,bi);
Log.e(TAG, "onCreate: " + ai + "#" + bi);
}
private void test(int a, int b) {
int c = a;
a = b;
b = c;
Log.e(TAG, "test: " + a + "#" + b);
}
当外面调用这个函数的时候,相信很多人都会感觉ai,bi两个变量的值被该函数改变了,但是结果却是这样的:
怎么样,傻眼了吧~~~下面再来看另外一种情况:
@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);
}
来看输出结果:
怎么样,晕了吧???为什么上面的改变入参就没有效果,而此次就可以改变入参的值呢?客官莫急,下面我们再列出几种情况:
private static final String TAG = "MainActivity";
private int ai;
private int bi;
private String sa,sb;
private StringBuffer sba,sbb;
private Person person;
private Float aFloat=10f;
private int[] ints=new int[]{0,1};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ai = 5;
bi = 10;
sa = "sa";
sb = "sb";
sba = new StringBuffer("xxx");
sbb = new StringBuffer("ooo");
person = new Person(0, "tb");
test(ai, bi);
Log.e(TAG, "onCreate: " + ai + "###" + bi);
test1(sa, sb);
Log.e(TAG, "onCreate: " + sa + "+++" + sb);
test2(sba, sbb);
Log.e(TAG, "onCreate: " + sba + "===" + sbb);
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 test1(String a, String b) {
String c = a;
a = b;
b = c;
Log.e(TAG, "test1: " + a + "#" + b);
}
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和数组,还有对象来说,就不一样了,他们传递进去的都是对象的引用,而不是值的拷贝(包装类型传入的虽然也是引用,但是传递进去的仍然是值的拷贝)。跟原引用指定的是同一块内存区域,所以在函数里面改变该内存区域的内容对外面会有同样的影响和效果,但也仅仅是改变引用所指向内存中的内容,而外面的对象所指向的内存引用不受函数内的变化,是没有改变的。我们可以打印如下日志来验证:
var demo=Demo()
Log.e(TAG,demo.toString())
Singleton.getInstance().test2(demo)
Log.e(TAG,demo.toString())
public void test2(Demo demo){
Log.e(TAG, "test2: "+demo );
demo=new Demo();
Log.e(TAG, "test2: "+demo );
}
E/Main2Activity: com.tb.my.Demo@b5c806c
E/Singleton: test2: com.tb.my.Demo@b5c806c
test2: com.tb.my.Demo@50bc635
E/Main2Activity: com.tb.my.Demo@b5c806c
可以看到外边打印出来的实例依然没变化,尽管在方法内重新赋值了。
这样也就解释了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所有的改变都是创建新对象或者新常量)
好了,今天的讲解就到此结束,还有疑问的小伙伴可以在下方留言,谢谢大家的支持!
参考文章: