java面試集合相關(guān)

準(zhǔn)備知識(shí)點(diǎn):

  • java中涉及到集合的接口與實(shí)現(xiàn)類都位于java.util包下
  • 集合中接口繼承體系如下圖所示:


    java集合接口體系.png
  • 集合中各個(gè)接口所對(duì)應(yīng)的主要實(shí)現(xiàn)類如下圖所示:


    集合中接口所對(duì)應(yīng)的主要實(shí)現(xiàn)類.png

筆試題目一:

代碼如下,問(wèn)控制臺(tái)為什么只打印出abc、xyz?

class People {
    String name;
    public People(String name) {
        this.name = name;
    }
    
    public String getName() {
        return this.name;
    }
}
public class HashSetTest {

    public static void main(String[] args) {
        Set<String> set = new HashSet<String>();
        set.add(new String("abc"));
        set.add(new String("xyz"));
        set.add(new String("abc"));
        for (Iterator<String> ite = set.iterator(); ite.hasNext();) {
            String next = ite.next();
            System.out.println(next);
        }
        
    }
}

答案:因?yàn)镠ashSet集合的特性是元素唯一性,而HashSet底層是通過(guò)hashcode以及equals方法來(lái)判定對(duì)象是否唯一的,而String類覆寫(xiě)了equals方法以及hashcode方法這讓new String("abc")與new String("abc")的hashcode以及equals方法的比較都為true從而被判定是重復(fù)元素?zé)o法添加到集合中。

準(zhǔn)備知識(shí)點(diǎn):

  • HashSet底層是通過(guò)HashMap來(lái)實(shí)現(xiàn)的
  • HashMap底層是通過(guò)Entry數(shù)組存儲(chǔ)Entry鏈表來(lái)實(shí)現(xiàn)的

HashSet增加元素時(shí)java底層的操作流程如下:

  • 調(diào)用HashMap.put方法來(lái)存儲(chǔ)key、value
  • 使用hash算法對(duì)key.hashCode()計(jì)算出一個(gè)hash值
  • 讓得到的hash值與數(shù)組長(zhǎng)度 - 1進(jìn)行&運(yùn)算得到Entry數(shù)組索引位置(該位置用來(lái)存儲(chǔ)Entry對(duì)象,該Entry對(duì)象封裝了所要添加的key-value對(duì))
  • 得到Entry數(shù)組索引后,遍歷該索引位置所指向的Entry鏈表中的Entry元素,如果計(jì)算得到的hash值與鏈表中對(duì)象的hash值相等且(key對(duì)象是同一個(gè)對(duì)象或者key對(duì)象的內(nèi)容相等)那么當(dāng)前所添加的value值覆蓋鏈表中匹配到的元素的value值并返回老的value值,至此HashSet.add方法結(jié)束,如果遍歷了Entry鏈表后發(fā)現(xiàn)沒(méi)有一個(gè)元素符合條件那么就會(huì)將所添加的key對(duì)象與value對(duì)象封裝成一個(gè)新的Entry對(duì)象并將所計(jì)算出來(lái)的Entry數(shù)組索引指向這個(gè)新的Entry對(duì)象,而這個(gè)新的Entry對(duì)象的next屬性指向舊的鏈表,換言之將新生成的Entry對(duì)象插入到Entry數(shù)組所計(jì)算出的索引處所指向的鏈表頭部。

總結(jié):當(dāng)向集合HashSet中增加對(duì)象時(shí),首先集合計(jì)算要增加對(duì)象的hashCode碼,根據(jù)該值來(lái)得到一個(gè)Entry數(shù)組索引位置用來(lái)存放當(dāng)前的對(duì)象,當(dāng)在該位置沒(méi)有一個(gè)對(duì)象的話,那么HashSet認(rèn)為該對(duì)象在集合中不存在,直接增加進(jìn)去。如果在該位置有一個(gè)Entry對(duì)象存在的話,接著將準(zhǔn)備增加到集合中的對(duì)象與該位置上的Entry鏈表中的各個(gè)Entry對(duì)象進(jìn)行equals方法和hashcode值比較,如果迭代完畢后發(fā)現(xiàn)沒(méi)有一個(gè)Entry對(duì)象的equals方法以及hashcode值與新增對(duì)象相符(注:即比較結(jié)果為true),那么集合認(rèn)為集合中不存在該對(duì)象,直接將該對(duì)象封裝成Entry對(duì)象并將其做為數(shù)組索引處所指向的Entry鏈表的頭部元素,如果在迭代過(guò)程中發(fā)現(xiàn)有元素與新增的元素的equals比較以及hashcode值得比較都為true,那么集合認(rèn)為集合中已經(jīng)存在該對(duì)象了,不會(huì)再將該對(duì)象增加到集合中去了(注:不過(guò)對(duì)于HashMap它會(huì)將對(duì)象的value值替換匹配到的對(duì)象的value值并將舊的value值返回)

