自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Σ(っ°Д°;)っ找個對象"Object"還要用八股文?

開發(fā) 前端
還是那句話,不管你是初級、中級、還是高開,甚至還是資深,不開玩笑的說,面試前都要刷八股文,因為你沒法保證的是遇見的面試官都會因為你的職位,而和你聊項目、架構(gòu)、源碼,我們能做的只能是做好準備。

[[414599]]

本文轉(zhuǎn)載自微信公眾號「稀飯下雪」,作者帥氣的小飯飯。轉(zhuǎn)載本文請聯(lián)系稀飯下雪公眾號。

還是那句話,不管你是初級、中級、還是高開,甚至還是資深,不開玩笑的說,面試前都要刷八股文,因為你沒法保證的是遇見的面試官都會因為你的職位,而和你聊項目、架構(gòu)、源碼,我們能做的只能是做好準備。

反正Object的我自覺八股文應(yīng)該是這些了,有興趣就看看。

Object八股文目錄,缺哪看哪,都缺都看!

  • equals
    • equlas 跟 == 的區(qū)別?
    • 說說看對hashCode和equals方法的理解?
    • 說說看hashCode的作用?
    • 說說看hash沖突或者碰撞?
  • clone方法
    • 淺拷貝是啥?
    • 實現(xiàn)淺拷貝的方法?
    • 深拷貝是啥?
    • 實現(xiàn)深拷貝的方法?
  • sleep、wait、notify、notifyAll
    • 使用wait、notify實現(xiàn)生產(chǎn)者、消費者模式
    • 說說看wait和sleep的異同?
    • 為什么wait 需要在同步代碼塊中使用?
    • 為什么wait notify notifyAll是定義在Object類 , 而 sleep是定義在Thread類中?
    • wait屬于Object 對象, 如果調(diào)用Thread.wait會發(fā)生什么?
    • 說說看notify和notifyAll的區(qū)別?
    • notifyAll后所有線程都會再次的搶占鎖,如果搶占失敗怎么辦?
  • 說說看finalize()的作用?
  • Java類裝載過程是什么樣的?
  • Class.forName()和ClassLoader.loadClass區(qū)別?

equals

沒什么區(qū)別。

test1會直接報空指針異常,你想想看,null.equals看不起來不就怪怪的嗎?空指針怎么可能有方法呢是吧,

「在日常開發(fā)中的意義:」 我們一般在企業(yè)開發(fā)中都會將已知的字面量放在equals,未知的參數(shù)放在equals后面,這樣可以避免空指針,編程新手容易犯這個異常,我review過的代碼這么實現(xiàn)的,說實話,挺多次的,不止編程新人,兩三年工作經(jīng)驗的都會這么做。

equlas 跟 == 的區(qū)別?

equals方法比較的是字符串的內(nèi)容是否相等,而 == 比較的則是對象地址。

首先Java中的數(shù)據(jù)類型可以分為兩種,一種是基本數(shù)據(jù)類型,也稱原始數(shù)據(jù)類型,如byte,short,char,int,long,float,double,boolean 他們之間的比較,應(yīng)用雙等號(==),比較的是他們的值。

另一種是復合數(shù)據(jù)類型,包括類,當他們用(==)進行比較的時候,比較的是他們在內(nèi)存中的存放地址,所以,除非 是同一個new出來的對象,他們的比較后的結(jié)果為true,否則比較后結(jié)果為false。

而JAVA當中所有的類都是繼承于Object這個基類的,在Object中的基類中定義了一個equals的方法,這個方法的初始行為是比較對象的內(nèi)存地址,但在一些類庫當中這個方法被覆蓋掉了,如String,Integer,Date在這些類當中equals有其自身的實現(xiàn),而不再是比較類在堆內(nèi)存中的存放地址了。

「在日常開發(fā)中的意義:」 沒記錯的話,我剛java也是經(jīng)常在考慮到底用equals還是用 == 做對比。

