Java常見面試題彙總-----------Java基礎(基本類型和包裝類型、equals和hashCode、++和--、值傳遞和引用傳遞)

19. Java基本數據類型、有了基本數據類型,爲什麼還需要包裝類型?

19.1、Java基本數據類型,數值範圍

  Java共有4類8種基礎數據類型:byte、short、int、long、float、double、char、boolean。
  1、四種整數類型(byte、short、int、long):byte:8位,用於表示最小數據單位,如文件中數據,-128~127。 short:16位,很少用,-32768 ~ 32767。int:32位,最常用,-231-1~231(21億)。long:64位,次常用。注意事項:int i=5; //5叫直接量(或字面量),即直接寫出的常數。整數字面量默認都爲int類型,所以在定義的long型數據後面加L或l。小於32 位數的變量,都按int結果計算。強轉符比數學運算符優先級高。

  2、兩種浮點數類型(float、double):float:32位,後綴F或f,1位符號位,8位指數,23位有效尾數。double:64位,最常用,後綴D或d,1位符號位,11位指數,52位有效尾數。注意事項:浮點數字面量默認都爲double類型,所以在定義的float型數據後面加F或f;double類型可不寫後綴,但在小數計算中一定要寫D或X.X。float的精度沒有long高,有效位數(尾數)短。float的範圍大於long,指數可以很大。浮點數是不精確的,不能對浮點數進行精確比較。

  3、一種字符類型(char):char:16位,是整數類型,用單引號括起來的1個字符(可以是一箇中文字符),使用Unicode碼代表字符,0~2^16-1(65535)。注意事項:不能爲0個字符。轉義字符:\n 換行、\r回車、\t Tab字符、\" 雙引號、\\表示一個\,兩字符char中間用“+”連接,內部先把字符轉成int類型,再進行加法運算,char本質就是個數!二進制的,顯示的時候,經過“處理”顯示爲字符。

  4、一種布爾類型(boolean):true真和false假。
  5、類型轉換:char–>自動轉換:byte–>short–>int–>long–>float–>double。強制轉換:①會損失精度,產生誤差,小數點以後的數字全部捨棄。②容易超過取值範圍。

19.2、爲什麼需要包裝類型

  我們都知道在Java語言中,new一個對象存儲在堆裏,我們通過棧中的引用來使用這些對象;但是對於經常用到的一系列類型如int,如果我們用new將其存儲在堆裏就不是很有效——特別是簡單的小的變量。所以就出現了基本類型,更加高效。
  而爲什麼還需要包裝類型呢?Java是一個面相對象的編程語言,基本類型並不具有對象的性質,爲了讓基本類型也具有對象的特徵,就出現了包裝類型(每一種基本的數據類型都有一種對應的包裝類型,如我們在使用集合類型Collection時就一定要使用包裝類型而非基本類型),它相當於將基本類型“包裝起來”,使得它具有了對象的性質,並且爲其添加了屬性和方法(最大值、最小值、null),豐富了基本類型的操作。
  另外,當需要往ArrayList,HashMap中放東西時,像int,double這種基本類型是放不進去的,因爲容器都是裝object的,這是就需要這些基本類型的包裝器類了。

19.3、裝箱與拆箱

  裝箱就是自動將基本數據類型轉換爲包裝器類型;拆箱就是自動將包裝器類型轉換爲基本數據類型。
  Integer i = 1;自動裝箱,實際上在編譯時會調用Integer.valueOf()方法來裝箱,在Java5之前,只能通過new的方式創建一個Integer對象,Java5之後實現自動裝箱。
  Integer i = 1;
  int j = i; //自動拆箱,實際上也會在編譯器調用Integer.intValue();
  其他的也類似,比如Double、Character等。
  因此可以用一句話總結裝箱和拆箱的實現過程:裝箱過程是通過調用包裝器的valueOf方法實現的,而拆箱過程是通過調用包裝器的 xxxValue方法實現的。(xxx代表對應的基本數據類型)。

19.4、Integer的緩存值

首先看下Integer的valueOf()方法:

public static Integer valueOf(int i) {
    if(i >= -128 && i <= IntegerCache.high)
        return IntegerCache.cache[i + 128];
    else
        return new Integer(i);
}

  在通過valueOf方法創建Integer對象的時候,如果數值在[-128,127]之間,便返回指向IntegerCache.cache中已經存在的對象的引用;否則創建一個新的Integer對象。這樣做主要的目的就是爲了提高效率(因爲一些小值數據經常使用)。
  注意,Integer、Short、Byte、Character、Long這幾個類的valueOf方法的實現是類似的。Double、Float的valueOf方法的實現是類似的。這主要是因爲在某個範圍內的整型數值的個數是有限的,而浮點數卻不是。

20. hashCode和equals比較、equals與“==”比較

