<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一個(gè) static 還能難得住我?

          共 6568字,需瀏覽 14分鐘

           ·

          2020-10-29 00:36


          點(diǎn)擊藍(lán)色“程序員cxuan?”關(guān)注我喲

          加個(gè)“星標(biāo)”,歡迎來(lái)撩


          這是程序員cxuan的第15期原創(chuàng)分享


          static 是我們?nèi)粘I钪薪?jīng)常用到的關(guān)鍵字,也是 Java 中非常重要的一個(gè)關(guān)鍵字,static 可以修飾變量、方法、做靜態(tài)代碼塊、靜態(tài)導(dǎo)包等,下面我們就來(lái)具體聊一聊這個(gè)關(guān)鍵字,我們先從基礎(chǔ)開(kāi)始,從基本用法入手,然后分析其原理、優(yōu)化等。


          1

          ?初識(shí)?static 關(guān)鍵字


          static 修飾變量

          static 關(guān)鍵字表示的概念是 全局的、靜態(tài)的,用它修飾的變量被稱為靜態(tài)變量。

          public?class?TestStatic?{
          ????
          ????static?int?i?=?10;?//?定義了一個(gè)靜態(tài)變量?i?
          }

          靜態(tài)變量也被稱為類變量,靜態(tài)變量是屬于這個(gè)類所有的。什么意思呢?這其實(shí)就是說(shuō),static 關(guān)鍵字只能定義在類的 {} 中,而不能定義在任何方法中。

          就算把方法中的 static 關(guān)鍵字去掉也是一樣的。

          static 屬于類所有,由類來(lái)直接調(diào)用 static 修飾的變量,它不需要手動(dòng)實(shí)例化類進(jìn)行調(diào)用

          public?class?TestStatic?{

          ????static?int?i?=?10;

          ????public?static?void?main(String[]?args)?{
          ????????System.out.println(TestStatic.i);
          ????}
          }

          這里你需要理解幾個(gè)變量的概念

          • 定義在構(gòu)造方法、代碼塊、方法的變量被稱為成員變量,成員變量的副本數(shù)量和實(shí)例的數(shù)量一樣。
          • 定義在方法、構(gòu)造方法、代碼塊內(nèi)的變量被稱為局部變量;
          • 定義在方法參數(shù)的變量被稱為參數(shù)。

          詳情參考

          都說(shuō)變量有七八種,到底誰(shuí)是 Java 的親兒子

          static 修飾方法

          static 可以修飾方法,被 static 修飾的方法被稱為靜態(tài)方法,其實(shí)就是在一個(gè)方法定義中加上 static 關(guān)鍵字進(jìn)行修飾,例如下面這樣

          static?void?sayHello(){}

          《Java 編程思想》在 P86 頁(yè)有一句經(jīng)典的描述

          static 方法就是沒(méi)有 this 的方法,在 static 內(nèi)部不能調(diào)用非靜態(tài)方法,反過(guò)來(lái)是可以的。而且可以在沒(méi)有創(chuàng)建任何對(duì)象的前提下,僅僅通過(guò)類本身來(lái)調(diào)用 static 方法,這實(shí)際上是 static 方法的主要用途

          其中有一句非常重要的話就是 static 方法就是沒(méi)有 this 的方法,也就是說(shuō),可以在不用創(chuàng)建對(duì)象的前提下就能夠訪問(wèn) static 方法,如何做到呢?看下面一段代碼

          在上面的例子中,由于 staticMethod 是靜態(tài)方法,所以能夠使用 類名.變量名進(jìn)行調(diào)用。

          因此,如果說(shuō)想在不創(chuàng)建對(duì)象的情況下調(diào)用某個(gè)方法,就可以將這個(gè)方法設(shè)置為 static。平常我們見(jiàn)的最多的 static 方法就是 main方 法,至于為什么 main 方法必須是 static 的,現(xiàn)在應(yīng)該很清楚了。因?yàn)槌绦蛟趫?zhí)行 main 方法的時(shí)候沒(méi)有創(chuàng)建任何對(duì)象,因此只有通過(guò)類名來(lái)訪問(wèn)。

          static 修飾方法的注意事項(xiàng)

          • 首先第一點(diǎn)就是最常用的,不用創(chuàng)建對(duì)象,直接類名.變量名 即可訪問(wèn);
          • static 修飾的方法內(nèi)部不能調(diào)用非靜態(tài)方法;

          • 非靜態(tài)方法內(nèi)部可以調(diào)用 static 靜態(tài)方法。

          static 修飾代碼塊

          static 關(guān)鍵字可以用來(lái)修飾代碼塊,代碼塊分為兩種,一種是使用 {} 代碼塊;一種是 static {} 靜態(tài)代碼塊。static 修飾的代碼塊被稱為靜態(tài)代碼塊。靜態(tài)代碼塊可以置于類中的任何地方,類中可以有多個(gè) static 塊,在類初次被加載的時(shí)候,會(huì)按照 static 代碼塊的順序來(lái)執(zhí)行,每個(gè) static 修飾的代碼塊只能執(zhí)行一次。我們會(huì)面會(huì)說(shuō)一下代碼塊的加載順序。下面是靜態(tài)代碼塊的例子

          static 代碼塊可以用來(lái)優(yōu)化程序執(zhí)行順序,是因?yàn)樗奶匦裕褐粫?huì)在類加載的時(shí)候執(zhí)行一次。

          static 用作靜態(tài)內(nèi)部類

          內(nèi)部類的使用場(chǎng)景比較少,但是內(nèi)部類還有具有一些比較有用的。在了解靜態(tài)內(nèi)部類前,我們先看一下內(nèi)部類的分類

          • 普通內(nèi)部類
          • 局部?jī)?nèi)部類
          • 靜態(tài)內(nèi)部類
          • 匿名內(nèi)部類

          靜態(tài)內(nèi)部類就是用 static 修飾的內(nèi)部類,靜態(tài)內(nèi)部類可以包含靜態(tài)成員,也可以包含非靜態(tài)成員,但是在非靜態(tài)內(nèi)部類中不可以聲明靜態(tài)成員。

          靜態(tài)內(nèi)部類有許多作用,由于非靜態(tài)內(nèi)部類的實(shí)例創(chuàng)建需要有外部類對(duì)象的引用,所以非靜態(tài)內(nèi)部類對(duì)象的創(chuàng)建必須依托于外部類的實(shí)例;而靜態(tài)內(nèi)部類的實(shí)例創(chuàng)建只需依托外部類;

          并且由于非靜態(tài)內(nèi)部類對(duì)象持有了外部類對(duì)象的引用,因此非靜態(tài)內(nèi)部類可以訪問(wèn)外部類的非靜態(tài)成員;而靜態(tài)內(nèi)部類只能訪問(wèn)外部類的靜態(tài)成員;

          • 內(nèi)部類需要脫離外部類對(duì)象來(lái)創(chuàng)建實(shí)例
          • 避免內(nèi)部類使用過(guò)程中出現(xiàn)內(nèi)存溢出
          public?class?ClassDemo?{
          ??
          ????private?int?a?=?10;
          ????private?static?int?b?=?20;

          ????static?class?StaticClass{
          ????????public?static?int?c?=?30;
          ????????public?int?d?=?40;
          ??????
          ????????public?static?void?print(){
          ????????????//下面代碼會(huì)報(bào)錯(cuò),靜態(tài)內(nèi)部類不能訪問(wèn)外部類實(shí)例成員
          ????????????//System.out.println(a);
          ?????
          ????????????//靜態(tài)內(nèi)部類只可以訪問(wèn)外部類類成員
          ????????????System.out.println("b?=?"+b);
          ????????????
          ????????}
          ??????
          ????????public?void?print01(){
          ????????????//靜態(tài)內(nèi)部?jī)?nèi)所處的類中的方法,調(diào)用靜態(tài)內(nèi)部類的實(shí)例方法,屬于外部類中調(diào)用靜態(tài)內(nèi)部類的實(shí)例方法
          ????????????StaticClass?sc?=?new?StaticClass();
          ????????????sc.print();
          ????????}???
          ????}
          }

          靜態(tài)導(dǎo)包

          不知道你注意到這種現(xiàn)象沒(méi)有,比如你使用了 java.util 內(nèi)的工具類時(shí),你需要導(dǎo)入 java.util 包,才能使用其內(nèi)部的工具類,如下

          但是還有一種導(dǎo)包方式是使用靜態(tài)導(dǎo)包,靜態(tài)導(dǎo)入就是使用 import static 用來(lái)導(dǎo)入某個(gè)類或者某個(gè)包中的靜態(tài)方法或者靜態(tài)變量。

          import?static?java.lang.Integer.*;

          public?class?StaticTest?{

          ????public?static?void?main(String[]?args)?{
          ????????System.out.println(MAX_VALUE);
          ????????System.out.println(toHexString(111));
          ????}
          }


          2

          static 進(jìn)階知識(shí)


          我們?cè)诹私饬?static 關(guān)鍵字的用法之后,來(lái)看一下 static 深入的用法,也就是由淺入深,慢慢來(lái),前戲要夠~????

          關(guān)于 static 的所屬類

          static 所修飾的屬性和方法都屬于類的,不會(huì)屬于任何對(duì)象;它們的調(diào)用方式都是 類名.屬性名/方法名,而實(shí)例變量和局部變量都是屬于具體的對(duì)象實(shí)例。

          static 修飾變量的存儲(chǔ)位置

          首先,先來(lái)認(rèn)識(shí)一下 JVM 的不同存儲(chǔ)區(qū)域。

          • 虛擬機(jī)棧 : Java 虛擬機(jī)棧是線程私有的數(shù)據(jù)區(qū),Java 虛擬機(jī)棧的生命周期與線程相同,虛擬機(jī)棧也是局部變量的存儲(chǔ)位置。方法在執(zhí)行過(guò)程中,會(huì)在虛擬機(jī)棧中創(chuàng)建一個(gè) 棧幀(stack frame)。

          • 本地方法棧: 本地方法棧也是線程私有的數(shù)據(jù)區(qū),本地方法棧存儲(chǔ)的區(qū)域主要是 Java 中使用 native 關(guān)鍵字修飾的方法所存儲(chǔ)的區(qū)域

          • 程序計(jì)數(shù)器:程序計(jì)數(shù)器也是線程私有的數(shù)據(jù)區(qū),這部分區(qū)域用于存儲(chǔ)線程的指令地址,用于判斷線程的分支、循環(huán)、跳轉(zhuǎn)、異常、線程切換和恢復(fù)等功能,這些都通過(guò)程序計(jì)數(shù)器來(lái)完成。

          • 方法區(qū):方法區(qū)是各個(gè)線程共享的內(nèi)存區(qū)域,它用于存儲(chǔ)虛擬機(jī)加載的 類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù),也就是說(shuō),static 修飾的變量存儲(chǔ)在方法區(qū)中

          • :堆是線程共享的數(shù)據(jù)區(qū),堆是 JVM 中最大的一塊存儲(chǔ)區(qū)域,所有的對(duì)象實(shí)例,包括實(shí)例變量都在堆上進(jìn)行相應(yīng)的分配。

          static 變量的生命周期

          static 變量的生命周期與類的生命周期相同,隨類的加載而創(chuàng)建,隨類的銷毀而銷毀;普通成員變量和其所屬的生命周期相同。

          static 序列化

          我們知道,序列化的目的就是為了 把 Java 對(duì)象轉(zhuǎn)換為字節(jié)序列。對(duì)象轉(zhuǎn)換為有序字節(jié)流,以便其能夠在網(wǎng)絡(luò)上傳輸或者保存在本地文件中。

          聲明為 static 和 transient 類型的變量不能被序列化,因?yàn)?static 修飾的變量保存在方法區(qū)中,只有堆內(nèi)存才會(huì)被序列化。而 transient 關(guān)鍵字的作用就是防止對(duì)象進(jìn)行序列化操作。

          類加載順序

          我們前面提到了類加載順序這么一個(gè)概念,static 修飾的變量和靜態(tài)代碼塊在使用前已經(jīng)被初始化好了,類的初始化順序依次是

          加載父類的靜態(tài)字段 -> 父類的靜態(tài)代碼塊 -> 子類靜態(tài)字段 -> 子類靜態(tài)代碼塊 -> 父類成員變量(非靜態(tài)字段)

          -> 父類非靜態(tài)代碼塊 -> 父類構(gòu)造器 -> 子類成員變量 ?-> 子類非靜態(tài)代碼塊 -> 子類構(gòu)造器

          static 經(jīng)常用作日志打印

          我們?cè)陂_(kāi)發(fā)過(guò)程中,經(jīng)常會(huì)使用 static 關(guān)鍵字作為日志打印,下面這行代碼你應(yīng)該經(jīng)常看到

          private?static?final?Logger?LOGGER?=?LogFactory.getLoggger(StaticTest.class);

          然而把 static 和 final 去掉都可以打印日志

          private?final?Logger?LOGGER?=?LogFactory.getLoggger(StaticTest.class);
          private?Logger?LOGGER?=?LogFactory.getLoggger(StaticTest.class);

          但是這種打印日志的方式存在問(wèn)題

          對(duì)于每個(gè) StaticTest 的實(shí)例化對(duì)象都會(huì)擁有一個(gè) LOGGER,如果創(chuàng)建了1000個(gè) StaticTest 對(duì)象,則會(huì)多出1000個(gè)Logger 對(duì)象,造成資源的浪費(fèi),因此通常會(huì)將 Logger 對(duì)象聲明為 static 變量,這樣一來(lái),能夠減少對(duì)內(nèi)存資源的占用。

          static 經(jīng)常用作單例模式

          由于單例模式指的就是對(duì)于不同的類來(lái)說(shuō),它的副本只有一個(gè),因此 static 可以和單例模式完全匹配。

          下面是一個(gè)經(jīng)典的雙重校驗(yàn)鎖實(shí)現(xiàn)單例模式的場(chǎng)景

          public?class?Singleton?{
          ??
          ????private?static?volatile?Singleton?singleton;
          ?
          ????private?Singleton()?{}
          ?
          ????public?static?Singleton?getInstance()?{
          ????????if?(singleton?==?null)?{
          ????????????synchronized?(Singleton.class)?{
          ????????????????if?(singleton?==?null)?{
          ????????????????????singleton?=?new?Singleton();
          ????????????????}
          ????????????}
          ????????}
          ????????return?singleton;
          ????}
          }

          來(lái)對(duì)上面代碼做一個(gè)簡(jiǎn)單的描述

          使用 static 保證 singleton 變量是靜態(tài)的,使用 volatile 保證 singleton 變量的可見(jiàn)性,使用私有構(gòu)造器確保 Singleton 不能被 new 實(shí)例化。

          使用 Singleton.getInstance() 獲取 singleton 對(duì)象,首先會(huì)進(jìn)行判斷,如果 singleton 為空,會(huì)鎖住 Singletion 類對(duì)象,這里有一些小伙伴們可能不知道為什么需要兩次判斷,這里來(lái)解釋下

          如果線程 t1 執(zhí)行到 singleton == null 后,判斷對(duì)象為 null,此時(shí)線程把執(zhí)行權(quán)交給了 t2,t2 判斷對(duì)象為 null,鎖住 Singleton 類對(duì)象,進(jìn)行下面的判斷和實(shí)例化過(guò)程。如果不進(jìn)行第二次判斷的話,那么 t1 在進(jìn)行第一次判空后,也會(huì)進(jìn)行實(shí)例化過(guò)程,此時(shí)仍然會(huì)創(chuàng)建多個(gè)對(duì)象。

          3

          ?類的構(gòu)造器是否是 static 的?


          這個(gè)問(wèn)題我相信大部分小伙伴都沒(méi)有考慮過(guò),在 Java 編程思想中有這么一句話 類的構(gòu)造器雖然沒(méi)有用 static 修飾,但是實(shí)際上是 static 方法,但是并沒(méi)有給出實(shí)際的解釋,但是這個(gè)問(wèn)題可以從下面幾個(gè)方面來(lái)回答

          • static 最簡(jiǎn)單、最方便記憶的規(guī)則就是沒(méi)有 this 引用。而在類的構(gòu)造器中,是有隱含的 this 綁定的,因?yàn)闃?gòu)造方法是和類綁定的,從這個(gè)角度來(lái)看,構(gòu)造器不是靜態(tài)的。
          • 從類的方法這個(gè)角度來(lái)看,因?yàn)?類.方法名不需要新創(chuàng)建對(duì)象就能夠訪問(wèn),所以從這個(gè)角度來(lái)看,構(gòu)造器也不是靜態(tài)的
          • 從 JVM 指令角度去看,我們來(lái)看一個(gè)例子
          public?class?StaticTest?{

          ????public?StaticTest(){}

          ????public?static?void?test(){

          ????}

          ????public?static?void?main(String[]?args)?{
          ????????StaticTest.test();
          ????????StaticTest?staticTest?=?new?StaticTest();
          ????}
          }

          我們使用 javap -c 生成 StaticTest 的字節(jié)碼看一下

          public class test.StaticTest {
          public test.StaticTest();
          Code:
          0: aload_0
          1: invokespecial #1 // Method java/lang/Object."":()V
          4: return

          public static void test();
          Code:
          0: return

          public static void main(java.lang.String[]);
          Code:
          0: invokestatic #2 // Method test:()V
          3: new #3 // class test/StaticTest
          6: dup
          7: invokespecial #4 // Method "":()V
          10: astore_1
          11: return
          }

          我們發(fā)現(xiàn),在調(diào)用 static 方法時(shí)是使用的 invokestatic 指令,new 對(duì)象調(diào)用的是 invokespecial 指令,而且在 JVM 規(guī)范中 https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokestatic 說(shuō)到

          從這個(gè)角度來(lái)講,invokestatic 指令是專門用來(lái)執(zhí)行 static 方法的指令;invokespecial 是專門用來(lái)執(zhí)行實(shí)例方法的指令;從這個(gè)角度來(lái)講,構(gòu)造器也不是靜態(tài)的。


          瀏覽 26
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天天怕天天色 | 亚洲小早川无码在线播放 | 免费看一级黄色片 | 日本hdav | 91麻豆精品国产91久久久久久久久 |