說說看對hashCode和equals方法的理解?

如果兩個對象equals方法相等,則它們的hashCode一定相同;

如果兩個對象的hashCode相同,它們的equals()方法則不一定相等。

而兩個對象的hashCode()返回值相等不能判斷這兩個對象是相等的,但兩個對象的hashcode()返回值不相等則可以判定兩個對象一定不相等。

因為對兩個對象是否相等的判斷都會通過先判斷hashCode,如果hashCode相等再判斷equals,保證對象一定相同。

說說看hashCode的作用?

hashCode的作用實際上是為了提高在散列結(jié)構(gòu)存儲中查找的效率,equals的實現(xiàn)會去判斷對象的所有成員一個個判斷,效率上來說是相對較慢的,,而hashCode則不一樣,hashCode是根據(jù)所有成員生成了一個值,將兩個值進行對比,因此效率更高,等到hashCode相同了,再去調(diào)用equals判斷,因為兩個對象hashCode相同,不一定意味著兩個對象相同,還存在著hash沖突呀。

說說看hash沖突或者碰撞?

對象Hash的前提是實現(xiàn)equals()和hashCode()兩個方法,那么HashCode()的作用就是保證對象返回唯一hash值,但當兩個對象計算值一樣時,這就發(fā)生了碰撞沖突。

那么如何解決hash沖突呢?

開放定址法

其實說穿了就是上次hash出來的值沖突了,那就再次散列,

  • 線性探測再散列:沖突發(fā)生時,順序查看表中下一單元,直到找出一個空單元或查遍全表。
  • 二次探測再散列:沖突發(fā)生時,在表的左右進行跳躍式探測,比較靈活。
  • 偽隨機探測再散列:具體實現(xiàn)是建立一個偽隨機數(shù)發(fā)生器,(如i=(i+p) % m),并給定一個隨機數(shù)做起點。

再哈希法

當哈希地址Hi=RH1(key)發(fā)生沖突時,再計算Hi=RH2(key)……,直到?jīng)_突不再產(chǎn)生。這種方法不易產(chǎn)生聚集,但增加了計算時間。

鏈地址法

這種方法的基本思想是將所有哈希地址為i的元素構(gòu)成一個稱為同義詞鏈的單鏈表,并將單鏈表的頭指針存在哈希表的第i個單元中,因而查找、插入和刪除主要在同義詞鏈中進行。鏈地址法適用于經(jīng)常進行插入和刪除的情況。

貌似HashMap用的就是鏈地址法,當插入的時候,會根據(jù)key的hash值然后計算出相應(yīng)的數(shù)組下標,計算方法是index = hashcode%table.length,當這個下標上面已經(jīng)存在元素的時候那么就會形成鏈表,將后插入的元素放到尾端,若是下標上面沒有存在元素的話,那么將直接將元素放到這個位置上。當進行查詢的時候,同樣會根據(jù)key的hash值先計算相應(yīng)的下標,然后到相應(yīng)的位置上進行查找,若是這個下標上面有很多元素的話,那么將在這個鏈表上一直查找直到找到對應(yīng)的元素。

  • 建立公共溢出區(qū)

將哈希表分為基本表和溢出表兩部分,凡是和基本表發(fā)生沖突的元素,一律填入溢出表。

「在日常開發(fā)中的意義:」 日常開發(fā)中容器用多了,有時候確實會遇見需要重寫equals和hashCode的情況,所以理解是有用的,另外就是有時候會遇見兩個對象明明不一樣的,但是被誤判一樣了的問題,最終找到是lombok注解@Data的原因,如果不了解equals和hashCode的原理,其實你確實找不到兇手。別肝,自然你還得知道@Data其實包括了重寫了hashCode和euqals。

clone方法

每次問到這道題,大部分人都是回答2,小部分人是回答1。

都錯,正確答案是直接報錯

為什么?因為clone方法是Object的protect方法,需要子類顯示的去重寫clone方法,并且實現(xiàn)Cloneable 接口,這是規(guī)定。