20.1、equals與“==”

  1、基本數據類型,也稱原始數據類型。
  Byte,short,char,int,long,float,double,boolean,他們之間的比較,應用雙等號(==),比較的是他們的值。

int a = 10;
long b = 10l;
double c = 10.0;
System.out.println(a == b); // true
System.out.println(a == c); // true

  這裏比較的時候存在強制類型的轉換,低精度自動向高精度轉換,然後比較。

  2、複合數據類型(類)
  當他們用(==)進行比較的時候,比較的是他們在內存中的存放地址,所以,除非是同一個new出來的對象,他們的比較後的結果爲true,否則比較後結果爲false。JAVA當中所有的類都是繼承於Object這個基類的,在Object中的基類中定義了一個equals的方法,這個方法的初始行爲是比較對象的內存地址, 但在一些類庫當中這個方法被覆蓋掉了,如String,Integer,Date在這些類當中equals有其自身的實現,而不再是比較類在堆內存中的存放地址了。而是比較指向的對象所存儲的內容是否相等。
  對於複合數據類型之間進行equals比較,在沒有覆寫equals方法的情況下,他們之間的比較還是基於他們在內存中的存放位置的地址值的,因爲Object的equals方法也是用雙等號(==)進行比較的,所以比較後的結果跟雙等號(==)的結果相同。

20.2、hashCode()是什麼

  hashCode() 的作用是獲取哈希碼,也稱爲散列碼;它實際上是返回一個int整數。這個哈希碼的作用是確定該對象在哈希表中的索引位置。
  hashCode() 定義在JDK的Object類中,這就意味着Java中的任何類都包含有hashCode() 函數。
  雖然,每個Java類都包含hashCode()函數。但是,僅僅當創建並某個“類的散列表”(散列表指的是:Java集合中本質是散列表的類,如HashMap,Hashtable,HashSet)時,該類的hashCode() 纔有用(作用是:確定該類的每一個對象在散列表中的位置)。其它情況下(例如,創建類的單個對象,或者創建類的對象數組等等),類的hashCode() 沒有作用。
  也就是說:hashCode() 在散列表中才有用,在其它情況下沒用。在散列表中hashCode() 的作用是獲取對象的散列碼,進而確定該對象在散列表中的位置。
  我們都知道,散列表存儲的是鍵值對(key-value),它的特點是:能根據“鍵”快速的檢索出對應的“值”。這其中就利用到了散列碼!
  散列表的本質是通過數組實現的。 當我們要獲取散列表中的某個“值”時,實際上是要獲取數組中的某個位置的元素。而數組的位置,就是通過“鍵”來獲取的;更進一步說,數組的位置,是通過“鍵”對應的散列碼計算得到的。
  下面,我們以HashSet爲例,來深入說明hashCode()的作用。
  假設,HashSet中已經有1000個元素。當插入第1001個元素時,需要怎麼處理?因爲HashSet是Set集合,它不允許有重複元素。
  “將第1001個元素逐個的和前面1000個元素進行比較”?顯然,這個效率是相等低下的。散列表很好的解決了這個問題,它根據元素的散列碼計算出元素在散列表中的位置,然後將元素插入該位置即可。對於相同的元素,自然是隻保存了一個。
  由此可知,若兩個元素相等,它們的散列碼一定相等; 但反過來確不一定。在散列表中,1、如果兩個對象相等(equals),那麼它們的hashCode()值一定要相同;2、如果兩個對象hashCode()相等,它們並不一定相等。

20.3、hashCode和equals的關係

  “hashCode() 和 equals()的關係”分2種情況來說明。
  第一種:不會創建“類對應的散列表”
  這裏所說的“不會創建類對應的散列表”是說:我們不會在HashSet, Hashtable, HashMap等等這些本質是散列表的數據結構中,用到該類。例如,不會創建該類的HashSet集合。
  在這種情況下,該類的“hashCode() 和 equals() ”沒有半毛錢關係的!這種情況下,equals() 用來比較該類的兩個對象是否相等。而hashCode() 則根本沒有任何作用,
  第二種:會創建“類對應的散列表”
  這裏所說的“會創建類對應的散列表”是說:我們會在HashSet, Hashtable, HashMap等等這些本質是散列表的數據結構中,用到該類。例如,會創建該類的HashSet集合。
  在這種情況下,該類的“hashCode() 和 equals() ”是有關係的:
  1)、如果兩個對象相等,那麼它們的hashCode()值一定相同。 這裏的相等是指,通過equals()比較兩個對象時返回true。
  2)、如果兩個對象hashCode()相等,它們並不一定相等。 因爲在散列表中,hashCode()相等,即兩個鍵值對的哈希值相等。然而哈希值相等,並不一定能得出鍵值對相等。補充說一句:“兩個不同的鍵值對,哈希值相等”,這就是哈希衝突。
  總結:
  1、如果根據 equals(Object) 方法,兩個對象是相等的,那麼對這兩個對象中的每個對象調用 hashCode 方法都必須生成相同的整數結果。
  2、如果兩個hashCode()返回的結果相等,則兩個對象的equals方法不一定相等。
  3、如果根據equals(java.lang.Object)方法,兩個對象不相等,那麼對這兩個對象中的任一對象上調用 hashCode 方法不一定生成不同的整數結果。但是,程序員應該意識到,爲不相等的對象生成不同整數結果可以提高哈希表的性能。
  因此在重寫類的equals方法時,也重寫hashcode方法,使相等的兩個對象獲取的HashCode也相等,這樣當此對象做Map類中的Key時,兩個equals爲true的對象其獲取的value都是同一個,比較符合實際。

