一 爲什麼 Java 中只有值傳遞?
首先回顧一下在程序設計語言中有關將參數傳遞給方法(或函數)的一些專業術語。按值調用(call by value)表示方法接收的是調用者提供的值,而按引用調用(call by reference)表示方法接收的是調用者提供的變量地址。一個方法可以修改傳遞引用所對應的變量值,而不能修改傳遞值調用所對應的變量值。 它用來描述各種程序設計語言(不只是Java)中方法參數傳遞方式。
Java程序設計語言總是採用按值調用。也就是說,方法得到的是所有參數值的一個拷貝,也就是說,方法不能修改傳遞給它的任何參數變量的內容。
下面通過 3 個例子來給大家說明
example 1
public static void main(String[] args) {
int num1 = 10;
int num2 = 20;
swap(num1, num2);
System.out.println("num1 = " + num1);
System.out.println("num2 = " + num2);
}
public static void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
System.out.println("a = " + a);
System.out.println("b = " + b);
}
結果:
a = 20
b = 10
num1 = 10
num2 = 20
解析:
在swap方法中,a、b的值進行交換,並不會影響到 num1、num2。因爲,a、b中的值,只是從 num1、num2 的複製過來的。也就是說,a、b相當於num1、num2 的副本,副本的內容無論怎麼修改,都不會影響到原件本身。
通過上面例子,我們已經知道了一個方法不能修改一個基本數據類型的參數,而對象引用作爲參數就不一樣,請看 example2.
example 2
public static void main(String[] args) {
int[] arr = { 1, 2, 3, 4, 5 };
System.out.println(arr[0]);
change(arr);
System.out.println(arr[0]);
}
public static void change(int[] array) {
// 將數組的第一個元素變爲0
array[0] = 0;
}
結果:
1
0
解析:
array 被初始化 arr 的拷貝也就是一個對象的引用,也就是說 array 和 arr 指向的時同一個數組對象。 因此,外部對引用對象的改變會反映到所對應的對象上。
通過 example2 我們已經看到,實現一個改變對象參數狀態的方法並不是一件難事。理由很簡單,方法得到的是對象引用的拷貝,對象引用及其他的拷貝同時引用同一個對象。
很多程序設計語言(特別是,C++和Pascal)提供了兩種參數傳遞的方式:值調用和引用調用。有些程序員(甚至本書的作者)認爲Java程序設計語言對對象採用的是引用調用,實際上,這種理解是不對的。由於這種誤解具有一定的普遍性,所以下面給出一個反例來詳細地闡述一下這個問題。
example 3
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
Student s1 = new Student("小張");
Student s2 = new Student("小李");
Test.swap(s1, s2);
System.out.println("s1:" + s1.getName());
System.out.println("s2:" + s2.getName());
}
public static void swap(Student x, Student y) {
Student temp = x;
x = y;
y = temp;
System.out.println("x:" + x.getName());
System.out.println("y:" + y.getName());
}
}
結果:
x:小李
y:小張
s1:小張
s2:小李
解析:
交換之前:
交換後:
通過上面兩張圖可以很清晰的看出: 方法並沒有改變存儲在變量 s1 和 s2 中的對象引用。swap方法的參數x和y被初始化爲兩個對象引用的拷貝,這個方法交換的是這兩個拷貝
總結
Java程序設計語言對對象採用的不是引用調用,實際上,對象引用是按 值傳遞的。
下面再總結一下Java中方法參數的使用情況:
- 一個方法不能修改一個基本數據類型的參數(即數值型或布爾型)。
- 一個方法可以改變一個對象參數的狀態。
- 一個方法不能讓對象參數引用一個新的對象。
參考:
《Java核心技術卷Ⅰ》基礎知識第十版第四章4.5小節
二 ==與equals(重要)
== : 它的作用是判斷兩個對象的地址是不是相等。即,判斷兩個對象是不是同一個對象。(基本數據類型比較的是值,引用數據類型比較的是內存地址)
equals() : 它的作用也是判斷兩個對象是否相等。但它一般有兩種使用情況:
- 情況1:類沒有覆蓋equals()方法。則通過equals()比較該類的兩個對象時,等價於通過“==”比較這兩個對象。
- 情況2:類覆蓋了equals()方法。一般,我們都覆蓋equals()方法來兩個對象的內容相等;若它們的內容相等,則返回true(即,認爲這兩個對象相等)。
舉個例子:
public class test1 {
public static void main(String[] args) {
String a = new String("ab"); // a 爲一個引用
String b = new String("ab"); // b爲另一個引用,對象的內容一樣
String aa = "ab"; // 放在常量池中
String bb = "ab"; // 從常量池中查找
if (aa == bb) // true
System.out.println("aa==bb");
if (a == b) // false,非同一對象
System.out.println("a==b");
if (a.equals(b)) // true
System.out.println("aEQb");
if (42 == 42.0) { // true
System.out.println("true");
}
}
}
說明:
- String中的equals方法是被重寫過的,因爲object的equals方法是比較的對象的內存地址,而String的equals方法比較的是對象的值。
- 當創建String類型的對象時,虛擬機會在常量池中查找有沒有已經存在的值和要創建的值相同的對象,如果有就把它賦給當前引用。如果沒有就在常量池中重新創建一個String對象。
三 hashCode與equals(重要)
面試官可能會問你:“你重寫過 hashcode 和 equals 麼,爲什麼重寫equals時必須重寫hashCode方法?”
hashCode()介紹
hashCode() 的作用是獲取哈希碼,也稱爲散列碼;它實際上是返回一個int整數。這個哈希碼的作用是確定該對象在哈希表中的索引位置。hashCode() 定義在JDK的Object.java中,這就意味着Java中的任何類都包含有hashCode() 函數。另外需要注意的是: Object 的 hashcode 方法是本地方法,也就是用 c 語言或 c++ 實現的,該方法通常用來將對象的 內存地址 轉換爲整數之後返回。
/**
* Returns a hash code value for the object. This method is
* supported for the benefit of hash tables such as those provided by
* {@link java.util.HashMap}.
* <p>
* As much as is reasonably practical, the hashCode method defined by
* class {@code Object} does return distinct integers for distinct
* objects. (This is typically implemented by converting the internal
* address of the object into an integer, but this implementation
* technique is not required by the
* Java™ programming language.)
*
* @return a hash code value for this object.
* @see java.lang.Object#equals(java.lang.Object)
* @see java.lang.System#identityHashCode
*/
public native int hashCode();
散列表存儲的是鍵值對(key-value),它的特點是:能根據“鍵”快速的檢索出對應的“值”。這其中就利用到了散列碼!(可以快速找到所需要的對象)
爲什麼要有hashCode
我們以“HashSet如何檢查重複”爲例子來說明爲什麼要有hashCode:
當你把對象加入HashSet時,HashSet會先計算對象的hashcode值來判斷對象加入的位置,同時也會與其他已經加入的對象的hashcode值作比較,如果沒有相符的hashcode,HashSet會假設對象沒有重複出現。但是如果發現有相同hashcode值的對象,這時會調用equals()方法來檢查hashcode相等的對象是否真的相同。如果兩者相同,HashSet就不會讓其加入操作成功。如果不同的話,就會重新散列到其他位置。(摘自我的Java啓蒙書《Head fist java》第二版)。這樣我們就大大減少了equals的次數,相應就大大提高了執行速度。
hashCode()與equals()的相關規定
- 如果兩個對象相等,則hashcode一定也是相同的
- 兩個對象相等,對兩個對象分別調用equals方法都返回true
- 兩個對象有相同的hashcode值,它們也不一定是相等的
- 因此,equals方法被覆蓋過,則hashCode方法也必須被覆蓋
- hashCode()的默認行爲是對堆上的對象產生獨特值。如果沒有重寫hashCode(),則該class的兩個對象無論如何都不會相等(即使這兩個對象指向相同的數據)
爲什麼兩個對象有相同的hashcode值,它們也不一定是相等的?
在這裏解釋一位小夥伴的問題。以下內容摘自《Head Fisrt Java》。
因爲hashCode() 所使用的雜湊算法也許剛好會讓多個對象傳回相同的雜湊值。越糟糕的雜湊算法越容易碰撞,但這也與數據值域分佈的特性有關(所謂碰撞也就是指的是不同的對象得到相同的 hashCode)。
我們剛剛也提到了 HashSet,如果 HashSet 在對比的時候,同樣的 hashcode 有多個對象,它會使用 equals() 來判斷是否真的相同。也就是說 hashcode 只是用來縮小查找成本。