淺拷貝是啥?

  • 對于數(shù)據(jù)類型是基本數(shù)據(jù)類型的成員變量,淺拷貝會直接進行值傳遞,也就是將該屬性值復制一份給新的對象。因為是兩份不同的數(shù)據(jù),所以對其中一個對象的該成員變量值進行修改,不會影響另一個對象拷貝得到的數(shù)據(jù)。
  • 對于數(shù)據(jù)類型是引用數(shù)據(jù)類型的成員變量,比如說成員變量是某個數(shù)組、某個類的對象等,那么淺拷貝會進行引用傳遞,也就是只是將該成員變量的引用值(內(nèi)存地址)復制一份給新的對象。因為實際上兩個對象的該成員變量都指向同一個實例。在這種情況下,在一個對象中修改該成員變量會影響到另一個對象的該成員變量值。

實現(xiàn)淺拷貝的方法?

  • 通過拷貝構(gòu)造方法實現(xiàn)淺拷貝,這個沒什么好說的;
  • 通過重寫clone()方法進行淺拷貝:Object類是類結(jié)構(gòu)的根類,其中有一個方法為protected Object 「clone」() 這個方法就是進行的淺拷貝。

有了這個淺拷貝模板,我們可以通過調(diào)用clone()方法來實現(xiàn)對象的淺拷貝。

但是需要注意:

1、Object類雖然有這個方法,但是這個方法是受保護的(被protected修飾),所以我們無法直接使用。

2、使用clone方法的類必須實現(xiàn)Cloneable接口,否則會拋出異常CloneNotSupportedException。

對于這兩點,我們的解決方法是,在要使用clone方法的類中重寫clone()方法,通過super.clone()調(diào)用Object類中的原clone方法。

深拷貝是啥?

對于深拷貝來說,不僅要復制對象的所有基本數(shù)據(jù)類型的成員變量值,還要為所有引用數(shù)據(jù)類型的成員變量申請存儲空間,并復制每個引用數(shù)據(jù)類型成員變量所引用的對象,直到該對象可達的所有對象;

也就是說,對象進行深拷貝要對整個對象圖進行拷貝;

簡單地說,深拷貝對引用數(shù)據(jù)類型的成員變量的對象圖中所有的對象都開辟了內(nèi)存空間;

而淺拷貝只是傳遞地址指向,新的對象并沒有對引用數(shù)據(jù)類型創(chuàng)建內(nèi)存空間。

實現(xiàn)深拷貝的方法?

通過重寫clone方法來實現(xiàn)深拷貝,與通過重寫clone方法實現(xiàn)淺拷貝的基本思路一樣,只需要為對象圖的每一層的每一個對象都實現(xiàn)Cloneable接口并重寫clone方法,最后在最頂層的類的重寫的clone方法中調(diào)用所有的clone方法即可實現(xiàn)深拷貝。

通過對象序列化實現(xiàn)深拷貝,將對象序列化為字節(jié)序列后,默認會將該對象的整個對象圖進行序列化,再通過反序列即可完美地實現(xiàn)深拷貝。

「在日常開發(fā)中的意義:」 拷貝這個知識點還是很重要的,企業(yè)開發(fā)中,如果clone對象沒有考慮深淺問題,可是分分鐘致命的。

sleep、wait、notify、notifyAll