21. 自增(++)和自減(–)的問題

  在java中,a++ 和 ++a的相同點都是給a+1,不同點是a++是先參加程序的運行再+1,而++a則是先+1再參加程序的運行。
  舉個例子來說:a = 2; b = a++;運行後:b = 2,a = 3;
         a = 2; b = ++a;運行後:b = 3,a = 3;
  a-- 和 --a情況與 a++ 和 ++a相似,a–爲先參加程序運算再-1;–a爲先減1後參加運算。

21.1、++和+1的區別

int i = 0;
i = i++;
System.out.println(i);

上述程序輸出0;

int i = 0;
i = ++i;
System.out.println(i);

上述程序輸出1;

  Java採取了中間變量緩存機制!在java中,執行自增運算時,會爲每一個自增操作分配一個臨時變量,如果是前綴加(++i),就會“先自加1後賦值(給臨時變量)”;如果是後綴加(i++),就會“先賦值(給臨時變量)後自加1”。運算最終使用的,並不是變量本身,而是被賦了值的臨時變量。

i = i++;                                        |   i = a++
int temp = i;  //先自增1,再使用i的值             |   int temp = a;
i  = i + 1;                                     |   a = a + 1;
i = temp;                                       |   i = temp;

i = ++i;                                        |   i = ++a
i  = i + 1;  //先自增1,再使用i的值               |   a = a + 1;
int temp = i;                                   |   int temp = a;
i = temp;                                       |   i = temp;

21.2、a = a+1和a += 1的區別

我們先看一段代碼:

byte b = 2;
b = b + 1;
System.out.println(b);

運行結果:錯誤: 不兼容的類型: 從int轉換到byte可能會有損失
報錯的原因是short變量在參與運算時會自動提升爲int類型,b+1運算完成後變爲int,int賦值給short報錯。
換成+=的情況:

byte b = 2;
b += 1;
System.out.println(b);

編譯通過,輸出結果3。
這是因爲b += 1並不是完全等價於b = b + 1,而是隱含了強制類型轉換,相當於b = (short)(b+1)。
注意:+=不會進行溢出檢查

byte b = 127;
b += 1;
System.out.println(b);

輸出結果是-128,開發中要特別注意。

22. 值傳遞與引用傳遞

  值傳遞是對基本數據類型變量而言的,傳遞的是該變量值的一個副本,改變副本不影響原變量。(此時內存中存在兩個相等的基本類型,即實際參數和形式參數)
  引用傳遞一般是對於對象類型而言的,傳遞的是該對象地址的一個副本,並不是原對象本身。也就是說實參和形參同時指向了同一個對象,因此在函數中可以通過形參來修改對象中的數據,但是讓形參重新指向另一個對象對實參是沒有任何影響的。

程序實例:

public class Parameter {
    
    public static void main(String[] args) {
        int real = 5;
        Obj obj = new Obj("5", 5);
        String str = "5";
        StringBuffer bf = new StringBuffer("5");
        
        System.out.println("調用方法前:");
        System.out.println("real = " + real);
        System.out.println("obj = " + obj);
        System.out.println("str = " + str);
        System.out.println("bf = " + bf);
        
        method(real);
        method(obj);
        method(str);
        method(bf);
        System.out.println("調用方法後:");
        System.out.println("real = " + real);
        System.out.println("obj = " + obj);
        System.out.println("str = " + str);
        System.out.println("bf = " + bf);
    }
    
    public static void method(int para) {
        // 基本數據類型,形參的改變不影響實參
        para = 10;
    }
    
    public static void method(Obj para) {
        // 引用數據類型,可以通過相同的引用修改對象內容
        para.age = 10;
    }
    
    public static void method(String para) {
        // 引用數據類型,形參指向新的對象,不影響實參(String類不可變,+操作會產生新對象)
        para = para + 10;
    }
    
    public static void method(StringBuffer para) {
        // 引用數據類型,可以通過相同的引用修改對象內容
        para.append(10);
    }
}

class Obj {
    String name;
    int age;
    
    public Obj(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String toString() {
        return "name:" + name + ",age:" + age;
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章