<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                ## 03 Java 常用關鍵字理解 ## 引導語 Java 中的關鍵字很多,大約有 50+,在命名上我們不能和這些關鍵字沖突的,編譯會報錯,每個關鍵字都代表著不同場景下的不同含義,接下來我們挑選 6 個比較重要的關鍵字,深入學習一下。 ### 1 static 意思是靜態的、全局的,一旦被修飾,說明被修飾的東西在一定范圍內是共享的,誰都可以訪問,這時候需要注意并發讀寫的問題。 1.1 修飾的對象 static 只能修飾類變量、方法和方法塊。補充:還有class。 當 static 修飾類變量時,如果該變量是 public 的話,表示該變量任何類都可以直接訪問,而且無需初始化類,直接使用 類名.static 變量 這種形式訪問即可。 這時候我們非常需要注意的一點就是線程安全的問題了,因為當多個線程同時對共享變量進行讀寫時,很有可能會出現并發問題,如我們定義了:public static List list = new ArrayList();這樣的共享變量。這個 list 如果同時被多個線程訪問的話,就有線程安全的問題,這時候一般有兩個解決辦法: 1. 把線程不安全的 ArrayList 換成 線程安全的 CopyOnWriteArrayList; 2. 每次訪問時,手動加鎖。 所以在使用 static 修飾類變量時,如何保證線程安全是我們常常需要考慮的。 當 static 修飾方法時,代表該方法和當前類是無關的,任意類都可以直接訪問(如果權限是 public 的話)。 有一點需要注意的是,該方法內部只能調用同樣被 static 修飾的方法,不能調用普通方法,我們常用的 util 類里面的各種方法,我們比較喜歡用 static 修飾方法,好處就是調用特別方便。 static 方法內部的變量在執行時是沒有線程安全問題的。方法執行時,數據運行在棧里面,棧的數據每個線程都是隔離開的,所以不會有線程安全的問題,所以 util 類的各個 static 方法,我們是可以放心使用的。 當 static 修飾方法塊時,我們叫做靜態塊,靜態塊常常用于在類啟動之前,初始化一些值,比如: ``` public static List<String> list = new ArrayList(); // 進行一些初始化的工作 static { list.add("1"); } ``` 這段代碼演示了靜態塊做一些初始化的工作,但需要注意的是,靜態塊只能調用同樣被 static 修飾的變量,并且 static 的變量需要寫在靜態塊的前面,不然編譯也會報錯。 1.2 初始化時機 對于被 static 修飾的類變量、方法塊和靜態方法的初始化時機,我們寫了一個測試 demo,如下圖: ![](https://img.kancloud.cn/c4/69/c469d2b7ffb8547341dc03f00d78e723_985x422.png) 打印出來的結果是: 父類靜態變量初始化 父類靜態塊初始化 子類靜態變量初始化 子類靜態塊初始化 main 方法執行 父類構造器初始化 子類構造器初始化 從結果中,我們可以看出兩點: 1. 父類的靜態變量和靜態塊比子類優先初始化; 2. 靜態變量和靜態塊比類構造器優先初始化。 被 static 修飾的方法,在類初始化的時候并不會初始化,只有當自己被調用時,才會被執行。 ### 2 final final 的意思是不變的,一般來說用于以下三種場景: 1. 被 final 修飾的類,表明該類是無法繼承的; 2. 被 final 修飾的方法,表明該方法是無法覆寫的; 3. 被 final 修飾的變量,說明該變量在聲明的時候,就必須初始化完成,而且以后也不能修改其內存地址。 第三點注意下,我們說的是無法修改其內存地址,并沒有說無法修改其值。因為對于 List、Map 這些集合類來說,被 final 修飾后,是可以修改其內部值的,但卻無法修改其初始化時的內存地址。 例子我們就不舉了,1-1 小節 String 的不變性就是一個很好的例子。 ### 3 try、catch、finally 這三個關鍵字常用于我們捕捉異常的一整套流程,try 用來確定代碼執行的范圍,catch 捕捉可能會發生的異常,finally 用來執行一定要執行的代碼塊,除了這些,我們還需要清楚,每個地方如果發生異常會怎么辦,我們舉一個例子來演示一下: ``` public void testCatchFinally() { try { log.info("try is run"); if (true) { throw new RuntimeException("try exception"); } } catch (Exception e) { log.info("catch is run"); if (true) { throw new RuntimeException("catch exception"); } } finally { log.info("finally is run"); } } ``` 這個代碼演示了在 try、catch 中都遇到了異常,代碼的執行順序為:try -> catch -> finally,輸出的結果如下: ![](https://img.kancloud.cn/62/28/62289962ee21ef8941c181b8fd738a49_986x167.png) 可以看到兩點: 1. finally 先執行后,再拋出 catch 的異常; 2. 最終捕獲的異常是 catch 的異常,try 拋出來的異常已經被 catch 吃掉了,所以當我們遇見 catch 也有可能會拋出 異常時,我們可以先打印出 try 的異常,這樣 try 的異常在日志中就會有所體現。 ### 4 volatile volatile 的意思是可見的,常用來修飾某個共享變量,意思是當共享變量的值被修改后,會及時通知到其它線程上,其它線程就能知道當前共享變量的值已經被修改了。 我們再說原理之前,先說下基礎知識。就是在多核 CPU 下,為了提高效率,線程在拿值時,是直接和 CPU 緩存打交道的,而不是內存。主要是因為 CPU 緩存執行速度更快,比如線程要拿值 C,會直接從 CPU 緩存中拿, CPU 緩存中沒有,就會從內存中拿,所以線程讀的操作永遠都是拿 CPU 緩存的值。 這時候會產生一個問題,CPU 緩存中的值和內存中的值可能并不是時刻都同步,導致線程計算的值可能不是最新的,共享變量的值有可能已經被其它線程所修改了,但此時修改是機器內存的值,CPU 緩存的值還是老的,導致計算會出現問題。 這時候有個機制,就是內存會主動通知 CPU 緩存。當前共享變量的值已經失效了,你需要重新來拉取一份,CPU 緩存就會重新從內存中拿取一份最新的值。 volatile 關鍵字就會觸發這種機制,加了 volatile 關鍵字的變量,就會被識別成共享變量,內存中值被修改后,會通知到各個 CPU 緩存,使 CPU 緩存中的值也對應被修改,從而保證線程從 CPU 緩存中拿取出來的值是最新的。 我們畫了一個圖來說明一下: ![](https://img.kancloud.cn/51/b5/51b55b4dc46fb852370207d992c068bc_982x560.png) 從圖中我們可以看到,線程 1 和線程 2 一開始都讀取了 C 值,CPU 1 和 CPU 2 緩存中也都有了 C 值,然后線程 1 把 C 值修改了,這時候內存的值和 CPU 2 緩存中的 C 值就不等了,內存這時發現 C 值被 volatile 關鍵字修飾,發現其是共享變量,就會使 CPU 2 緩存中的 C 值狀態置為無效,CPU 2 會從內存中重新拉取最新的值,這時候線程 2 再來讀取 C 值時,讀取的已經是內存中最新的值了。 ### 5 transient transient 關鍵字我們常用來修飾類變量,意思是當前變量是無需進行序列化的。在序列化時,就會忽略該變量,這些在序列化工具底層,就已經對 transient 進行了支持。 ### 6 default default 關鍵字一般會用在接口的方法上,意思是對于該接口,子類是無需強制實現的,但自己必須有默認實現,我們舉個例子如下: default 關鍵字被很多源碼使用,我們后面會說。 ![](https://img.kancloud.cn/6a/fa/6afadb29e0ded760b5d8473312653123_986x373.png) ### 7 面試題 #### 7.1 如何證明 static 靜態變量和類無關? 答:從三個方面就可以看出靜態變量和類無關。 1. 我們不需要初始化類就可直接使用靜態變量; 2. 我們在類中寫個 main 方法運行,即便不寫初始化類的代碼,靜態變量都會自動初始化; 3. 靜態變量只會初始化一次,初始化完成之后,不管我再 new 多少個類出來,靜態變量都不會再初始化了。 不僅僅是靜態變量,靜態方法塊也和類無關。 #### 7.2 常常看見變量和方法被 static 和 final 兩個關鍵字修飾,為什么這么做? 答:這么做有兩個目的: 1. 變量和方法于類無關,可以直接使用,使用比較方便; 2. 強調變量內存地址不可變,方法不可繼承覆寫,強調了方法內部的穩定性。 #### 7.3 catch 中發生了未知異常,finally 還會執行么? 答:會的,catch 發生了異常,finally 還會執行的,并且是 finally 執行完成之后,才會拋出 catch 中的異常。 不過 catch 會吃掉 try 中拋出的異常,為了避免這種情況,在一些可以預見 catch 中會發生異常的地方,先把 try 拋出的異常打印出來,這樣從日志中就可以看到完整的異常了。 #### 7.4 volatile 關鍵字的作用和原理 答:這個上文說的比較清楚,可以參考上文。 ### 總結 Java 的關鍵字屬于比較基礎的內容,我們需要清晰明確其含義,才能在后續源碼閱讀和工作中碰到這些關鍵字時了然于心,才能明白為什么會在這里使用這樣的關鍵字。比如 String 源碼是如何使用 final 關鍵字達到起不變性的,比如 Java 8 集合中 Map 是如何利用 default 關鍵字新增各種方法的,這些我們在后續內容都會提到。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看