一、传值和传引用

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方法的类)。