面試1

一 為什么 Java 中只有值傳遞?

首先回顧一下在程序設計語言中有關將參數(shù)傳遞給方法(或函數(shù))的一些專業(yè)術語。按值調用(call by value)表示方法接收的是調用者提供的值,而按引用調用(call by reference)表示方法接收的是調用者提供的變量地址。一個方法可以修改傳遞引用所對應的變量值,而不能修改傳遞值調用所對應的變量值。 它用來描述各種程序設計語言(不只是Java)中方法參數(shù)傳遞方式。

Java程序設計語言總是采用按值調用。也就是說,方法得到的是所有參數(shù)值的一個拷貝,也就是說,方法不能修改傳遞給它的任何參數(shù)變量的內容。

下面通過 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

解析:

example 1

在swap方法中,a、b的值進行交換,并不會影響到 num1、num2。因為,a、b中的值,只是從 num1、num2 的復制過來的。也就是說,a、b相當于num1、num2 的副本,副本的內容無論怎么修改,都不會影響到原件本身。

通過上面例子,我們已經知道了一個方法不能修改一個基本數(shù)據(jù)類型的參數(shù),而對象引用作為參數(shù)就不一樣,請看 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) {
        // 將數(shù)組的第一個元素變?yōu)?
        array[0] = 0;
    }

結果:

1
0

解析:

example 2

array 被初始化 arr 的拷貝也就是一個對象的引用,也就是說 array 和 arr 指向的時同一個數(shù)組對象。 因此,外部對引用對象的改變會反映到所對應的對象上。

通過 example2 我們已經看到,實現(xiàn)一個改變對象參數(shù)狀態(tài)的方法并不是一件難事。理由很簡單,方法得到的是對象引用的拷貝,對象引用及其他的拷貝同時引用同一個對象。

很多程序設計語言(特別是,C++和Pascal)提供了兩種參數(shù)傳遞的方式:值調用和引用調用。有些程序員(甚至本書的作者)認為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:小李

解析:

交換之前:

image

交換之后:

image

通過上面兩張圖可以很清晰的看出: 方法并沒有改變存儲在變量 s1 和 s2 中的對象引用。swap方法的參數(shù)x和y被初始化為兩個對象引用的拷貝,這個方法交換的是這兩個拷貝

總結

Java程序設計語言對對象采用的不是引用調用,實際上,對象引用是按
值傳遞的。

下面再總結一下Java中方法參數(shù)的使用情況:

  • 一個方法不能修改一個基本數(shù)據(jù)類型的參數(shù)(即數(shù)值型或布爾型》
  • 一個方法可以改變一個對象參數(shù)的狀態(tài)。
  • 一個方法不能讓對象參數(shù)引用一個新的對象。

參考:

《Java核心技術卷Ⅰ》基礎知識第十版第四章4.5小節(jié)

二 ==與equals(重要)

== : 它的作用是判斷兩個對象的地址是不是相等。即,判斷兩個對象是不是同一個對象。(基本數(shù)據(jù)類型==比較的是值,引用數(shù)據(jù)類型==比較的是內存地址)

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方法比較的是對象的值。
  • 當創(chuàng)建String類型的對象時,虛擬機會在常量池中查找有沒有已經存在的值和要創(chuàng)建的值相同的對象,如果有就把它賦給當前引用。如果沒有就在常量池中重新創(chuàng)建一個String對象。

三 hashCode與equals(重要)

面試官可能會問你:“你重寫過 hashcode 和 equals 么,為什么重寫equals時必須重寫hashCode方法?”

hashCode()介紹

hashCode() 的作用是獲取哈希碼,也稱為散列碼;它實際上是返回一個int整數(shù)。這個哈希碼的作用是確定該對象在哈希表中的索引位置。hashCode() 定義在JDK的Object.java中,這就意味著Java中的任何類都包含有hashCode() 函數(shù)。另外需要注意的是: Object 的 hashcode 方法是本地方法,也就是用 c 語言或 c++ 實現(xiàn)的,該方法通常用來將對象的 內存地址 轉換為整數(shù)之后返回。

    /**
     * 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&trade; 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),它的特點是:能根據(jù)“鍵”快速的檢索出對應的“值”。這其中就利用到了散列碼?。梢钥焖僬业剿枰膶ο螅?/p>

為什么要有hashCode

我們以“HashSet如何檢查重復”為例子來說明為什么要有hashCode:

當你把對象加入HashSet時,HashSet會先計算對象的hashcode值來判斷對象加入的位置,同時也會與其他已經加入的對象的hashcode值作比較,如果沒有相符的hashcode,HashSet會假設對象沒有重復出現(xiàn)。但是如果發(fā)現(xiàn)有相同hashcode值的對象,這時會調用equals()方法來檢查hashcode相等的對象是否真的相同。如果兩者相同,HashSet就不會讓其加入操作成功。如果不同的話,就會重新散列到其他位置。(摘自我的Java啟蒙書《Head fist java》第二版)。這樣我們就大大減少了equals的次數(shù),相應就大大提高了執(zhí)行速度。

hashCode()與equals()的相關規(guī)定

  1. 如果兩個對象相等,則hashcode一定也是相同的
  2. 兩個對象相等,對兩個對象分別調用equals方法都返回true
  3. 兩個對象有相同的hashcode值,它們也不一定是相等的
  4. 因此,equals方法被覆蓋過,則hashCode方法也必須被覆蓋
  5. hashCode()的默認行為是對堆上的對象產生獨特值。如果沒有重寫hashCode(),則該class的兩個對象無論如何都不會相等(即使這兩個對象指向相同的數(shù)據(jù))

為什么兩個對象有相同的hashcode值,它們也不一定是相等的?

在這里解釋一位小伙伴的問題。以下內容摘自《Head Fisrt Java》。

因為hashCode() 所使用的雜湊算法也許剛好會讓多個對象傳回相同的雜湊值。越糟糕的雜湊算法越容易碰撞,但這也與數(shù)據(jù)值域分布的特性有關(所謂碰撞也就是指的是不同的對象得到相同的 hashCode)。

我們剛剛也提到了 HashSet,如果 HashSet 在對比的時候,同樣的 hashcode 有多個對象,它會使用 equals() 來判斷是否真的相同。也就是說 hashcode 只是用來縮小查找成本。

參考:

https://blog.csdn.net/zhzhao999/article/details/53449504

https://www.cnblogs.com/skywang12345/p/3324958.html

https://www.cnblogs.com/skywang12345/p/3324958.html

https://www.cnblogs.com/Eason-S/p/5524837.html

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容