使用wait、notify實現(xiàn)生產(chǎn)者、消費者模式

  1. public class ConsumeAndProviderDesign { 
  2.  
  3.     private static int size = 10000; 
  4.  
  5.     public static void main(String[] args) { 
  6.         ConsumeAndProviderDesign design = new ConsumeAndProviderDesign(); 
  7.         design.init(); 
  8.     } 
  9.  
  10.     private void init() { 
  11.         Container container = new Container(); 
  12.         // 生產(chǎn) 
  13.         new Thread(() -> { 
  14.             try { 
  15.                 for (int i = 1; i <= size; i++) { 
  16.                     container.add(); 
  17.                 } 
  18.             } catch (InterruptedException e) { 
  19.                 e.printStackTrace(); 
  20.             } 
  21.         }).start(); 
  22.    
  23.         // 消費 
  24.         new Thread(() -> { 
  25.             try { 
  26.                 for (int i = 1; i <= size; i++) { 
  27.                     container.remove(); 
  28.                 } 
  29.             } catch (InterruptedException e) { 
  30.                 e.printStackTrace(); 
  31.             } 
  32.         }).start(); 
  33.     } 
  34.  
  35.     class Container { 
  36.  
  37.         private List<Date> list = new ArrayList<>(); 
  38.  
  39.         // 使用this.wait()和this.notify()必須給對象加鎖,否則會報IllegalMonitorStateException異常 
  40.         private synchronized void add() throws InterruptedException { 
  41.             // 之所以用while,是因為this.notify()喚醒的不一定是滿足條件的,因為this.notify()是隨機喚醒一條等待訪問Container監(jiān)視器的線程 
  42.             while (list.size() == size) { 
  43.                 // 擁有該對象的線程會進入等待 
  44.                 this.wait(); 
  45.             } 
  46.  
  47.             list.add(new Date()); 
  48.             System.out.println("倉庫里有了 " + list.size() + " 個產(chǎn)品"); 
  49.             // 隨機喚醒擁有該對象的線程 
  50.             this.notify(); 
  51.         } 
  52.  
  53.         // 使用this.wait()和this.notify()必須給對象加鎖,否則會報IllegalMonitorStateException異常 
  54.         private synchronized void remove() throws InterruptedException { 
  55.             // 之所以用while,是因為this.notify()喚醒的不一定是滿足條件的,因為this.notify()是隨機喚醒一條等待訪問Container監(jiān)視器的線程 
  56.             while (list.size() == 0) { 
  57.                 // 擁有該對象的線程會進入等待 
  58.                 this.wait(); 
  59.             } 
  60.  
  61.             Date remove = list.remove(0); 
  62.             System.out.println("消耗了" + remove.toString() + " , 現(xiàn)在倉庫還剩下 " + list.size()); 
  63.             // 隨機喚醒擁有該對象的線程 
  64.             this.notify(); 
  65.         } 
  66.     } 

核心關(guān)注點:

  • 使用this.wait()和this.notify()必須給對象加鎖
  • this.notify()喚醒的不一定是滿足條件的,因為this.notify()是隨機喚醒一條等待訪問Container監(jiān)視器的線程,所以條件那里需要用while

「在日常開發(fā)中的意義:」 生產(chǎn)者消費者模式一直都很重要。

說說看wait和sleep的異同?

  • sleep方法

首先sleep使當前線程進入了停滯狀態(tài),也就是阻塞了當前線程,讓出CPU的使用,目的是不讓當前線程獨自霸占CPU資源,留一定時間給其他線程執(zhí)行的機會。

其次我們可以看到sleep是Thread類的靜態(tài)方法,因此不能改變持有對象的鎖,所以在當一個Synchronized塊中調(diào)用sleep方法時,線程雖然休眠了,但是持有對象的鎖并沒有被釋放,也就是說盡管線程睡著了,其他線程依舊無法獲得某對象的鎖。

  • wait方法

可以看到wait方法是屬于Object類里邊的方法,當一個線程執(zhí)行wait方法時,就意味著他進入到一個和某對象相關(guān)的等待池中,同時失去了某對象的鎖,這個時候其他線程就可以訪問了,等wait指定等待時間到或者外部調(diào)用了某對象的notify方法或者notifyAll方法才能喚醒當前等待池中的對象。

另外就是wait方法必須放在synchronized或者lock中,否則執(zhí)行的時候會拋 java.lang.IllegalMonitorStateException 異常。

總結(jié)下來就是:

sleep睡眠時,保持對象鎖;

wait等待時,釋放對象鎖;

不過wait和sleep都可以通過Interrupt方法打斷線程的暫停狀態(tài),從而使線程立刻拋出InterruptedException。

為什么wait 需要在同步代碼塊中使用?

原因是避免CPU切換到其他線程,而其他線程又提前執(zhí)行了notify()方法,那這樣就達不到我們的預期(先wait再由其他線程來喚醒),所以需要一個同步鎖來保護。

為什么wait notify notifyAll是定義在Object類 , 而 sleep是定義在Thread類中?

首先wait、notify、notifyAll三者的作用時釋放鎖、喚醒線程,java中每個對象都是Object類型,而每個線程的鎖都是對象鎖,而不是線程鎖,每個線程想要執(zhí)行鎖內(nèi)的代碼,都必須先獲取此對象,因此定義釋放鎖、喚醒線程的這兩種行為必須放在Object類中,如果放在Thread類中,那么wait要讓線程等待的時哪個鎖就不明確了。

至于sleep方法,從sleep的作用入手即可,sleep的作用是讓線程在預期的時間內(nèi)執(zhí)行,其他時候不要來占用CPU,而且不需要釋放鎖,也就是說sleep是針對線程的,因此放在Thead類中合適。

總歸就是因為在java中,wait(),notify()和notifyAll()屬于鎖級別的操作,而鎖是屬于某個對象的,因此放在Object類中。

wait屬于Object 對象, 如果調(diào)用Thread.wait會發(fā)生什么?

Thread也是個對象,這樣調(diào)用是可以的,只是Thread是個特殊的對象,在線程退出的時候會自動執(zhí)行notify,可能會導致和我們預期的設(shè)計不一致,所以一般不這么用。

說說看notify和notifyAll的區(qū)別?

notifyAll使所有原來在該對象上等待被notify的線程統(tǒng)統(tǒng)退出wait的狀態(tài),變成等待該對象上的鎖,一旦該對象被解鎖,他們就會去競爭。

notify則文明得多,他只是選擇一個wait狀態(tài)線程進行通知,并使他獲得該對象上的鎖,但不驚動其他同樣在等待被該對象notify的線程們,當?shù)谝粋€線程運行完畢以后釋放對象上的鎖此時如果該對象沒有再次使用notify語句,則即便該對象已經(jīng)空閑,其他wait狀態(tài)等待的線程由于沒有得到該對象的通知,繼續(xù)處在wait狀態(tài),直到這個對象發(fā)出一個notify或notifyAll,它們等待的是被notify或notifyAll,而不是鎖。

