一、传值和传引用
C++里面有传值和传引用的说法,而java里面却不一样,公司让我出一道考查相关的东西,于是出了下面这道题:
class Number {
int i;
}
public class Assignment {
public static void main (String [] args) {
Number n1 = new Number();
Number n2 = new Number();
n1.i = 9;
n2.i = 47;
System.out.println("1: n1.i: " + n1.i + ", n2.i:" + n2.i);//1: n1.i: 9, n2.i:47
n1 = n2;
System.out.println("2: n1.i: " + n1.i + ", n2.i:" + n2.i);//2: n1.i: 47, n2.i:47
n1.i = 27;
System.out.println("3: n1.i: " + n1.i + ", n2.i:" + n2.i);//3: n1.i: 27, n2.i:27
test(n1, n2);
System.out.println("4: n1.i: " + n1.i + ", n2.i:" + n2.i);//4: n1.i: 6, n2.i:6
StringBuffer sb1 = new StringBuffer ("A");
StringBuffer sb2 = new StringBuffer ("B");
test(sb1,sb2);
System.out.println(sb1 + "." + sb2);//AB.B
sb2 = sb1;
System.out.println(sb1 + "." + sb2);//AB.AB
String s1 = new String("A");
String s2 = new String("B");
test(s1, s2);
System.out.println("String: "+ s1 + "." + s2);//String: A.B
}
static void test (Number n1, Number n2)
{
n1.i = 6;
n2 = n1;
}
static void test (String s1, String s2)
{
s1 = s2; //赋值时,s1就重新指向了s2指向的内容,但是实参的内容没有改变
s1 = "C";
}
static void test (StringBuffer sb1, StringBuffer sb2)
{
sb1.append ("B");//没有产生新对象
sb2 = sb1;
}
}
在Java中,事实上底层工作原理不存在传引用的概念,这也象《Practical Java》中所说的那样,Java中只有传值。这句话理解起来需要费一定的周折。
传值和传引用的问题一直是Java里争论的话题。与C++不同的,Java里面没有指针的概念,Java的设计者巧妙的对指针的操作进行了管理。
下面举个简单的例子,说明什么是传值,什么是传引用。
//例1
void method1(){
int x=0;
this.change(x);
System.out.println(x);
}
void int change(int i){
i=7;
}
很显然的,在mothod1中执行了change(x)后,x的值并不会因为change方法中将输入参数赋值为1而变成1,也就是说在执行
change(x)后,x的值z依然是0。这是因为x传递给change(int i)的是值。这就是最简单的传值。
同样的,进行一点简单的变化。
//例2
void method1(){
StringBuffer x=new StringBuffer("Hello");
this.change(x);
System.out.println(x);
}
void int change(StringBuffer i){
i.append(" world!");
}
看起来没什么变化,但是这次mothed1中执行了change (x)后,x的值不再是"Hello"了,而是变成了"Hello world!"。这
是因为x传递给change(i)的是x的引用。这是最经典的传引用。
似乎有些奇怪了,两段程序没有特别的不同,可是为什么一个传的是值而另一个传的是引用呢?
Java 提出的思想,在Java里面任何东西都是类。但是Java里面同时还有简单数据类型:int,byte,char,boolean,与这些数据类型相对应的类是Integer,Byte,Character,Boolean,这样做依然不会破坏Java关于任何东西都是类的提法。这里提到数据类型和类似乎和我们要说的传值和传引用的问题无关,但这是我们分辨传值和传引用的基础。
我们分析一下上面的几个例子:
先看例1,即使你不明白为什么,但是你应该知道这样做肯定不会改变x的值。为了方便说明,我们给例子都加上行号。
//例1
1 void method1(){
2 int x=0;
3 this.change(x);
4 }
5
6 void int change(int i){
7 i=7;
8}
让我们从内存的存储方式看一下x和I之间到底是什么关系。
在执行到第2行的时候,变量x指向一个存放着int 0的内存地址。
变量x---->[存放值0]
执行第3行调用change(x)方法的时候,内存中是这样的情形:x把自己值在内存中复制一份,然后变量i指向这个被复制出来的0。
变量x---->[存放值0]
↓进行了一次值复制
变量i---->[存放值0]
这时候再执行到第7行的时候,变量i的被赋值为7,而这一步的操作已经跟x没有任何关系了。
变量x---->[存放值0]
变量i---->[存放值7]
说到这里应该已经理解为什么change(x)不能改变x的值了吧?因为这个例子是传值的。
那么,试着分析一下为什么例三中的switchValue()方法不能完成变量值交换的工作?
再看例2。
//例2
1void method1(){
2 StringBuffer x=new StringBuffer("Hello");
3 this.change(x);
4}
5
6 void change(StringBuffer i){
7 i.append(" world!");
8}
例2似乎和例1从代码上看不出什么差别,但是执行结果却是change(x)能改变x的值。依然才从内存的存储角度来看看例2的蹊跷在哪里。
在执行到第2行时候,同例1一样,x指向一个存放"Hello"的内存空间。
变量x---->[存放值"Hello"]
接下来执行第三行change(x),注意,这里就与例1有了本质的不同:调用change(x)时,变量i也指向了x指向的内存空间,而不是指向x的一个拷贝。
变量x "
-->[存放值"Hello"]
变量i /
于是,第7行对i调用append方法,改变i指向的内存空间的值,x的值也就随之改变了。
变量x "
-->[追加为"Hello World!"]
变量i /
为什么x值能改变呢?因为这个例子是传引用的。
对于参数传递,如果是简单数据类型,那么它传递的是值拷贝,对于类的实例它传递的是类的引用。
需要注意的是,这条规则只适用于参数传递。为什么这
么说呢?我们看看这样一个例子:
//例5
String str="abcdefghijk";
str.replaceAll("b","B");
这两句执行后,str的内容依然是"abcdefghijk",但是我们明明是对str操作的,为什么是这样的呢?因为str的值究竟会不会被改变完全取
决于replaceAll这个方法是怎么实现的。类似的,有这样一个例子:
//例6
1 void method1() {
2 StringBuffer x = new StringBuffer("Hello");
3 change1(x);
4 System.out.println(x);
5 }
6
7 void method2() {
8 StringBuffer x = new StringBuffer("Hello");
9 change2(x);
10 System.out.println(x);
11 }
12
13 void change1(StringBuffer sb) {
14 sb.append(" world!");
15 }
16
17 void change2(StringBuffer sb) {
18 sb = new StringBuffer("hi");
19 sb.append(" world!");
20 }
调用method1(),屏幕打印结果为:"Hello world!"
调用method2(),我们认为结果应该是"hi world",因为sb传进来的是引用。可是实际执行的结果是"Hello"!
难道change2()又变成传值了?!其实change1()和change2()的确都是通过参数传入引用,但是在方法内部因为处理方法的不同而使结果大相径庭。
所以,还有一条不成规则的规则:对于函数调用,最终效果是什么完全看函数内部的实现。比较标准的做法是如果会改变引用的内容,则使用void作为方法返回值,而不会改变引用内容的则在返回值中返回新的值。
二、相等判断
在使用vector中的contains方法时,重载了vector中对象的类DataDictionry的equals
方法,方法如下:
public boolean equals(Object obj) {
if(this.code == ((DataDictionry)obj).code)
return true;
else
return false;
}
相等的条件根据业务,code字符串内容属性相等即可。即如果有一个DataDictionry对象和另一个DataDictionry的code相等即两个对象为同一个对象。但是发现运行结果有问题,即使两个的code的内容相等
程序也判断是false。但是改成如下:
public boolean equals(Object obj) {
if(this.code.equals(((DataDictionry)obj).code))
return true;
else
return false;
}
则结果是true。
因为对于字符串来说用这两种方式判断的结果是一样的?什么情况下两个字符串用equals判断是相等的,但是用“==”判断则不相等呢?
其实并不是字符串的比较“==”和equals方法的结果是一样的,而是根据字符串的初始化相关。
1 public class TestString {
2
3 public static void main(String[] args) {
4 String a = "0010";
5 String b = "0010";
6
7 if(a == b)
8 System.out.println("equals");
9 else
10 System.out.println("not equals");
11 /* print equals */
12
13 if(a.equals(b))
14 System.out.println("equals");
15 else
16 System.out.println("not equals");
17 /* print equals */
18
19 String s1 = new String("abc");
20 String s2 = new String("abc");
21
22 if(s1 == s2)
23 System.out.println("equals");
24 else
25 System.out.println("not equals");
26
27 /* print not equals */
28
29 if(s1.equals(s2))
30 System.out.println("equals");
31 else
32 System.out.println("not equals");
33
34 /* print not equals */
35
36 }
37
38 }
所以如果不能肯定是new产生的还是直接赋值得到的字符串进行比较,都使用equals方法是没有问题的。
本质上说,“ ==”还是比较的引用地址,equals比较具体的内容(String等那些重载了equals方法的类)。