java中传值与传引用的问题由来已久,今天上网无意中又发现了这个问题的讨论,于是自己也复习了一下,想想还是整理一下思路,记下来供概念还有些模糊的童鞋参考参考。

关于值与引用的问题,最直观的可以用一段代码来表示:

public class Test {
    public static void main(String[] args) {
        String a = new String("hello");
        String b = new String("hello");
        if(a==b) System.out.println("true");
        else System.out.println("false");
    }
}

这段代码返回的是“flase”,显然可以看出来:比较(==)两个String数据的时候,并不是比较的两个数据的“值”,而是比较两个数据的“引用”是否指向同一个对象

PS.进行“比较”,也就是“==”的时候,
如果比较的是两个基本数据类型(char,byte,short,int,long,float,double,boolean),则是判断它们的值是否相等。
如果比较的不是两个基本数据类型,而是两个对象变量,则是判断它们的引用是否指向同一个对象,也就是看引用地址是否一致。

引申一下:java的基本数据类型都是传“值”的,其他的则是传“引用” (“引用”指的是内存里保存这个“值”的地址标识,本质上还是一个“值”)

所谓的“值”指的是内存里的数据值,这里是“hello”;而“引用”指的是内存里这个数据值的地址。这段代码里a与b是分别实例化的,在内存里的地址显然是不同,所以比较的结果就是“false”。

========================

这边偏一下题:创建String对象除了new,常用还有通过引号来创建,比如

public class Test {
    public static void main(String[] args) {
        String a = "hello";
        String b = "hello";
        if(a==b) System.out.println("true");
        else System.out.println("false");
    }
}

这中情况下返回的是“true”!这个就与JAVA虚拟机(JVM)中的字符串池有关系了。字符串池里的String对象是可以被共享使用的,因此它提高了效率。并且由于String类是final的,它的值一经创建就不可改变,因此不会因为String对象共享而带来程序的混乱。

当执行String a = "hello";的时候,JVM首先在字符串池中查找是否已经存在了值为"hello"的这么一个对象,它的判断依据是String类equals(Object obj)方法的返回值。如果字符串池中已经存在,则不再创建新的对象,直接返回已存在对象的引用;如果不存在,则先创建这个值为“hello”的对象,再把这个对象加入到字符串池中,最后将这个对象的引用返回。

因此,在这段程序中,执行String a= "hello";的时候,由于字符串池中不存在“hello”,程序创建了一个新的对象“hello”,保存到池里,并把这个对象的引用地址返回;当执行String b = "hello";的时候,由于字符串池中已经存在了刚刚创建的“hello”对象,所以直接获得了这个hello对象的引用地址,并赋值给了b。

所以这个时候,a跟b进行比较的时候,a跟b的引用地址是一样的(都是执行String a= "hello";的时候返回的那个引用地址),所以最终的比较结果是“true”。

========================

回到正题:再来看一段简单的代码

public class Test {
    public static void opt(String str) {   
        String _str = str;  
        str = "world";  
        System.out.println(_str);  
        System.out.println(str);  
    } 
    public static void main(String[] args) {
        String str = "hello";  
        opt(str);  
        System.out.println(str);  
        }
}

这段代码输出结果是:

hello
world
hello

结合上文可以很容易得到这个答案。简单说明下就是

1.先执行main方法里的String str = "hello";于是在字符串池里创建了一个值为“hello”的对象,并返回其引用地址
2.执行opt方法,其参数是str,也就是上一步返回的刚刚创建的那个值为“hello”的对象的引用地址
3.执行opt方法里的String _str = str; 也就是把传进来的参数(也就是那个引用地址,【注意,这只是一个副本,原先的str并没有任何改变】)赋值给_str;所以打印出来的_str为引用地址指向的那个内存空间里存的值,也就是“hello”
4.str = "world";这一步其实跟第一步一样,都是在字符串池里创建新对象,这次的对象值为“world”,返回的是这个对象的引用地址,并把这个新的引用地址赋值给str(注意,这个str是opt方法的参数,是形参,是opt方法私有的);所以打印出来的str就是“world”
5.回到mian方法,打印的str为main方法中原本定义的“hello”(这个str与上一步的不一样,是实参,是main方法内部私有的)

========================

再偏一下题:说到形参与实参之间的相互影响问题,可以简单粗糙的归纳一下:

对于“==(比较)”,“=(赋值)”来说,形参的改变对实参是没有影响的,因为这时候改变的只是引用或者值的副本(形参本身),实参不发生改变
而对于“.(操作)”来说,形参的改变会直接改变形参(引用地址)所指向值,这就导致实参的值发生了变化

比如下面这段代码:

public class Test { 
    public static void main(String[] args) { 
      StringBuffer a=new StringBuffer ("A"); 
      StringBuffer b=new StringBuffer ("B"); 
      oper(a,b); 
      System.out.print(a+","+b); 
    } 
    static void oper(StringBuffer c,StringBuffer d){ 
      c.append("C"); 
      d=c; 
      d.append("D"); 
    } 
} 

输出结果是什么呢?简单分析一下:

1.分别创建值为"A","B"的对象a,b,分别返回对象a,b的引用
2.执行oper方法,将第一步返回的引用地址作为参数。此时oper方法中的参数c跟d(形参)的值就分别是a跟b的引用地址(当然这边是个副本)
3.执行“.append”操作,注意这边是“.”,这个操作直接在形参c所指向的那个值的后面加上一个“C”字符串。此时,c的值是一个引用地址,这个引用跟a的引用是一样的,所以c所指向的值跟a所指向的值是同一个,所以这一步操作就相当于把对象a的值变成了“AC”
4.继续执行d=c;这一步操作把c的引用赋值给d,此时d实际指向的值跟a指向的值也是同一个
5.再执行 d.append("D"); 之后,对象a的值再次被改变,成为“ACD”
6.所以最终的打印结果是“ACD,B”

有一点要注意的是,如果不是StringBuffer而是String的话,结果又不一样了。比如说:

public class Test { 
    public static void main(String[] args) { 
      String a=new String ("A"); 
      String b=new String ("B"); 
      oper(a,b); 
      System.out.print(a+","+b); 
    } 
    static void oper(String c,String d){ 
      c.concat("C"); 
      d=c; 
      d.concat("D"); 
    } 
} 

这个的输出结果为“A,B”。这是因为java里规定了String值的不可被改变,所以随便怎么折腾,a,b的值在创建的时候已经被固定下来了,没法再改了

后记:
本想随便写几句,没想到巴拉巴拉说了一大通,不过也借此把自己的思路缕了一遍,正好复习java基础知识,哈哈!
有说的不对的地方请务必指正!!!拜谢~~^_^