「在日常開發(fā)中的意義:」 貌似在寫中間件的時候用過,所以還是有用的這個知識點。

notifyAll后所有線程都會再次的搶占鎖,如果搶占失敗怎么辦?

首先看下線程的生命周期

可以看到線程在調(diào)用了wait后便處于等待狀態(tài),而在被notifyAll后,可以看到進行了可運行的RUNNABLE狀態(tài),之后搶占失敗的則進入了BLOCKED被阻塞狀態(tài)。

說說看finalize()的作用?

finalize()是在java.lang.Object里定義的,在對象被回收的時候調(diào)用,特殊情況下,需要我們實現(xiàn)finalize,當對象被回收的時候釋放一些資源,比如:一個socket鏈接,在對象初始化時創(chuàng)建,整個生命周期內(nèi)有效,那么就需要實現(xiàn)finalize,關(guān)閉這個鏈接。

雖然一個對象的finalize()方法只會被調(diào)用一次,但是finalize()被調(diào)用不意味著gc會立即回收該對象,所以有可能調(diào)用finalize()后,該對象又不需要被回收了,然后到了真正要被回收的時候,因為前面調(diào)用過一次,所以不會調(diào)用finalize(),導致出現(xiàn)Bug, 所以,推薦不要使用finalize()方法,它跟析構(gòu)函數(shù)還是不一樣的。