筆試題目二:

代碼如下,控制臺(tái)會(huì)輸出什么,并進(jìn)行相應(yīng)的解釋:

class People {
    String name;
    public People(String name) {
        this.name = name;
    }
    
    public String getName() {
        return this.name;
    }
}
public class HashSetTest {

    public static void main(String[] args) {
        Set<People> set = new HashSet<People>();
        set.add(new People("zhangsan"));
        set.add(new People("lisi"));
        set.add(new People("zhangsan"));
        for (Iterator<People> ite = set.iterator(); ite.hasNext();) {
            People next = ite.next();
            System.out.println(next.getName());
        }
    }
}

答案:控制臺(tái)會(huì)輸出zhangsan、lisi、zhangsan。因?yàn)镻eople類沒(méi)有覆寫(xiě)equals以及hashcode方法,從而導(dǎo)致HashSet底層判定它們?yōu)椴煌兀罱K被存儲(chǔ)到了集合中。

筆試題目三:

代碼如下,控制臺(tái)會(huì)輸出什么?為什么?


class People {
    String name;
    public People(String name) {
        this.name = name;
    }
    
    public String getName() {
        return this.name;
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (null == obj) return false;
        if (obj instanceof People) {
            People p = (People)obj;
            return p.name.equals(this.name);
        }
        return false;
    }
    
    @Override
    public int hashCode() {
        return this.name.hashCode();
    }
   
}
public class HashSetTest {

    public static void main(String[] args) {
        Set<People> set = new HashSet<People>();
        set.add(new People("zhangsan"));
        set.add(new People("lisi"));
        set.add(new People("zhangsan"));
        for (Iterator<People> ite = set.iterator(); ite.hasNext();) {
            People next = ite.next();
            System.out.println(next.getName());
        }
    }
}

答案:控制臺(tái)會(huì)輸出lisi、zhangsan。因?yàn)镻eople類重寫(xiě)了equals方法以及hashcode方法,從而在HashSet底層new People("zhangsan")與new People("zhangsan")被判定為重復(fù)元素,故無(wú)法添加到集合中。

知識(shí)點(diǎn)羅列:

  • 當(dāng)重寫(xiě)equals方法的時(shí)候必須重寫(xiě)hashcode方法 (HashMap/HashSet都需要用到這個(gè)特性,否則這些對(duì)象要存儲(chǔ)于HashMap/HashSet集合中的時(shí)候會(huì)出現(xiàn)重復(fù)元素存儲(chǔ)的問(wèn)題,即無(wú)法維持元素的唯一性。)
  • 如果一個(gè)類的兩個(gè)對(duì)象進(jìn)行equals方法比較的時(shí)候返回true,那么這兩個(gè)對(duì)象必須具有相同的hashcode。(否則存儲(chǔ)到HashMap/HashSet里的時(shí)候還是無(wú)法維持元素的唯一性。注:判定元素重復(fù)的條件是:hashcode以及equals方法都返回true)
  • transient修飾的變量無(wú)法被序列化到文件中,信息會(huì)丟失
  • HashMap對(duì)于key為null的存儲(chǔ)規(guī)則是將它存儲(chǔ)在HashMap所維護(hù)的Entry數(shù)組的第一個(gè)元素的鏈表中(即:key為null的key-value永遠(yuǎn)存儲(chǔ)在數(shù)組的第一個(gè)元素的鏈表中)
  • java約定用關(guān)鍵字final修飾的常量名要大寫(xiě)
  • 對(duì)于HashSet、HashMap來(lái)說(shuō),這樣做就是為了提高查找效率,使得查找時(shí)間不隨著set或者map的大小而改變
  • HashMap所維護(hù)的Entry數(shù)組的默認(rèn)長(zhǎng)度是16
  • HashMap負(fù)載因子默認(rèn)為0.75f(根據(jù)經(jīng)驗(yàn)得到,數(shù)組現(xiàn)存儲(chǔ)元素個(gè)數(shù)/數(shù)組長(zhǎng)度),主要是降低沖突的可能性,當(dāng)超過(guò)該負(fù)載因子的時(shí)候會(huì)擴(kuò)容數(shù)組,不讓數(shù)組中的Entry鏈表長(zhǎng)度過(guò)長(zhǎng)而違背HashMap設(shè)計(jì)的初衷
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容