「在日常開發(fā)中的意義:」 知道了finalize后不會亂用就可以了,應(yīng)該說不會再用就可以了,控制生命周期的方式太多了,沒必要用它。

Java類裝載過程是什么樣的?

加載:通過類的全限定名獲取二進制字節(jié)流,將二進制字節(jié)流轉(zhuǎn)換成方法區(qū)中的運行時數(shù)據(jù)結(jié)構(gòu),在內(nèi)存中生成Java.lang.Class對象;

鏈接:執(zhí)行下面的校驗、準備和解析步驟,其中解析步驟是可以選擇的;

驗證:檢查導入類或接口的二進制數(shù)據(jù)的正確性;(文件格式驗證,元數(shù)據(jù)驗證,字節(jié)碼驗證,符號引用驗證)

準備:給類的靜態(tài)變量分配并初始化存儲空間;

解析:將常量池中的符號引用轉(zhuǎn)成直接引用;

初始化:激活類的靜態(tài)變量的初始化Java代碼和靜態(tài)Java代碼塊,并初始化要設(shè)置的變量值。

Class.forName()和ClassLoader.loadClass區(qū)別?

Class.forName(className)方法,內(nèi)部實際調(diào)用的方法是 Class.forName(className,true,classloader);

可以看到第2個boolean參數(shù)表示類是否需要初始化,Class.forName(className)默認是需要初始化,

一旦初始化,就會觸發(fā)目標對象的static塊代碼執(zhí)行,static參數(shù)也也會被再次初始化。

ClassLoader.loadClass(className)方法,內(nèi)部實際調(diào)用的方法是 ClassLoader.loadClass(className,false);

可以看到第2個 boolean參數(shù),表示目標對象是否進行鏈接,false表示不進行鏈接,由上面介紹可以,不進行鏈接意味著不進行包括初始化等一些列步驟,那么靜態(tài)塊和靜態(tài)對象就不會得到執(zhí)行

「在日常開發(fā)中的意義:」 還是有點用的, 其實在寫中間件的時候經(jīng)常會用上Class.forName(className),不開玩笑的說很少會考慮說靜態(tài)代碼塊被再次初始化了什么的問題, 不過確實可以不用。

原文鏈接:https://mp.weixin.qq.com/s/EKSfiMQjG2h3l5wlPUU9aQ

 

責任編輯:武曉燕 來源: 稀飯下雪
相關(guān)推薦

2021-11-04 14:32:17

Spring 面試作用域

2021-10-26 14:40:03

MySQL SQL 語句數(shù)據(jù)庫

2021-10-21 14:43:23

Java 語言 Java 基礎(chǔ)

2021-09-07 14:46:42

面試網(wǎng)絡(luò)HTTP 協(xié)議

2021-07-26 14:59:23

面試Redis內(nèi)存數(shù)據(jù)庫

2023-11-28 18:09:49

Java多態(tài)

2021-10-26 17:05:55

Redis字符串復雜度

2022-09-03 11:36:11

Python文件網(wǎng)絡(luò)

2021-04-14 10:02:59

網(wǎng)絡(luò)八股文協(xié)議

2021-08-12 09:28:24

Java多線程變量

2021-05-20 11:43:57

操作系統(tǒng)硬件軟件

2024-02-23 19:17:12

構(gòu)造函數(shù)C++開發(fā)

2024-10-12 09:26:32

線程池系統(tǒng)核心線程

2021-05-06 07:27:57

面試任務(wù)調(diào)度器

2023-11-29 17:28:07

2022-05-19 08:41:09

JVM虛擬機架構(gòu)

2022-05-27 14:43:45

JVM字節(jié)碼指令

2023-01-13 18:04:03

面試題消息中間件

2024-06-05 10:59:51

2022-01-04 08:54:32

Redis數(shù)據(jù)庫數(shù)據(jù)類型
點贊
收藏

51CTO技術(shù)棧公眾號